openstudio-standards 0.2.12.rc4 → 0.2.12.rc5
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-ashrae_90_1(space_types).xlsx +0 -0
- data/data/standards/OpenStudio_Standards-ashrae_90_1.xlsx +0 -0
- data/data/standards/test_performance_expected_dd_results.csv +950 -950
- data/lib/openstudio-standards.rb +8 -1
- data/lib/openstudio-standards/btap/btap.model.rb +1 -1
- data/lib/openstudio-standards/btap/economics.rb +14 -11
- data/lib/openstudio-standards/btap/envelope.rb +185 -257
- data/lib/openstudio-standards/btap/fileio.rb +1 -0
- data/lib/openstudio-standards/btap/geometry.rb +21 -1
- data/lib/openstudio-standards/btap/measures.rb +12 -11
- data/lib/openstudio-standards/btap/schedules.rb +3 -12
- data/lib/openstudio-standards/hvac_sizing/Siz.AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.rb +178 -0
- data/lib/openstudio-standards/hvac_sizing/Siz.CoilCoolingDXMultiSpeed.rb +8 -8
- data/lib/openstudio-standards/hvac_sizing/Siz.Model.rb +3 -0
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.hvac_systems.rb +3 -3
- data/lib/openstudio-standards/standards/Standards.AirLoopHVAC.rb +25 -23
- data/lib/openstudio-standards/standards/Standards.CoilCoolingDXMultiSpeed.rb +91 -0
- data/lib/openstudio-standards/standards/Standards.CoilDX.rb +20 -2
- data/lib/openstudio-standards/standards/Standards.CoilHeatingGasMultiStage.rb +39 -0
- data/lib/openstudio-standards/standards/Standards.Model.rb +29 -0
- data/lib/openstudio-standards/standards/Standards.ThermalZone.rb +37 -4
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.unitary_acs.json +15 -15
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/data/ashrae_90_1_2007.unitary_acs.json +15 -15
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/data/ashrae_90_1_2010.unitary_acs.json +5 -5
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.unitary_acs.json +15 -15
- data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_1980_2004/data/doe_ref_1980_2004.spc_typ.json +5963 -2723
- data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_pre_1980/data/doe_ref_pre_1980.spc_typ.json +5917 -2697
- data/lib/openstudio-standards/standards/ashrae_90_1/nrel_zne_ready_2017/data/nrel_zne_ready_2017.spc_typ.json +2011 -1112
- data/lib/openstudio-standards/standards/ashrae_90_1/ze_aedg_multifamily/data/ze_aedg_multifamily.spc_typ.json +1946 -1106
- data/lib/openstudio-standards/standards/necb/BTAP1980TO2010/btap_1980to2010.rb +2 -18
- data/lib/openstudio-standards/standards/necb/BTAP1980TO2010/data/space_types.json +1677 -1005
- data/lib/openstudio-standards/standards/necb/BTAPPRE1980/btap_pre1980.rb +64 -13
- data/lib/openstudio-standards/standards/necb/BTAPPRE1980/building_envelope.rb +31 -19
- data/lib/openstudio-standards/standards/necb/BTAPPRE1980/data/curves.json +75 -0
- data/lib/openstudio-standards/standards/necb/BTAPPRE1980/data/heat_pumps.json +16 -16
- data/lib/openstudio-standards/standards/necb/BTAPPRE1980/data/space_types.json +1677 -1005
- data/lib/openstudio-standards/standards/necb/ECMS/data/boiler_set.json +29 -0
- data/lib/openstudio-standards/standards/necb/ECMS/data/curves.json +913 -0
- data/lib/openstudio-standards/standards/necb/ECMS/data/equip_eff_lim.json +52 -0
- data/lib/openstudio-standards/standards/necb/ECMS/data/erv.json +105 -0
- data/lib/openstudio-standards/standards/necb/ECMS/data/furnace_set.json +23 -0
- data/lib/openstudio-standards/standards/necb/ECMS/data/heat_pumps.json +803 -0
- data/lib/openstudio-standards/standards/necb/ECMS/data/heat_pumps_heating.json +787 -0
- data/lib/openstudio-standards/standards/necb/ECMS/data/shw_set.json +29 -0
- data/lib/openstudio-standards/standards/necb/ECMS/ecms.rb +87 -0
- data/lib/openstudio-standards/standards/necb/ECMS/erv.rb +22 -0
- data/lib/openstudio-standards/standards/necb/ECMS/hvac_systems.rb +1593 -0
- data/lib/openstudio-standards/standards/necb/NECB2011/autozone.rb +68 -33
- data/lib/openstudio-standards/standards/necb/NECB2011/beps_compliance_path.rb +24 -13
- data/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb +104 -99
- data/lib/openstudio-standards/standards/necb/NECB2011/data/constants.json +24 -24
- data/lib/openstudio-standards/standards/necb/NECB2011/data/curves.json +50 -0
- data/lib/openstudio-standards/standards/necb/NECB2011/data/erv.json +31 -0
- data/lib/openstudio-standards/standards/necb/NECB2011/data/led_lighting_data.json +2028 -0
- data/lib/openstudio-standards/standards/necb/NECB2011/data/space_types.json +1745 -1297
- data/lib/openstudio-standards/standards/necb/NECB2011/daylighting_control.md +70 -0
- data/lib/openstudio-standards/standards/necb/NECB2011/demand_controlled_ventilation.md +46 -0
- data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_1_multi_speed.rb +69 -107
- data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_1_single_speed.rb +24 -1
- data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_multi_speed.rb +139 -141
- data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_3_and_8_single_speed.rb +24 -0
- data/lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb +344 -234
- data/lib/openstudio-standards/standards/necb/NECB2011/led_lighting.md +51 -0
- data/lib/openstudio-standards/standards/necb/NECB2011/lighting.rb +57 -9
- data/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +1060 -34
- data/lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb +9 -1
- data/lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb +1 -1
- data/lib/openstudio-standards/standards/necb/NECB2015/data/led_lighting_data.json +2883 -0
- data/lib/openstudio-standards/standards/necb/NECB2015/data/space_types.json +2554 -1916
- data/lib/openstudio-standards/standards/necb/NECB2015/necb_2015.rb +32 -1
- data/lib/openstudio-standards/standards/necb/NECB2017/data/led_lighting_data.json +2883 -0
- data/lib/openstudio-standards/standards/necb/NECB2017/data/space_types.json +2554 -1916
- data/lib/openstudio-standards/standards/necb/NECB2017/necb_2017.rb +29 -0
- data/lib/openstudio-standards/version.rb +1 -1
- metadata +21 -2
@@ -0,0 +1,51 @@
|
|
1
|
+
# High Performance LED Lighting Measure
|
2
|
+
This measure adds a new lighting definition regarding LED lighting in each space of the model,
|
3
|
+
and replace the existing lighting definition with the LED lighting definition in each space.
|
4
|
+
|
5
|
+
# Description
|
6
|
+
The workflow of this measure is as follows:
|
7
|
+
1. Define a new LED lighting definition for each space.
|
8
|
+
2. Use the new LED lighting definition instead of the existing lighting definition in each space.
|
9
|
+
|
10
|
+
# Approach
|
11
|
+
This measure follows the functions already existed in the BTAP environment with respect to setting lights in spaces.<br>
|
12
|
+
However, a new function called **set_lighting_per_area_led_lighting** has been created in necb_2011.rb to set lighting power density (LPD) for LED lighting.<br>
|
13
|
+
Moreover, the **apply_standard_lights** function (in lighting.rb) has been modified to set the three fields of
|
14
|
+
fraction radiant, fraction visible, and return air fraction for LED lighting.<br>
|
15
|
+
Furthermore, two variables have been added to the **apply_standard_lights** function:
|
16
|
+
(1) **lights_type** to specify which lighting type to be used in the model. The lighting types that a user can choose are: CFL, LED.
|
17
|
+
(2) **scale** to specify whether LPD default values are used or a fraction of LPD default values are used in the model.
|
18
|
+
|
19
|
+
# Testing Plan
|
20
|
+
* This measure has been called in the **apply_loads** function (in necb_2011.rb) -> **model_add_loads** function (in necb_2011.rb)
|
21
|
+
-> **space_type_apply_internal_loads** function (in beps_compliance_path.rb) -> **apply_standard_lights** function (in lighting.rb).
|
22
|
+
* This measure was tested for NECB 2011 full service restaurant archetype.
|
23
|
+
* Note that regarding atriums' heights, since none of the archetypes has atriums,
|
24
|
+
the testing procedure was performed for the space type including the "Dining" term and with some tweaks in the LPD equations.
|
25
|
+
|
26
|
+
# Waiting On
|
27
|
+
* There are four fields in the OS:Lights:Definition object that need to be updated in standards/lib/openstudio-standards/standards/necb/NECB2011/**data/led_lighting_data.json**, as follows:
|
28
|
+
1. LPD (W/m<sup>2</sup>)
|
29
|
+
2. Fraction Radiant
|
30
|
+
3. Fraction Visible
|
31
|
+
4. Return Air Fraction
|
32
|
+
|
33
|
+
* Note that three xlsx files (**led_lighting_data_necb2011.xlsx**, **led_lighting_data_necb2015.xlsx**, **led_lighting_data_necb2017.xlsx**)
|
34
|
+
should be updated as per Mike Lubun's xlsx files for lighting sets.
|
35
|
+
* To this end, openstudio-standards/lib/openstudio-standards/utilities/**LEDLightingData_xlsx_to_json.rb** can be used to convert the xlsx files to json format.
|
36
|
+
* Once the openstudio-standards/lib/openstudio-standards/**btap/led_lighting_data.json** file is generated using LEDLightingData_xlsx_to_json.rb,
|
37
|
+
openstudio-standards/lib/openstudio-standards/standards/necb/NECB2011/**data/led_lighting_data.json** should be updated manually (copy and paste from **btap/led_lighting_data.json**).
|
38
|
+
|
39
|
+
# Files Added/Modified
|
40
|
+
* Files have been modified:
|
41
|
+
* **necb_2011.rb**
|
42
|
+
* **beps_compliance_path.rb**
|
43
|
+
* **lighting.rb**
|
44
|
+
* Files have been added:
|
45
|
+
* **led_lighting.md**
|
46
|
+
* openstudio-standards/lib/openstudio-standards/utilities/**LEDLightingData_xlsx_to_json.rb**
|
47
|
+
* openstudio-standards/lib/openstudio-standards/btap/**led_lighting_data_necb2011.xlsx**
|
48
|
+
* openstudio-standards/lib/openstudio-standards/btap/**led_lighting_data_necb2015.xlsx**
|
49
|
+
* openstudio-standards/lib/openstudio-standards/btap/**led_lighting_data_necb2017.xlsx**
|
50
|
+
* openstudio-standards/lib/openstudio-standards/**btap/led_lighting_data.json**
|
51
|
+
* openstudio-standards/lib/openstudio-standards/standards/necb/NECB2011/**data/led_lighting_data.json**
|
@@ -1,5 +1,10 @@
|
|
1
1
|
class NECB2011
|
2
|
-
|
2
|
+
|
3
|
+
def apply_standard_lights(set_lights: true,
|
4
|
+
space_type:, space_type_properties:,
|
5
|
+
lights_type: 'NECB_Default',
|
6
|
+
lights_scale: 1.0)
|
7
|
+
|
3
8
|
lights_have_info = false
|
4
9
|
lighting_per_area = space_type_properties['lighting_per_area'].to_f
|
5
10
|
lighting_per_person = space_type_properties['lighting_per_person'].to_f
|
@@ -7,8 +12,27 @@ class NECB2011
|
|
7
12
|
lights_frac_radiant = space_type_properties['lighting_fraction_radiant'].to_f
|
8
13
|
lights_frac_visible = space_type_properties['lighting_fraction_visible'].to_f
|
9
14
|
lights_frac_replaceable = space_type_properties['lighting_fraction_replaceable'].to_f
|
10
|
-
lights_have_info = true
|
11
|
-
|
15
|
+
lights_have_info = true if !lighting_per_area.zero? or !lighting_per_person.zero?
|
16
|
+
|
17
|
+
|
18
|
+
##### NOTE: Reference for LED lighting's return air, radiant, and visible fraction values is: page 142, NREL (2014), "Proven Energy-Saving Technologies for Commercial Properties", available at https://www.nrel.gov/docs/fy15osti/63807.pdf
|
19
|
+
if lights_type == 'LED'
|
20
|
+
led_lights_have_info = false
|
21
|
+
led_spacetype_data = @standards_data['tables']['led_lighting_data']['table']
|
22
|
+
standards_building_type = space_type.standardsBuildingType.is_initialized ? space_type.standardsBuildingType.get : nil
|
23
|
+
standards_space_type = space_type.standardsSpaceType.is_initialized ? space_type.standardsSpaceType.get : nil
|
24
|
+
led_space_type_properties = led_spacetype_data.detect { |s| (s['building_type'] == standards_building_type) && (s['space_type'] == standards_space_type) }
|
25
|
+
if led_space_type_properties.nil?
|
26
|
+
raise("#{standards_building_type} for #{standards_space_type} was not found please verify the led lighting database names match the space type names.")
|
27
|
+
end
|
28
|
+
|
29
|
+
lighting_per_area_led_lighting = led_space_type_properties['lighting_per_area'].to_f
|
30
|
+
lights_frac_to_return_air_led_lighting = led_space_type_properties['lighting_fraction_to_return_air'].to_f
|
31
|
+
lights_frac_radiant_led_lighting = led_space_type_properties['lighting_fraction_radiant'].to_f
|
32
|
+
lights_frac_visible_led_lighting = led_space_type_properties['lighting_fraction_visible'].to_f
|
33
|
+
led_lights_have_info = true unless lighting_per_area_led_lighting.zero?
|
34
|
+
|
35
|
+
end
|
12
36
|
|
13
37
|
if set_lights && lights_have_info
|
14
38
|
|
@@ -16,9 +40,15 @@ class NECB2011
|
|
16
40
|
instances = space_type.lights.sort
|
17
41
|
if instances.size.zero?
|
18
42
|
definition = OpenStudio::Model::LightsDefinition.new(space_type.model)
|
19
|
-
|
43
|
+
if lights_type == 'NECB_Default'
|
44
|
+
definition.setName("#{space_type.name} Lights Definition")
|
45
|
+
elsif lights_type == 'LED'
|
46
|
+
definition.setName("#{space_type.name} Lights Definition - LED lighting")
|
47
|
+
end
|
48
|
+
# puts definition.name().to_s
|
20
49
|
instance = OpenStudio::Model::Lights.new(definition)
|
21
50
|
instance.setName("#{space_type.name} Lights")
|
51
|
+
# puts instance.name.to_s
|
22
52
|
instance.setSpaceType(space_type)
|
23
53
|
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} had no lights, one has been created.")
|
24
54
|
instances << instance
|
@@ -34,20 +64,39 @@ class NECB2011
|
|
34
64
|
space_type.lights.sort.each do |inst|
|
35
65
|
definition = inst.lightsDefinition
|
36
66
|
unless lighting_per_area.zero?
|
37
|
-
|
67
|
+
if lights_type == 'NECB_Default'
|
68
|
+
set_lighting_per_area(space_type, definition, lighting_per_area)
|
69
|
+
elsif lights_type == 'LED'
|
70
|
+
set_lighting_per_area_led_lighting(space_type: space_type,
|
71
|
+
definition: definition,
|
72
|
+
lighting_per_area_led_lighting: lighting_per_area_led_lighting,
|
73
|
+
lights_scale: lights_scale)
|
74
|
+
end
|
38
75
|
end
|
39
76
|
unless lighting_per_person.zero?
|
40
77
|
definition.setWattsperPerson(OpenStudio.convert(lighting_per_person.to_f, 'W/person', 'W/person').get)
|
41
78
|
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set lighting to #{lighting_per_person} W/person.")
|
42
79
|
end
|
43
80
|
unless lights_frac_to_return_air.zero?
|
44
|
-
|
81
|
+
if lights_type == 'NECB_Default'
|
82
|
+
definition.setReturnAirFraction(lights_frac_to_return_air)
|
83
|
+
elsif lights_type == 'LED'
|
84
|
+
definition.setReturnAirFraction(lights_frac_to_return_air_led_lighting)
|
85
|
+
end
|
45
86
|
end
|
46
87
|
unless lights_frac_radiant.zero?
|
47
|
-
|
88
|
+
if lights_type == 'NECB_Default'
|
89
|
+
definition.setFractionRadiant(lights_frac_radiant)
|
90
|
+
elsif lights_type == 'LED'
|
91
|
+
definition.setFractionRadiant(lights_frac_radiant_led_lighting)
|
92
|
+
end
|
48
93
|
end
|
49
94
|
unless lights_frac_visible.zero?
|
50
|
-
|
95
|
+
if lights_type == 'NECB_Default'
|
96
|
+
definition.setFractionVisible(lights_frac_visible)
|
97
|
+
elsif lights_type == 'LED'
|
98
|
+
definition.setFractionVisible(lights_frac_visible_led_lighting)
|
99
|
+
end
|
51
100
|
end
|
52
101
|
# unless lights_frac_replaceable.zero?
|
53
102
|
# definition.setFractionReplaceable(lights_frac_replaceable)
|
@@ -70,7 +119,6 @@ class NECB2011
|
|
70
119
|
additional_lights.setName("#{space_type.name} Additional Lights")
|
71
120
|
additional_lights.setSpaceType(space_type)
|
72
121
|
end
|
73
|
-
|
74
122
|
end
|
75
123
|
end
|
76
124
|
|
@@ -40,7 +40,7 @@ class NECB2011 < Standard
|
|
40
40
|
else
|
41
41
|
path = "#{File.dirname(__FILE__)}/../common/"
|
42
42
|
raise ('Could not find common folder') unless Dir.exist?(path)
|
43
|
-
files = Dir.glob("#{path}/*.json").select {|e| File.file? e}
|
43
|
+
files = Dir.glob("#{path}/*.json").select { |e| File.file? e }
|
44
44
|
files.each do |file|
|
45
45
|
data = JSON.parse(File.read(file))
|
46
46
|
if not data["tables"].nil?
|
@@ -62,7 +62,7 @@ class NECB2011 < Standard
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
else
|
65
|
-
files = Dir.glob("#{File.dirname(__FILE__)}/data/*.json").select {|e| File.file? e}
|
65
|
+
files = Dir.glob("#{File.dirname(__FILE__)}/data/*.json").select { |e| File.file? e }
|
66
66
|
files.each do |file|
|
67
67
|
data = JSON.parse(File.read(file))
|
68
68
|
if not data["tables"].nil?
|
@@ -117,7 +117,7 @@ class NECB2011 < Standard
|
|
117
117
|
end
|
118
118
|
|
119
119
|
def get_all_spacetype_names
|
120
|
-
return @standards_data['space_types'].map {|space_types| [space_types['building_type'], space_types['space_type']]}
|
120
|
+
return @standards_data['space_types'].map { |space_types| [space_types['building_type'], space_types['space_type']] }
|
121
121
|
end
|
122
122
|
|
123
123
|
# Enter in [latitude, longitude] for each loc and this method will return the distance.
|
@@ -129,8 +129,8 @@ class NECB2011 < Standard
|
|
129
129
|
dlat_rad = (loc2[0] - loc1[0]) * rad_per_deg # Delta, converted to rad
|
130
130
|
dlon_rad = (loc2[1] - loc1[1]) * rad_per_deg
|
131
131
|
|
132
|
-
lat1_rad, lon1_rad = loc1.map {|i| i * rad_per_deg}
|
133
|
-
lat2_rad, lon2_rad = loc2.map {|i| i * rad_per_deg}
|
132
|
+
lat1_rad, lon1_rad = loc1.map { |i| i * rad_per_deg }
|
133
|
+
lat2_rad, lon2_rad = loc2.map { |i| i * rad_per_deg }
|
134
134
|
|
135
135
|
a = Math.sin(dlat_rad / 2) ** 2 + Math.cos(lat1_rad) * Math.cos(lat2_rad) * Math.sin(dlon_rad / 2) ** 2
|
136
136
|
c = 2 * Math::atan2(Math::sqrt(a), Math::sqrt(1 - a))
|
@@ -169,13 +169,67 @@ class NECB2011 < Standard
|
|
169
169
|
epw_file:,
|
170
170
|
debug: false,
|
171
171
|
sizing_run_dir: Dir.pwd,
|
172
|
-
primary_heating_fuel: 'DefaultFuel'
|
172
|
+
primary_heating_fuel: 'DefaultFuel',
|
173
|
+
dcv_type: 'NECB_Default',
|
174
|
+
lights_type: 'NECB_Default',
|
175
|
+
lights_scale: 1.0,
|
176
|
+
daylighting_type: 'NECB_Default',
|
177
|
+
ecm_system_name: 'NECB_Default',
|
178
|
+
erv_package: 'NECB_Default',
|
179
|
+
boiler_eff: nil,
|
180
|
+
furnace_eff: nil,
|
181
|
+
shw_eff: nil,
|
182
|
+
ext_wall_cond: nil,
|
183
|
+
ext_floor_cond: nil,
|
184
|
+
ext_roof_cond: nil,
|
185
|
+
ground_wall_cond: nil,
|
186
|
+
ground_floor_cond: nil,
|
187
|
+
ground_roof_cond: nil,
|
188
|
+
door_construction_cond: nil,
|
189
|
+
fixed_window_cond: nil,
|
190
|
+
glass_door_cond: nil,
|
191
|
+
overhead_door_cond: nil,
|
192
|
+
skylight_cond: nil,
|
193
|
+
glass_door_solar_trans: nil,
|
194
|
+
fixed_wind_solar_trans: nil,
|
195
|
+
skylight_solar_trans: nil,
|
196
|
+
fdwr_set: -1.0,
|
197
|
+
srr_set: -1.0
|
198
|
+
|
199
|
+
)
|
173
200
|
|
174
201
|
model = load_building_type_from_library(building_type: building_type)
|
175
202
|
return model_apply_standard(model: model,
|
176
203
|
epw_file: epw_file,
|
177
204
|
sizing_run_dir: sizing_run_dir,
|
178
|
-
primary_heating_fuel: primary_heating_fuel
|
205
|
+
primary_heating_fuel: primary_heating_fuel,
|
206
|
+
dcv_type: dcv_type, # Four options: (1) 'NECB_Default', (2) 'No_DCV', (3) 'Occupancy_based_DCV' , (4) 'CO2_based_DCV'
|
207
|
+
lights_type: lights_type, # Two options: (1) 'NECB_Default', (2) 'LED'
|
208
|
+
lights_scale: lights_scale,
|
209
|
+
daylighting_type: daylighting_type, # Two options: (1) 'NECB_Default', (2) 'add_daylighting_controls'
|
210
|
+
ecm_system_name: ecm_system_name,
|
211
|
+
erv_package: erv_package,
|
212
|
+
boiler_eff: boiler_eff,
|
213
|
+
furnace_eff: furnace_eff,
|
214
|
+
shw_eff: shw_eff,
|
215
|
+
ext_wall_cond: ext_wall_cond,
|
216
|
+
ext_floor_cond: ext_floor_cond,
|
217
|
+
ext_roof_cond: ext_roof_cond,
|
218
|
+
ground_wall_cond: ground_wall_cond,
|
219
|
+
ground_floor_cond: ground_floor_cond,
|
220
|
+
ground_roof_cond: ground_roof_cond,
|
221
|
+
door_construction_cond: door_construction_cond,
|
222
|
+
fixed_window_cond: fixed_window_cond,
|
223
|
+
glass_door_cond: glass_door_cond,
|
224
|
+
overhead_door_cond: overhead_door_cond,
|
225
|
+
skylight_cond: skylight_cond,
|
226
|
+
glass_door_solar_trans: glass_door_solar_trans,
|
227
|
+
fixed_wind_solar_trans: fixed_wind_solar_trans,
|
228
|
+
skylight_solar_trans: skylight_solar_trans,
|
229
|
+
fdwr_set: fdwr_set,
|
230
|
+
srr_set: srr_set
|
231
|
+
|
232
|
+
)
|
179
233
|
end
|
180
234
|
|
181
235
|
def load_building_type_from_library(building_type:)
|
@@ -191,25 +245,146 @@ class NECB2011 < Standard
|
|
191
245
|
def model_apply_standard(model:,
|
192
246
|
epw_file:,
|
193
247
|
sizing_run_dir: Dir.pwd,
|
194
|
-
primary_heating_fuel: 'DefaultFuel'
|
248
|
+
primary_heating_fuel: 'DefaultFuel',
|
249
|
+
dcv_type: 'NECB_Default',
|
250
|
+
lights_type: 'NECB_Default',
|
251
|
+
lights_scale: 1.0,
|
252
|
+
daylighting_type: 'NECB_Default',
|
253
|
+
ecm_system_name: 'NECB_Default',
|
254
|
+
erv_package: 'NECB_Default',
|
255
|
+
boiler_eff: nil,
|
256
|
+
furnace_eff: nil,
|
257
|
+
shw_eff: nil,
|
258
|
+
ext_wall_cond: nil,
|
259
|
+
ext_floor_cond: nil,
|
260
|
+
ext_roof_cond: nil,
|
261
|
+
ground_wall_cond: nil,
|
262
|
+
ground_floor_cond: nil,
|
263
|
+
ground_roof_cond: nil,
|
264
|
+
door_construction_cond: nil,
|
265
|
+
fixed_window_cond: nil,
|
266
|
+
glass_door_cond: nil,
|
267
|
+
overhead_door_cond: nil,
|
268
|
+
skylight_cond: nil,
|
269
|
+
glass_door_solar_trans: nil,
|
270
|
+
fixed_wind_solar_trans: nil,
|
271
|
+
skylight_solar_trans: nil,
|
272
|
+
fdwr_set: -1,
|
273
|
+
srr_set: -1,
|
274
|
+
rotation_degrees: nil,
|
275
|
+
scale_x: nil,
|
276
|
+
scale_y: nil,
|
277
|
+
scale_z: nil
|
278
|
+
)
|
279
|
+
|
280
|
+
BTAP::Geometry::rotate_building(model: model,degrees: rotation_degrees) unless rotation_degrees.nil?
|
281
|
+
unless scale_x.nil? && scale_y.nil? && scale_z.nil?
|
282
|
+
scale_x = 1 if scale_x.nil?
|
283
|
+
scale_y = 1 if scale_y.nil?
|
284
|
+
scale_z = 1 if scale_y.nil?
|
285
|
+
BTAP::Geometry::scale_model(model, scale_x, scale_y, scale_z)
|
286
|
+
end
|
287
|
+
|
195
288
|
apply_weather_data(model: model, epw_file: epw_file)
|
196
|
-
apply_loads(model: model)
|
197
|
-
apply_envelope(model: model
|
198
|
-
|
199
|
-
|
289
|
+
apply_loads(model: model, lights_type: lights_type, lights_scale: lights_scale)
|
290
|
+
apply_envelope(model: model,
|
291
|
+
ext_wall_cond: ext_wall_cond,
|
292
|
+
ext_floor_cond: ext_floor_cond,
|
293
|
+
ext_roof_cond: ext_roof_cond,
|
294
|
+
ground_wall_cond: ground_wall_cond,
|
295
|
+
ground_floor_cond: ground_floor_cond,
|
296
|
+
ground_roof_cond: ground_roof_cond,
|
297
|
+
door_construction_cond: door_construction_cond,
|
298
|
+
fixed_window_cond: fixed_window_cond,
|
299
|
+
glass_door_cond: glass_door_cond,
|
300
|
+
overhead_door_cond: overhead_door_cond,
|
301
|
+
skylight_cond: skylight_cond,
|
302
|
+
glass_door_solar_trans: glass_door_solar_trans,
|
303
|
+
fixed_wind_solar_trans: fixed_wind_solar_trans,
|
304
|
+
skylight_solar_trans: skylight_solar_trans)
|
305
|
+
apply_fdwr_srr_daylighting(model: model,
|
306
|
+
fdwr_set: fdwr_set,
|
307
|
+
srr_set: srr_set)
|
308
|
+
apply_auto_zoning(model: model,
|
309
|
+
sizing_run_dir: sizing_run_dir,
|
310
|
+
lights_type: lights_type,
|
311
|
+
lights_scale: lights_scale)
|
312
|
+
apply_systems_and_efficiencies(model: model,
|
313
|
+
primary_heating_fuel: primary_heating_fuel,
|
314
|
+
sizing_run_dir: sizing_run_dir,
|
315
|
+
dcv_type: dcv_type,
|
316
|
+
ecm_system_name: ecm_system_name,
|
317
|
+
erv_package: erv_package,
|
318
|
+
boiler_eff: boiler_eff,
|
319
|
+
furnace_eff: furnace_eff,
|
320
|
+
shw_eff: shw_eff,
|
321
|
+
daylighting_type: daylighting_type
|
322
|
+
)
|
323
|
+
return model
|
324
|
+
end
|
325
|
+
|
326
|
+
|
327
|
+
def apply_systems_and_efficiencies(model:,
|
328
|
+
primary_heating_fuel:,
|
329
|
+
sizing_run_dir:,
|
330
|
+
dcv_type: 'NECB_Default',
|
331
|
+
ecm_system_name: 'NECB_Default',
|
332
|
+
erv_package: 'NECB_Default',
|
333
|
+
boiler_eff: nil,
|
334
|
+
furnace_eff: nil,
|
335
|
+
shw_eff: nil,
|
336
|
+
daylighting_type: 'NECB_Default')
|
337
|
+
# Create ECM object.
|
338
|
+
ecm = ECMS.new
|
339
|
+
|
340
|
+
# -------- Systems Layout-----------
|
341
|
+
|
342
|
+
# Create Default Systems.
|
200
343
|
apply_systems(model: model, primary_heating_fuel: primary_heating_fuel, sizing_run_dir: sizing_run_dir)
|
344
|
+
|
345
|
+
# Apply new ECM system. Overwrite standard as required.
|
346
|
+
ecm.apply_system_ecm(model: model, ecm_system_name: ecm_system_name, template_standard: self)
|
347
|
+
|
348
|
+
# Apply ERV equipment as required.
|
349
|
+
ecm.apply_erv_ecm(model: model, erv_package: erv_package)
|
350
|
+
|
351
|
+
|
352
|
+
# -------- Performace, Efficiencies, Controls and Sensors ------------
|
353
|
+
#
|
354
|
+
# Set code standard equipment charecteristics.
|
201
355
|
apply_standard_efficiencies(model: model, sizing_run_dir: sizing_run_dir)
|
202
|
-
|
203
|
-
|
356
|
+
# Apply System
|
357
|
+
ecm.apply_system_efficiencies_ecm(model: model, ecm_system_name: ecm_system_name)
|
358
|
+
|
359
|
+
# Apply ECM ERV charecteristics as required. Part 2 of above ECM.
|
360
|
+
ecm.apply_erv_ecm_efficiency(model: model, erv_package: erv_package)
|
361
|
+
# Apply DCV as required
|
362
|
+
model_enable_demand_controlled_ventilation(model, dcv_type)
|
363
|
+
# Apply Boiler Efficiency
|
364
|
+
ecm.modify_boiler_efficiency(model: model, boiler_eff: boiler_eff)
|
365
|
+
# Apply Furnace Efficiency
|
366
|
+
ecm.modify_furnace_efficiency(model: model, furnace_eff: furnace_eff)
|
367
|
+
# Apply SHW Efficiency
|
368
|
+
ecm.modify_shw_efficiency(model: model, shw_eff: shw_eff)
|
369
|
+
# Apply daylight controls.
|
370
|
+
model_add_daylighting_controls(model) if daylighting_type == 'add_daylighting_controls'
|
371
|
+
|
372
|
+
|
373
|
+
# -------Pump sizing required by some vintages----------------
|
374
|
+
# Apply Pump power as required.
|
375
|
+
apply_loop_pump_power(model: model, sizing_run_dir: sizing_run_dir)
|
204
376
|
end
|
205
377
|
|
206
|
-
|
207
|
-
|
208
|
-
|
378
|
+
|
379
|
+
def apply_loads(model:, lights_type: 'NECB_Default', lights_scale: 1.0, validate: true)
|
380
|
+
if validate
|
381
|
+
raise('validation of model failed.') unless validate_initial_model(model)
|
382
|
+
raise('validation of spacetypes failed.') unless validate_and_upate_space_types(model)
|
383
|
+
end
|
209
384
|
#this sets/stores the template version loads that the model uses.
|
210
385
|
model.getBuilding.setStandardsTemplate(self.class.name)
|
211
386
|
set_occ_sensor_spacetypes(model, @space_type_map)
|
212
|
-
model_add_loads(model)
|
387
|
+
model_add_loads(model, lights_type, lights_scale)
|
213
388
|
end
|
214
389
|
|
215
390
|
def apply_weather_data(model:, epw_file:)
|
@@ -223,23 +398,44 @@ class NECB2011 < Standard
|
|
223
398
|
end
|
224
399
|
|
225
400
|
def apply_envelope(model:,
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
401
|
+
ext_wall_cond: nil,
|
402
|
+
ext_floor_cond: nil,
|
403
|
+
ext_roof_cond: nil,
|
404
|
+
ground_wall_cond: nil,
|
405
|
+
ground_floor_cond: nil,
|
406
|
+
ground_roof_cond: nil,
|
407
|
+
door_construction_cond: nil,
|
408
|
+
fixed_window_cond: nil,
|
409
|
+
glass_door_cond: nil,
|
410
|
+
overhead_door_cond: nil,
|
411
|
+
skylight_cond: nil,
|
412
|
+
glass_door_solar_trans: nil,
|
413
|
+
fixed_wind_solar_trans: nil,
|
414
|
+
skylight_solar_trans: nil)
|
236
415
|
raise('validation of model failed.') unless validate_initial_model(model)
|
237
416
|
model_apply_infiltration_standard(model)
|
238
417
|
model.getInsideSurfaceConvectionAlgorithm.setAlgorithm('TARP')
|
239
418
|
model.getOutsideSurfaceConvectionAlgorithm.setAlgorithm('TARP')
|
240
419
|
model_add_constructions(model)
|
241
|
-
apply_standard_construction_properties(model: model,
|
420
|
+
apply_standard_construction_properties(model: model,
|
421
|
+
ext_wall_cond: ext_wall_cond,
|
422
|
+
ext_floor_cond: ext_floor_cond,
|
423
|
+
ext_roof_cond: ext_roof_cond,
|
424
|
+
ground_wall_cond: ground_wall_cond,
|
425
|
+
ground_floor_cond: ground_floor_cond,
|
426
|
+
ground_roof_cond: ground_roof_cond,
|
427
|
+
door_construction_cond: door_construction_cond,
|
428
|
+
fixed_window_cond: fixed_window_cond,
|
429
|
+
glass_door_cond: glass_door_cond,
|
430
|
+
overhead_door_cond: overhead_door_cond,
|
431
|
+
skylight_cond: skylight_cond,
|
432
|
+
glass_door_solar_trans: glass_door_solar_trans,
|
433
|
+
fixed_wind_solar_trans: fixed_wind_solar_trans,
|
434
|
+
skylight_solar_trans: skylight_solar_trans)
|
435
|
+
|
436
|
+
|
242
437
|
model_create_thermal_zones(model, @space_multiplier_map)
|
438
|
+
|
243
439
|
end
|
244
440
|
|
245
441
|
# Thermal zones need to be set to determine conditioned spaces when applying fdwr and srr limits.
|
@@ -254,19 +450,20 @@ class NECB2011 < Standard
|
|
254
450
|
def apply_fdwr_srr_daylighting(model:, fdwr_set: -1.0, srr_set: -1.0)
|
255
451
|
apply_standard_window_to_wall_ratio(model: model, fdwr_set: fdwr_set)
|
256
452
|
apply_standard_skylight_to_roof_ratio(model: model, srr_set: srr_set)
|
257
|
-
model_add_daylighting_controls(model) # to be removed after refactor.
|
453
|
+
# model_add_daylighting_controls(model) # to be removed after refactor.
|
258
454
|
end
|
259
455
|
|
260
|
-
def apply_standard_efficiencies(model:, sizing_run_dir:)
|
456
|
+
def apply_standard_efficiencies(model:, sizing_run_dir:, dcv_type: 'NECB_Default')
|
261
457
|
raise('validation of model failed.') unless validate_initial_model(model)
|
262
458
|
climate_zone = 'NECB HDD Method'
|
263
459
|
raise("sizing run 1 failed! check #{sizing_run_dir}") if model_run_sizing_run(model, "#{sizing_run_dir}/plant_loops") == false
|
264
460
|
# This is needed for NECB2011 as a workaround for sizing the reheat boxes
|
265
|
-
model.getAirTerminalSingleDuctVAVReheats.each {|iobj| air_terminal_single_duct_vav_reheat_set_heating_cap(iobj)}
|
461
|
+
model.getAirTerminalSingleDuctVAVReheats.each { |iobj| air_terminal_single_duct_vav_reheat_set_heating_cap(iobj) }
|
266
462
|
# Apply the prototype HVAC assumptions
|
267
463
|
model_apply_prototype_hvac_assumptions(model, nil, climate_zone)
|
268
464
|
# Apply the HVAC efficiency standard
|
269
465
|
model_apply_hvac_efficiency_standard(model, climate_zone)
|
466
|
+
model_enable_demand_controlled_ventilation(model, dcv_type)
|
270
467
|
end
|
271
468
|
|
272
469
|
def apply_loop_pump_power(model:, sizing_run_dir:)
|
@@ -294,7 +491,7 @@ class NECB2011 < Standard
|
|
294
491
|
#Now iterate though each vintage
|
295
492
|
space_type_vintage_list.each do |template|
|
296
493
|
#Create the standard object and get a list of all the spacetypes available for that vintage.
|
297
|
-
standard_space_type_list = Standard.build(template).get_all_spacetype_names.map {|spacetype| [spacetype[0].to_s + '-' + spacetype[1].to_s]}
|
494
|
+
standard_space_type_list = Standard.build(template).get_all_spacetype_names.map { |spacetype| [spacetype[0].to_s + '-' + spacetype[1].to_s] }
|
298
495
|
# set array to contain unknown spacetypes.
|
299
496
|
unknown_spacetypes = []
|
300
497
|
# iterate though all space types that the model is using
|
@@ -331,7 +528,7 @@ class NECB2011 < Standard
|
|
331
528
|
bt_target_vintage_string = "#{self.class.name}_building_type"
|
332
529
|
space_type_upgrade_map = @standards_data['space_type_upgrade_map']
|
333
530
|
model.getSpaceTypes.sort.each do |st|
|
334
|
-
space_type_map = space_type_upgrade_map.detect {|row| (row[st_model_vintage_string] == st.standardsSpaceType.get.to_s) && (row[bt_model_vintage_string] == st.standardsBuildingType.get.to_s)}
|
531
|
+
space_type_map = space_type_upgrade_map.detect { |row| (row[st_model_vintage_string] == st.standardsSpaceType.get.to_s) && (row[bt_model_vintage_string] == st.standardsBuildingType.get.to_s) }
|
335
532
|
st.setStandardsBuildingType(space_type_map[bt_target_vintage_string].to_s.strip)
|
336
533
|
raise('could not set buildingtype') unless st.setStandardsBuildingType(space_type_map[bt_target_vintage_string].to_s.strip)
|
337
534
|
raise('could not set this') unless st.setStandardsSpaceType(space_type_map[st_target_vintage_string].to_s.strip)
|
@@ -518,4 +715,833 @@ class NECB2011 < Standard
|
|
518
715
|
end
|
519
716
|
|
520
717
|
|
718
|
+
def model_add_daylighting_controls(model)
|
719
|
+
|
720
|
+
##### Ask user's inputs for daylighting controls illuminance setpoint and number of stepped control steps.
|
721
|
+
##### Note that the minimum number of stepped control steps is two steps as per NECB2011.
|
722
|
+
def daylighting_controls_settings(illuminance_setpoint: 500.0,
|
723
|
+
number_of_stepped_control_steps: 2)
|
724
|
+
return illuminance_setpoint, number_of_stepped_control_steps
|
725
|
+
end
|
726
|
+
|
727
|
+
##### Find spaces with exterior fenestration including fixed window, operable window, and skylight.
|
728
|
+
daylight_spaces = []
|
729
|
+
model.getSpaces.sort.each do |space|
|
730
|
+
space.surfaces.sort.each do |surface|
|
731
|
+
surface.subSurfaces.sort.each do |subsurface|
|
732
|
+
if subsurface.outsideBoundaryCondition == "Outdoors" &&
|
733
|
+
(subsurface.subSurfaceType == "FixedWindow" ||
|
734
|
+
subsurface.subSurfaceType == "OperableWindow" ||
|
735
|
+
subsurface.subSurfaceType == "Skylight")
|
736
|
+
daylight_spaces << space
|
737
|
+
end #subsurface.outsideBoundaryCondition == "Outdoors" && (subsurface.subSurfaceType == "FixedWindow" || "OperableWindow")
|
738
|
+
end #surface.subSurfaces.each do |subsurface|
|
739
|
+
end #space.surfaces.each do |surface|
|
740
|
+
end #model.getSpaces.sort.each do |space|
|
741
|
+
|
742
|
+
##### Remove duplicate spaces from the "daylight_spaces" array, as a daylighted space may have various fenestration types.
|
743
|
+
daylight_spaces = daylight_spaces.uniq
|
744
|
+
# puts daylight_spaces
|
745
|
+
|
746
|
+
##### Create hashes for "Primary Sidelighted Areas", "Sidelighting Effective Aperture", "Daylighted Area Under Skylights",
|
747
|
+
##### and "Skylight Effective Aperture" for the whole model.
|
748
|
+
##### Each of these hashes will be used later in this function (i.e. model_add_daylighting_controls)
|
749
|
+
##### to provide a dictionary of daylighted space names and the associated value (i.e. daylighted area or effective aperture).
|
750
|
+
primary_sidelighted_area_hash = {}
|
751
|
+
sidelighting_effective_aperture_hash = {}
|
752
|
+
daylighted_area_under_skylights_hash = {}
|
753
|
+
skylight_effective_aperture_hash = {}
|
754
|
+
|
755
|
+
##### Calculate "Primary Sidelighted Areas" AND "Sidelighting Effective Aperture" as per NECB2011. #TODO: consider removing overlapped sidelighted area
|
756
|
+
daylight_spaces.sort.each do |daylight_space|
|
757
|
+
# puts daylight_space.name.to_s
|
758
|
+
primary_sidelighted_area = 0.0
|
759
|
+
area_weighted_vt_handle = 0.0
|
760
|
+
area_weighted_vt = 0.0
|
761
|
+
window_area_sum = 0.0
|
762
|
+
|
763
|
+
##### Calculate floor area of the daylight_space and get floor vertices of the daylight_space (to be used for the calculation of daylight_space depth)
|
764
|
+
floor_surface = nil
|
765
|
+
floor_area = 0.0
|
766
|
+
floor_vertices = []
|
767
|
+
daylight_space.surfaces.sort.each do |surface|
|
768
|
+
if surface.surfaceType == "Floor"
|
769
|
+
floor_surface = surface
|
770
|
+
floor_area += surface.netArea
|
771
|
+
floor_vertices << surface.vertices
|
772
|
+
end
|
773
|
+
end
|
774
|
+
|
775
|
+
##### Loop through the surfaces of each daylight_space to calculate primary_sidelighted_area and
|
776
|
+
##### area-weighted visible transmittance and window_area_sum which are used to calculate sidelighting_effective_aperture
|
777
|
+
primary_sidelighted_area, area_weighted_vt_handle, window_area_sum =
|
778
|
+
get_parameters_sidelighting(daylight_space: daylight_space,
|
779
|
+
floor_surface: floor_surface,
|
780
|
+
floor_vertices: floor_vertices,
|
781
|
+
floor_area: floor_area,
|
782
|
+
primary_sidelighted_area: primary_sidelighted_area,
|
783
|
+
area_weighted_vt_handle: area_weighted_vt_handle,
|
784
|
+
window_area_sum: window_area_sum)
|
785
|
+
|
786
|
+
primary_sidelighted_area_hash[daylight_space.name.to_s] = primary_sidelighted_area
|
787
|
+
|
788
|
+
##### Calculate area-weighted VT of glazing (this is used to calculate sidelighting effective aperture; see NECB2011: 4.2.2.10.).
|
789
|
+
area_weighted_vt = area_weighted_vt_handle / window_area_sum
|
790
|
+
sidelighting_effective_aperture_hash[daylight_space.name.to_s] = window_area_sum * area_weighted_vt / primary_sidelighted_area
|
791
|
+
|
792
|
+
end #daylight_spaces.each do |daylight_space|
|
793
|
+
|
794
|
+
|
795
|
+
##### Calculate "Daylighted Area Under Skylights" AND "Skylight Effective Aperture"
|
796
|
+
daylight_spaces.sort.each do |daylight_space|
|
797
|
+
# puts daylight_space.name.to_s
|
798
|
+
skylight_area = 0.0
|
799
|
+
skylight_area_weighted_vt_handle = 0.0
|
800
|
+
skylight_area_weighted_vt = 0.0
|
801
|
+
skylight_area_sum = 0.0
|
802
|
+
daylighted_under_skylight_area = 0.0
|
803
|
+
|
804
|
+
##### Loop through the surfaces of each daylight_space to calculate daylighted_area_under_skylights and skylight_effective_aperture for each daylight_space
|
805
|
+
daylighted_under_skylight_area, skylight_area_weighted_vt_handle, skylight_area_sum =
|
806
|
+
get_parameters_skylight(daylight_space: daylight_space,
|
807
|
+
skylight_area_weighted_vt_handle: skylight_area_weighted_vt_handle,
|
808
|
+
skylight_area_sum: skylight_area_sum,
|
809
|
+
daylighted_under_skylight_area: daylighted_under_skylight_area)
|
810
|
+
|
811
|
+
daylighted_area_under_skylights_hash[daylight_space.name.to_s] = daylighted_under_skylight_area
|
812
|
+
|
813
|
+
##### Calculate skylight_effective_aperture as per NECB2011: 4.2.2.7.
|
814
|
+
##### Note that it was assumed that the skylight is flush with the ceiling. Therefore, area-weighted average well factor (WF) was set to 0.9 in the below Equation.
|
815
|
+
skylight_area_weighted_vt = skylight_area_weighted_vt_handle / skylight_area_sum
|
816
|
+
skylight_effective_aperture_hash[daylight_space.name.to_s] = 0.85 * skylight_area_sum * skylight_area_weighted_vt * 0.9 / daylighted_under_skylight_area
|
817
|
+
|
818
|
+
end #daylight_spaces.each do |daylight_space|
|
819
|
+
|
820
|
+
# puts primary_sidelighted_area_hash
|
821
|
+
# puts sidelighting_effective_aperture_hash
|
822
|
+
# puts daylighted_area_under_skylights_hash
|
823
|
+
# puts skylight_effective_aperture_hash
|
824
|
+
|
825
|
+
##### Find office spaces >= 25m2 among daylight_spaces
|
826
|
+
offices_larger_25m2 = []
|
827
|
+
daylight_spaces.sort.each do |daylight_space|
|
828
|
+
|
829
|
+
## The following steps are for in case an office has multiple floors at various heights
|
830
|
+
## 1. Calculate number of floors of each daylight_space
|
831
|
+
## 2. Find the lowest z among all floors of each daylight_space
|
832
|
+
## 3. Find lowest floors of each daylight_space (these floors are at the same level)
|
833
|
+
## 4. Calculate 'daylight_space_area' as sum of area of all the lowest floors of each daylight_space, and gather the vertices of all the lowest floors of each daylight_space
|
834
|
+
|
835
|
+
## 1. Calculate number of floors of daylight_space
|
836
|
+
floor_vertices = []
|
837
|
+
number_floor = 0
|
838
|
+
daylight_space.surfaces.sort.each do |surface|
|
839
|
+
if surface.surfaceType == 'Floor'
|
840
|
+
floor_vertices << surface.vertices
|
841
|
+
number_floor += 1
|
842
|
+
end
|
843
|
+
end
|
844
|
+
|
845
|
+
## 2. Loop through all floors of daylight_space, and find the lowest z among all floors of daylight_space
|
846
|
+
lowest_floor_z = []
|
847
|
+
highest_floor_z = []
|
848
|
+
for i in 0..number_floor - 1
|
849
|
+
if i == 0
|
850
|
+
lowest_floor_z = floor_vertices[i][0].z
|
851
|
+
highest_floor_z = floor_vertices[i][0].z
|
852
|
+
else
|
853
|
+
if lowest_floor_z > floor_vertices[i][0].z
|
854
|
+
lowest_floor_z = floor_vertices[i][0].z
|
855
|
+
else
|
856
|
+
lowest_floor_z = lowest_floor_z
|
857
|
+
end
|
858
|
+
if highest_floor_z < floor_vertices[i][0].z
|
859
|
+
highest_floor_z = floor_vertices[i][0].z
|
860
|
+
else
|
861
|
+
highest_floor_z = highest_floor_z
|
862
|
+
end
|
863
|
+
end
|
864
|
+
end
|
865
|
+
|
866
|
+
## 3 and 4. Loop through all floors of daylight_space, and calculate the sum of area of all the lowest floors of daylight_space,
|
867
|
+
## and gather the vertices of all the lowest floors of daylight_space
|
868
|
+
daylight_space_area = 0
|
869
|
+
lowest_floors_vertices = []
|
870
|
+
floor_vertices = []
|
871
|
+
daylight_space.surfaces.sort.each do |surface|
|
872
|
+
if surface.surfaceType == 'Floor'
|
873
|
+
floor_vertices = surface.vertices
|
874
|
+
if floor_vertices[0].z == lowest_floor_z
|
875
|
+
lowest_floors_vertices << floor_vertices
|
876
|
+
daylight_space_area = daylight_space_area + surface.netArea
|
877
|
+
end
|
878
|
+
end
|
879
|
+
end
|
880
|
+
|
881
|
+
if daylight_space.spaceType.get.standardsSpaceType.get.to_s == "Office - enclosed" && daylight_space_area >= 25.0
|
882
|
+
offices_larger_25m2 << daylight_space.name.to_s
|
883
|
+
end
|
884
|
+
end
|
885
|
+
|
886
|
+
##### find daylight_spaces which do not need daylight sensor controls based on the primary_sidelighted_area as per NECB2011: 4.2.2.8.
|
887
|
+
##### Note: Office spaces >= 25m2 are excluded (i.e. they should have daylighting controls even if their primary_sidelighted_area <= 100m2), as per NECB2011: 4.2.2.2.
|
888
|
+
daylight_spaces_exception = []
|
889
|
+
primary_sidelighted_area_hash.sort.each do |key_daylight_space_name, value_primary_sidelighted_area|
|
890
|
+
if value_primary_sidelighted_area <= 100.0 && [key_daylight_space_name].any? { |word| offices_larger_25m2.include?(word) } == false
|
891
|
+
daylight_spaces_exception << key_daylight_space_name
|
892
|
+
end
|
893
|
+
end
|
894
|
+
|
895
|
+
##### find daylight_spaces which do not need daylight sensor controls based on the sidelighting_effective_aperture as per NECB2011: 4.2.2.8.
|
896
|
+
##### Note: Office spaces >= 25m2 are excluded (i.e. they should have daylighting controls even if their sidelighting_effective_aperture <= 10%), as per NECB2011: 4.2.2.2.
|
897
|
+
sidelighting_effective_aperture_hash.sort.each do |key_daylight_space_name, value_sidelighting_effective_aperture|
|
898
|
+
if value_sidelighting_effective_aperture <= 0.1 && [key_daylight_space_name].any? { |word| offices_larger_25m2.include?(word) } == false
|
899
|
+
daylight_spaces_exception << key_daylight_space_name
|
900
|
+
end
|
901
|
+
end
|
902
|
+
|
903
|
+
##### find daylight_spaces which do not need daylight sensor controls based on the daylighted_area_under_skylights as per NECB2011: 4.2.2.4.
|
904
|
+
##### Note: Office spaces >= 25m2 are excluded (i.e. they should have daylighting controls even if their daylighted_area_under_skylights <= 400m2), as per NECB2011: 4.2.2.2.
|
905
|
+
daylighted_area_under_skylights_hash.sort.each do |key_daylight_space_name, value_daylighted_area_under_skylights|
|
906
|
+
if value_daylighted_area_under_skylights <= 400.0 && [key_daylight_space_name].any? { |word| offices_larger_25m2.include?(word) } == false
|
907
|
+
daylight_spaces_exception << key_daylight_space_name
|
908
|
+
end
|
909
|
+
end
|
910
|
+
|
911
|
+
##### find daylight_spaces which do not need daylight sensor controls based on the skylight_effective_aperture criterion as per NECB2011: 4.2.2.4.
|
912
|
+
##### Note: Office spaces >= 25m2 are excluded (i.e. they should have daylighting controls even if their skylight_effective_aperture <= 0.6%), as per NECB2011: 4.2.2.2.
|
913
|
+
skylight_effective_aperture_hash.sort.each do |key_daylight_space_name, value_skylight_effective_aperture|
|
914
|
+
if value_skylight_effective_aperture <= 0.006 && [key_daylight_space_name].any? { |word| offices_larger_25m2.include?(word) } == false
|
915
|
+
daylight_spaces_exception << key_daylight_space_name
|
916
|
+
end
|
917
|
+
end
|
918
|
+
# puts daylight_spaces_exception
|
919
|
+
|
920
|
+
##### Loop through the daylight_spaces and exclude the daylight_spaces that do not meet the criteria (see above) as per NECB2011: 4.2.2.4. and 4.2.2.8.
|
921
|
+
daylight_spaces_exception.sort.each do |daylight_space_exception|
|
922
|
+
daylight_spaces.sort.each do |daylight_space|
|
923
|
+
if daylight_space.name.to_s == daylight_space_exception
|
924
|
+
daylight_spaces.delete(daylight_space)
|
925
|
+
end
|
926
|
+
end
|
927
|
+
end
|
928
|
+
# puts daylight_spaces
|
929
|
+
|
930
|
+
##### Create one daylighting sensor and put it at the center of each daylight_space if the space area < 250m2;
|
931
|
+
##### otherwise, create two daylight sensors, divide the space into two parts and put each of the daylight sensors at the center of each part of the space.
|
932
|
+
daylight_spaces.sort.each do |daylight_space|
|
933
|
+
# puts daylight_space.name.to_s
|
934
|
+
##### 1. Calculate number of floors of each daylight_space
|
935
|
+
##### 2. Find the lowest z among all floors of each daylight_space
|
936
|
+
##### 3. Find lowest floors of each daylight_space (these floors are at the same level)
|
937
|
+
##### 4. Calculate 'daylight_space_area' as sum of area of all the lowest floors of each daylight_space, and gather the vertices of all the lowest floors of each daylight_space
|
938
|
+
##### 5. Find min and max of x and y among vertices of all the lowest floors of each daylight_space
|
939
|
+
|
940
|
+
##### Calculate number of floors of daylight_space
|
941
|
+
floor_vertices = []
|
942
|
+
number_floor = 0
|
943
|
+
daylight_space.surfaces.sort.each do |surface|
|
944
|
+
if surface.surfaceType == 'Floor'
|
945
|
+
floor_vertices << surface.vertices
|
946
|
+
number_floor += 1
|
947
|
+
end
|
948
|
+
end
|
949
|
+
|
950
|
+
##### Loop through all floors of daylight_space, and find the lowest z among all floors of daylight_space
|
951
|
+
lowest_floor_z = []
|
952
|
+
highest_floor_z = []
|
953
|
+
for i in 0..number_floor - 1
|
954
|
+
if i == 0
|
955
|
+
lowest_floor_z = floor_vertices[i][0].z
|
956
|
+
highest_floor_z = floor_vertices[i][0].z
|
957
|
+
else
|
958
|
+
if lowest_floor_z > floor_vertices[i][0].z
|
959
|
+
lowest_floor_z = floor_vertices[i][0].z
|
960
|
+
else
|
961
|
+
lowest_floor_z = lowest_floor_z
|
962
|
+
end
|
963
|
+
if highest_floor_z < floor_vertices[i][0].z
|
964
|
+
highest_floor_z = floor_vertices[i][0].z
|
965
|
+
else
|
966
|
+
highest_floor_z = highest_floor_z
|
967
|
+
end
|
968
|
+
end
|
969
|
+
end
|
970
|
+
# puts lowest_floor_z
|
971
|
+
|
972
|
+
##### Loop through all floors of daylight_space, and calculate the sum of area of all the lowest floors of daylight_space,
|
973
|
+
##### and gather the vertices of all the lowest floors of daylight_space
|
974
|
+
daylight_space_area = 0
|
975
|
+
lowest_floors_vertices = []
|
976
|
+
floor_vertices = []
|
977
|
+
daylight_space.surfaces.sort.each do |surface|
|
978
|
+
if surface.surfaceType == 'Floor'
|
979
|
+
floor_vertices = surface.vertices
|
980
|
+
if floor_vertices[0].z == lowest_floor_z
|
981
|
+
lowest_floors_vertices << floor_vertices
|
982
|
+
daylight_space_area = daylight_space_area + surface.netArea
|
983
|
+
end
|
984
|
+
end
|
985
|
+
end
|
986
|
+
# puts daylight_space.name.to_s
|
987
|
+
# puts number_floor
|
988
|
+
# puts lowest_floors_vertices
|
989
|
+
# puts daylight_space_area
|
990
|
+
|
991
|
+
##### Loop through all lowest floors of daylight_space and find the min and max of x and y among their vertices
|
992
|
+
xmin = lowest_floors_vertices[0][0].x
|
993
|
+
ymin = lowest_floors_vertices[0][0].y
|
994
|
+
xmax = lowest_floors_vertices[0][0].x
|
995
|
+
ymax = lowest_floors_vertices[0][0].y
|
996
|
+
zmin = lowest_floor_z
|
997
|
+
for i in 0..lowest_floors_vertices.count - 1 #this loops through each of the lowers floors of daylight_space
|
998
|
+
for j in 0..lowest_floors_vertices[i].count - 1 #this loops through each of vertices of each of the lowers floors of daylight_space
|
999
|
+
|
1000
|
+
if xmin > lowest_floors_vertices[i][j].x
|
1001
|
+
xmin = lowest_floors_vertices[i][j].x
|
1002
|
+
end
|
1003
|
+
if ymin > lowest_floors_vertices[i][j].y
|
1004
|
+
ymin = lowest_floors_vertices[i][j].y
|
1005
|
+
end
|
1006
|
+
if xmax < lowest_floors_vertices[i][j].x
|
1007
|
+
xmax = lowest_floors_vertices[i][j].x
|
1008
|
+
end
|
1009
|
+
if ymax < lowest_floors_vertices[i][j].y
|
1010
|
+
ymax = lowest_floors_vertices[i][j].y
|
1011
|
+
end
|
1012
|
+
end
|
1013
|
+
end
|
1014
|
+
# puts daylight_space.name.to_s
|
1015
|
+
# puts xmin
|
1016
|
+
# puts xmax
|
1017
|
+
# puts ymin
|
1018
|
+
# puts ymax
|
1019
|
+
|
1020
|
+
##### Get the thermal zone of daylight_space (this is used later to assign daylighting sensor)
|
1021
|
+
zone = daylight_space.thermalZone
|
1022
|
+
if !zone.empty?
|
1023
|
+
zone = daylight_space.thermalZone.get
|
1024
|
+
##### Get the floor of the daylight_space
|
1025
|
+
floors = []
|
1026
|
+
daylight_space.surfaces.sort.each do |surface|
|
1027
|
+
if surface.surfaceType == "Floor"
|
1028
|
+
floors << surface
|
1029
|
+
end
|
1030
|
+
end
|
1031
|
+
|
1032
|
+
##### Get user's input for daylighting controls illuminance setpoint and number of stepped control steps
|
1033
|
+
illuminance_setpoint, number_of_stepped_control_steps = daylighting_controls_settings(illuminance_setpoint: 500.0, number_of_stepped_control_steps: 2)
|
1034
|
+
|
1035
|
+
##### Create daylighting sensor control
|
1036
|
+
##### NOTE: NECB2011 has some requirements on the number of sensors in spaces based on the area of the spaces.
|
1037
|
+
##### However, EnergyPlus/OpenStudio allows to put maximum two built-in sensors in each thermal zone rather than in each space.
|
1038
|
+
##### Since a thermal zone may include several spaces which are not next to each other on the same floor, or
|
1039
|
+
##### a thermal zone may include spaces on different floors, a simplified method has been used to create a daylighting sensor.
|
1040
|
+
##### So, in each thermal zone, only one daylighting sensor has been created even if the area of that thermal zone requires more than one daylighting sensor.
|
1041
|
+
##### Also, it has been assumed that a thermal zone includes spaces which are next to each other and are on the same floor.
|
1042
|
+
##### Furthermore, the one daylighting sensor in each thermal zone (where the thermal zone needs daylighting sensor),
|
1043
|
+
##### the sensor has been put at the intersection of the minimum and maximum x and y of the lowest floor of that thermal zones.
|
1044
|
+
sensor = OpenStudio::Model::DaylightingControl.new(daylight_space.model)
|
1045
|
+
sensor.setName("#{daylight_space.name.to_s} daylighting control")
|
1046
|
+
sensor.setSpace(daylight_space)
|
1047
|
+
sensor.setIlluminanceSetpoint(illuminance_setpoint)
|
1048
|
+
sensor.setLightingControlType('Stepped')
|
1049
|
+
sensor.setNumberofSteppedControlSteps(number_of_stepped_control_steps)
|
1050
|
+
x_pos = (xmin + xmax) / 2.0
|
1051
|
+
y_pos = (ymin + ymax) / 2.0
|
1052
|
+
z_pos = zmin + 0.8 #put it 0.8 meter above the floor
|
1053
|
+
sensor_vertex = OpenStudio::Point3d.new(x_pos, y_pos, z_pos)
|
1054
|
+
sensor.setPosition(sensor_vertex)
|
1055
|
+
zone.setPrimaryDaylightingControl(sensor)
|
1056
|
+
zone.setFractionofZoneControlledbyPrimaryDaylightingControl(1.0)
|
1057
|
+
|
1058
|
+
end #if !zone.empty?
|
1059
|
+
end #daylight_spaces.each do |daylight_space|
|
1060
|
+
end
|
1061
|
+
|
1062
|
+
#def model_add_daylighting_controls(model)
|
1063
|
+
|
1064
|
+
|
1065
|
+
def model_enable_demand_controlled_ventilation(model, dcv_type = 'No_DCV') # Note: Values for dcv_type are: 'Occupancy_based_DCV', 'CO2_based_DCV', 'No_DCV', 'NECB_Default'
|
1066
|
+
if dcv_type != 'NECB_Default'
|
1067
|
+
if dcv_type == 'Occupancy_based_DCV' || dcv_type == 'CO2_based_DCV'
|
1068
|
+
#TODO: IMPORTANT: (upon other BTAP tasks) Set a value for the "Outdoor Air Flow per Person" field of the "OS:DesignSpecification:OutdoorAir" object
|
1069
|
+
# Note: The "Outdoor Air Flow per Person" field is required for occupancy-based DCV.
|
1070
|
+
# Note: The "Outdoor Air Flow per Person" values should be based on ASHRAE 62.1: Article 6.2.2.1.
|
1071
|
+
# Note: The "Outdoor Air Flow per Person" should be entered for "ventilation_per_person" in "lib/openstudio-standards/standards/necb/NECB2011/data/space_types.json"
|
1072
|
+
|
1073
|
+
##### Define ScheduleTypeLimits for Any_Number_ppm
|
1074
|
+
##### TODO: (upon other BTAP tasks) This function can be added to btap/schedules.rb > module StandardScheduleTypeLimits
|
1075
|
+
def self.get_any_number_ppm(model)
|
1076
|
+
name = 'Any_Number_ppm'
|
1077
|
+
any_number_ppm_schedule_type_limits = model.getScheduleTypeLimitsByName(name)
|
1078
|
+
if any_number_ppm_schedule_type_limits.empty?
|
1079
|
+
any_number_ppm_schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model)
|
1080
|
+
any_number_ppm_schedule_type_limits.setName(name)
|
1081
|
+
any_number_ppm_schedule_type_limits.setNumericType('CONTINUOUS')
|
1082
|
+
any_number_ppm_schedule_type_limits.setUnitType('Dimensionless')
|
1083
|
+
any_number_ppm_schedule_type_limits.setLowerLimitValue(400.0)
|
1084
|
+
any_number_ppm_schedule_type_limits.setUpperLimitValue(1000.0)
|
1085
|
+
return any_number_ppm_schedule_type_limits
|
1086
|
+
else
|
1087
|
+
return any_number_ppm_schedule_type_limits.get
|
1088
|
+
end
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
##### Define indoor CO2 availability schedule (required for CO2-based DCV)
|
1092
|
+
##### Reference: see page B.13 of PNNL (2017), "Impacts of Commercial Building Controls on Energy Savings and Peak Load Reduction", available a: https://www.energy.gov/eere/buildings/downloads/impacts-commercial-building-controls-energy-savings-and-peak-load-reduction
|
1093
|
+
##### Note: the defined schedule here is redundant as the schedule says it is always on AND
|
1094
|
+
##### the "ZoneControl:ContaminantController" object says that "If this field is left blank, the schedule has a value of 1 for all time periods".
|
1095
|
+
indoor_co2_availability_schedule = OpenStudio::Model::ScheduleCompact.new(model)
|
1096
|
+
indoor_co2_availability_schedule.setName('indoor_co2_availability_schedule')
|
1097
|
+
indoor_co2_availability_schedule.setScheduleTypeLimits(BTAP::Resources::Schedules::StandardScheduleTypeLimits::get_fraction(model))
|
1098
|
+
indoor_co2_availability_schedule.to_ScheduleCompact.get
|
1099
|
+
# indoor_co2_availability_schedule.setString(1,"indoor_co2_availability_schedule")
|
1100
|
+
indoor_co2_availability_schedule.setString(3, "Through: 12/31")
|
1101
|
+
indoor_co2_availability_schedule.setString(4, "For: Weekdays SummerDesignDay")
|
1102
|
+
indoor_co2_availability_schedule.setString(5, "Until: 07:00")
|
1103
|
+
indoor_co2_availability_schedule.setString(6, "0.0")
|
1104
|
+
indoor_co2_availability_schedule.setString(7, "Until: 22:00")
|
1105
|
+
indoor_co2_availability_schedule.setString(8, "1.0")
|
1106
|
+
indoor_co2_availability_schedule.setString(9, "Until: 24:00")
|
1107
|
+
indoor_co2_availability_schedule.setString(10, "0.0")
|
1108
|
+
indoor_co2_availability_schedule.setString(11, "For: Saturday WinterDesignDay")
|
1109
|
+
indoor_co2_availability_schedule.setString(12, "Until: 07:00")
|
1110
|
+
indoor_co2_availability_schedule.setString(13, "0.0")
|
1111
|
+
indoor_co2_availability_schedule.setString(14, "Until: 18:00")
|
1112
|
+
indoor_co2_availability_schedule.setString(15, "1.0")
|
1113
|
+
indoor_co2_availability_schedule.setString(16, "Until: 24:00")
|
1114
|
+
indoor_co2_availability_schedule.setString(17, "0.0")
|
1115
|
+
indoor_co2_availability_schedule.setString(18, "For: AllOtherDays")
|
1116
|
+
indoor_co2_availability_schedule.setString(19, "Until: 24:00")
|
1117
|
+
indoor_co2_availability_schedule.setString(20, "0.0")
|
1118
|
+
|
1119
|
+
##### Define indoor CO2 setpoint schedule (required for CO2-based DCV)
|
1120
|
+
##### Reference: see page B.13 of PNNL (2017), "Impacts of Commercial Building Controls on Energy Savings and Peak Load Reduction", available a: https://www.energy.gov/eere/buildings/downloads/impacts-commercial-building-controls-energy-savings-and-peak-load-reduction
|
1121
|
+
indoor_co2_setpoint_schedule = OpenStudio::Model::ScheduleCompact.new(model)
|
1122
|
+
indoor_co2_setpoint_schedule.setName('indoor_co2_setpoint_schedule')
|
1123
|
+
indoor_co2_setpoint_schedule.setScheduleTypeLimits(get_any_number_ppm(model))
|
1124
|
+
indoor_co2_setpoint_schedule.to_ScheduleCompact.get
|
1125
|
+
indoor_co2_setpoint_schedule.setString(3, "Through: 12/31")
|
1126
|
+
indoor_co2_setpoint_schedule.setString(4, "For: AllDays")
|
1127
|
+
indoor_co2_setpoint_schedule.setString(5, "Until: 24:00")
|
1128
|
+
indoor_co2_setpoint_schedule.setString(6, "1000.0")
|
1129
|
+
# indoor_co2_setpoint_schedule.setToConstantValue(1000.0) #1000 ppm
|
1130
|
+
|
1131
|
+
|
1132
|
+
##### Define outdoor CO2 schedule (required for CO2-based DCV
|
1133
|
+
##### Reference: see page B.13 of PNNL (2017), "Impacts of Commercial Building Controls on Energy Savings and Peak Load Reduction", available a: https://www.energy.gov/eere/buildings/downloads/impacts-commercial-building-controls-energy-savings-and-peak-load-reduction
|
1134
|
+
outdoor_co2_schedule = OpenStudio::Model::ScheduleCompact.new(model)
|
1135
|
+
outdoor_co2_schedule.setName('outdoor_co2_schedule')
|
1136
|
+
outdoor_co2_schedule.setScheduleTypeLimits(get_any_number_ppm(model))
|
1137
|
+
outdoor_co2_schedule.to_ScheduleCompact.get
|
1138
|
+
outdoor_co2_schedule.setString(3, "Through: 12/31")
|
1139
|
+
outdoor_co2_schedule.setString(4, "For: AllDays")
|
1140
|
+
outdoor_co2_schedule.setString(5, "Until: 24:00")
|
1141
|
+
outdoor_co2_schedule.setString(6, "400.0")
|
1142
|
+
# outdoor_co2_schedule.setToConstantValue(400.0) #400 ppm
|
1143
|
+
|
1144
|
+
##### Define ZoneAirContaminantBalance (required for CO2-based DCV)
|
1145
|
+
zone_air_contaminant_balance = model.getZoneAirContaminantBalance()
|
1146
|
+
zone_air_contaminant_balance.setCarbonDioxideConcentration(true)
|
1147
|
+
zone_air_contaminant_balance.setOutdoorCarbonDioxideSchedule(outdoor_co2_schedule)
|
1148
|
+
|
1149
|
+
##### Set CO2 controller in each space (required for CO2-based DCV)
|
1150
|
+
model.getSpaces.sort.each do |space|
|
1151
|
+
# puts space.name.to_s
|
1152
|
+
zone = space.thermalZone
|
1153
|
+
if !zone.empty?
|
1154
|
+
zone = space.thermalZone.get
|
1155
|
+
end
|
1156
|
+
zone_control_co2 = OpenStudio::Model::ZoneControlContaminantController.new(zone.model)
|
1157
|
+
zone_control_co2.setName("#{space.name.to_s} Zone Control Contaminant Controller")
|
1158
|
+
zone_control_co2.setCarbonDioxideControlAvailabilitySchedule(indoor_co2_availability_schedule)
|
1159
|
+
zone_control_co2.setCarbonDioxideSetpointSchedule(indoor_co2_setpoint_schedule)
|
1160
|
+
zone.setZoneControlContaminantController(zone_control_co2)
|
1161
|
+
end
|
1162
|
+
|
1163
|
+
end #if dcv_type == "Occupancy_based_DCV" || dcv_type == "CO2_based_DCV"
|
1164
|
+
|
1165
|
+
##### Loop through AirLoopHVACs
|
1166
|
+
model.getAirLoopHVACs.sort.each do |air_loop|
|
1167
|
+
##### Loop through AirLoopHVAC's supply nodes to:
|
1168
|
+
##### (1) Find its AirLoopHVAC:OutdoorAirSystem using the supply node;
|
1169
|
+
##### (2) Find Controller:OutdoorAir using AirLoopHVAC:OutdoorAirSystem;
|
1170
|
+
##### (3) Get "Controller Mechanical Ventilation" from Controller:OutdoorAir.
|
1171
|
+
air_loop.supplyComponents.sort.each do |supply_component|
|
1172
|
+
##### Find AirLoopHVAC:OutdoorAirSystem of AirLoopHVAC using the supply node.
|
1173
|
+
hvac_component = supply_component.to_AirLoopHVACOutdoorAirSystem
|
1174
|
+
|
1175
|
+
if !hvac_component.empty?
|
1176
|
+
##### Find Controller:OutdoorAir using AirLoopHVAC:OutdoorAirSystem.
|
1177
|
+
hvac_component = hvac_component.get
|
1178
|
+
controller_oa = hvac_component.getControllerOutdoorAir
|
1179
|
+
|
1180
|
+
##### Get "Controller Mechanical Ventilation" from Controller:OutdoorAir.
|
1181
|
+
controller_mv = controller_oa.controllerMechanicalVentilation
|
1182
|
+
|
1183
|
+
##### Set "Demand Controlled Ventilation" to "Yes" or "No" in Controller:MechanicalVentilation depending on dcv_type.
|
1184
|
+
if (dcv_type == 'CO2_based_DCV') || (dcv_type == 'Occupancy_based_DCV') #Occupancy
|
1185
|
+
controller_mv.setDemandControlledVentilation(true)
|
1186
|
+
##### Set the "System Outdoor Air Method" field based on dcv_type in the Controller:MechanicalVentilation object
|
1187
|
+
if dcv_type == 'CO2_based_DCV'
|
1188
|
+
controller_mv.setSystemOutdoorAirMethod('IndoorAirQualityProcedure')
|
1189
|
+
else #dcv_type == 'Occupancy_based_DCV'
|
1190
|
+
controller_mv.setSystemOutdoorAirMethod('ZoneSum')
|
1191
|
+
end
|
1192
|
+
elsif dcv_type == 'No_DCV'
|
1193
|
+
controller_mv.setDemandControlledVentilation(false)
|
1194
|
+
end
|
1195
|
+
# puts controller_mv
|
1196
|
+
|
1197
|
+
end #if !hvac_component.empty?
|
1198
|
+
|
1199
|
+
end #air_loop.supplyComponents.each do |supply_component|
|
1200
|
+
end #model.getAirLoopHVACs.each do |air_loop|
|
1201
|
+
end #if dcv_type != 'NECB_Default'
|
1202
|
+
end
|
1203
|
+
|
1204
|
+
#def model_enable_demand_controlled_ventilation
|
1205
|
+
|
1206
|
+
|
1207
|
+
def set_lighting_per_area_led_lighting(space_type:, definition:, lighting_per_area_led_lighting:, lights_scale:)
|
1208
|
+
# puts "#{space_type.name.to_s} - 'space_height' - #{space_height.to_s}"
|
1209
|
+
occ_sens_lpd_frac = 1.0
|
1210
|
+
# NECB2011 space types that require a reduction in the LPD to account for
|
1211
|
+
# the requirement of an occupancy sensor (8.4.4.6(3) and 4.2.2.2(2))
|
1212
|
+
reduce_lpd_spaces = ['Classroom/lecture/training', 'Conf./meet./multi-purpose', 'Lounge/recreation',
|
1213
|
+
'Conf./meet./multi-purpose', 'Washroom-sch-A', 'Washroom-sch-B', 'Washroom-sch-C', 'Washroom-sch-D',
|
1214
|
+
'Washroom-sch-E', 'Washroom-sch-F', 'Washroom-sch-G', 'Washroom-sch-H', 'Washroom-sch-I',
|
1215
|
+
'Dress./fitt. - performance arts', 'Locker room', 'Locker room-sch-A', 'Locker room-sch-B',
|
1216
|
+
'Locker room-sch-C', 'Locker room-sch-D', 'Locker room-sch-E', 'Locker room-sch-F', 'Locker room-sch-G',
|
1217
|
+
'Locker room-sch-H', 'Locker room-sch-I', 'Retail - dressing/fitting']
|
1218
|
+
if reduce_lpd_spaces.include?(space_type.standardsSpaceType.get)
|
1219
|
+
# Note that "Storage area", "Storage area - refrigerated", "Hospital - medical supply" and "Office - enclosed"
|
1220
|
+
# LPD should only be reduced if their space areas are less than specific area values.
|
1221
|
+
# This is checked in a space loop after this function in the calling routine.
|
1222
|
+
occ_sens_lpd_frac = 0.9
|
1223
|
+
end
|
1224
|
+
|
1225
|
+
# ##### Since Atrium's LPD for LED lighting depends on atrium's height, the height of the atrium (if applicable) should be found.
|
1226
|
+
standards_space_type = space_type.standardsSpaceType.is_initialized ? space_type.standardsSpaceType.get : nil
|
1227
|
+
if standards_space_type.include? 'Atrium' #TODO: Note that since none of the archetypes has Atrium, this was tested for 'Dining'. #Atrium
|
1228
|
+
puts "#{standards_space_type} - has atrium" #space_type.name.to_s
|
1229
|
+
# Get the max height for the spacetype.
|
1230
|
+
max_space_height_for_spacetype = get_max_space_height_for_space_type(space_type: space_type)
|
1231
|
+
if max_space_height_for_spacetype < 12.0 #TODO: Note that since none of the archetypes has Atrium, this was tested for 'Dining' with the threshold of 5.0 m for space_height.
|
1232
|
+
# TODO: Regarding the below equations, identify which version of ASHRAE 90.1 was used in NECB2015.
|
1233
|
+
atrium_lpd_eq_smaller_12_intercept = 0
|
1234
|
+
atrium_lpd_eq_smaller_12_slope = 1.06
|
1235
|
+
atrium_lpd_eq_larger_12_intercept = 4.3
|
1236
|
+
atrium_lpd_eq_larger_12_slope = 1.06
|
1237
|
+
lighting_per_area_led_lighting_atrium = (atrium_lpd_eq_smaller_12_intercept + atrium_lpd_eq_smaller_12_slope * 12.0) * 0.092903 # W/ft2 TODO: Note that for NECB2011, a constant LPD is used for atrium based on NECB2015's equations. NECB2011's threshold for height is 13.0 m.
|
1238
|
+
elsif max_space_height_for_spacetype >= 12.0 && max_space_height_for_spacetype < 13.0
|
1239
|
+
lighting_per_area_led_lighting_atrium = (atrium_lpd_eq_larger_12_intercept + atrium_lpd_eq_larger_12_slope * 12.5) * 0.092903 # W/ft2
|
1240
|
+
else #i.e. space_height >= 13.0
|
1241
|
+
lighting_per_area_led_lighting_atrium = (atrium_lpd_eq_larger_12_intercept + atrium_lpd_eq_larger_12_slope * 13.0) * 0.092903 # W/ft2
|
1242
|
+
end
|
1243
|
+
puts "#{standards_space_type} - has lighting_per_area_led_lighting_atrium - #{lighting_per_area_led_lighting_atrium}"
|
1244
|
+
lighting_per_area_led_lighting = lighting_per_area_led_lighting_atrium
|
1245
|
+
end
|
1246
|
+
lighting_per_area_led_lighting = lighting_per_area_led_lighting * lights_scale
|
1247
|
+
definition.setWattsperSpaceFloorArea(OpenStudio.convert(lighting_per_area_led_lighting.to_f * occ_sens_lpd_frac, 'W/ft^2', 'W/m^2').get)
|
1248
|
+
|
1249
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.SpaceType', "#{space_type.name} set LPD to #{lighting_per_area_led_lighting} W/ft^2.")
|
1250
|
+
end
|
1251
|
+
|
1252
|
+
# Adds the loads and associated schedules for each space type
|
1253
|
+
# as defined in the OpenStudio_Standards_space_types.json file.
|
1254
|
+
# This includes lights, plug loads, occupants, ventilation rate requirements,
|
1255
|
+
# infiltration, gas equipment (for kitchens, etc.) and typical schedules for each.
|
1256
|
+
# Some loads are governed by the standard, others are typical values
|
1257
|
+
# pulled from sources such as the DOE Reference and DOE Prototype Buildings.
|
1258
|
+
#
|
1259
|
+
# @return [Bool] returns true if successful, false if not
|
1260
|
+
def model_add_loads(model, lights_type, lights_scale)
|
1261
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Started applying space types (loads)')
|
1262
|
+
|
1263
|
+
# Loop through all the space types currently in the model,
|
1264
|
+
# which are placeholders, and give them appropriate loads and schedules
|
1265
|
+
model.getSpaceTypes.sort.each do |space_type|
|
1266
|
+
# Rendering color
|
1267
|
+
space_type_apply_rendering_color(space_type)
|
1268
|
+
|
1269
|
+
# Loads
|
1270
|
+
space_type_apply_internal_loads(space_type: space_type, lights_type: lights_type, lights_scale: lights_scale)
|
1271
|
+
|
1272
|
+
# Schedules
|
1273
|
+
space_type_apply_internal_load_schedules(space_type, true, true, true, true, true, true, true)
|
1274
|
+
end
|
1275
|
+
|
1276
|
+
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', 'Finished applying space types (loads)')
|
1277
|
+
|
1278
|
+
return true
|
1279
|
+
end
|
1280
|
+
|
1281
|
+
##### This method is needed to the space_type_apply_internal_loads space needed for the calculation of atriums' LPD when LED lighting is used in atriums. ***END***
|
1282
|
+
def get_max_space_height_for_space_type(space_type:)
|
1283
|
+
# initialize return value to zero.
|
1284
|
+
max_space_height_for_space_type = 0.0
|
1285
|
+
# Interate through all spaces in model.. not just ones that have space type defined.. Is this right sara?
|
1286
|
+
space_type.spaces.sort.each do |space|
|
1287
|
+
# Get only the wall type surfaces and iterate throught them.
|
1288
|
+
space.surfaces.sort.select(&:surfaceType == 'Wall').each do |wall_surface|
|
1289
|
+
# Find the vertex with the max z value.
|
1290
|
+
vertex_with_max_height = wall_surface.vertices.max_by(&:z)
|
1291
|
+
# replace max if this surface has something bigger.
|
1292
|
+
max_space_height_for_space_type = vertex_with_max_height.z if vertex_with_max_height.z > max_space_height_for_space_type
|
1293
|
+
end
|
1294
|
+
end
|
1295
|
+
return max_space_height_for_space_type
|
1296
|
+
end
|
1297
|
+
|
1298
|
+
##### The below method is for the 'model_add_daylighting_controls' method
|
1299
|
+
def get_parameters_sidelighting(daylight_space:, floor_surface:, floor_vertices:, floor_area:, primary_sidelighted_area:, area_weighted_vt_handle:, window_area_sum:)
|
1300
|
+
|
1301
|
+
daylight_space.surfaces.sort.each do |surface|
|
1302
|
+
|
1303
|
+
##### Get the vertices of each exterior wall of the daylight_space on the floor
|
1304
|
+
##### (these vertices will be used to calculate daylight_space depth in relation to the exterior wall, and
|
1305
|
+
##### the distance of the window to vertical walls on each side of the window)
|
1306
|
+
if surface.outsideBoundaryCondition == "Outdoors" && surface.surfaceType == "Wall"
|
1307
|
+
wall_vertices_x_on_floor = []
|
1308
|
+
wall_vertices_y_on_floor = []
|
1309
|
+
surface_z_min = [surface.vertices[0].z, surface.vertices[1].z, surface.vertices[2].z, surface.vertices[3].z].min
|
1310
|
+
surface.vertices.each do |vertex|
|
1311
|
+
# puts vertex.z
|
1312
|
+
if vertex.z == surface_z_min && surface_z_min == floor_vertices[0][0].z
|
1313
|
+
wall_vertices_x_on_floor << vertex.x
|
1314
|
+
wall_vertices_y_on_floor << vertex.y
|
1315
|
+
end
|
1316
|
+
end
|
1317
|
+
end
|
1318
|
+
|
1319
|
+
if surface.outsideBoundaryCondition == "Outdoors" && surface.surfaceType == "Wall" && surface_z_min == floor_vertices[0][0].z
|
1320
|
+
|
1321
|
+
##### Calculate the daylight_space depth in relation to the considered exterior wall.
|
1322
|
+
##### To calculate daylight_space depth, first get the floor vertices which are on the opposite side of the considered exterior wall.
|
1323
|
+
floor_vertices_x_wall_opposite = []
|
1324
|
+
floor_vertices_y_wall_opposite = []
|
1325
|
+
floor_vertices[0].each do |floor_vertex|
|
1326
|
+
if (floor_vertex.x != wall_vertices_x_on_floor[0] && floor_vertex.y != wall_vertices_y_on_floor[0]) || (floor_vertex.x != wall_vertices_x_on_floor[1] && floor_vertex.y != wall_vertices_y_on_floor[1])
|
1327
|
+
floor_vertices_x_wall_opposite << floor_vertex.x
|
1328
|
+
floor_vertices_y_wall_opposite << floor_vertex.y
|
1329
|
+
end
|
1330
|
+
end
|
1331
|
+
|
1332
|
+
##### To calculate daylight_space depth, second calculate floor length on both sides: (1) exterior wall side, (2) on the opposite side of the exterior wall
|
1333
|
+
floor_width_wall_side = Math.sqrt((wall_vertices_x_on_floor[0] - wall_vertices_x_on_floor[1]) ** 2 + (wall_vertices_y_on_floor[0] - wall_vertices_y_on_floor[1]) ** 2)
|
1334
|
+
floor_width_wall_opposite = Math.sqrt((floor_vertices_x_wall_opposite[0] - floor_vertices_x_wall_opposite[1]) ** 2 + (floor_vertices_y_wall_opposite[0] - floor_vertices_y_wall_opposite[1]) ** 2)
|
1335
|
+
|
1336
|
+
##### Now, daylight_space depth can be calculated using the floor area and two lengths of the floor (note that these two lengths are in parallel to each other).
|
1337
|
+
daylight_space_depth = 2 * floor_area / (floor_width_wall_side + floor_width_wall_opposite)
|
1338
|
+
|
1339
|
+
##### Loop through the windows (including fixed and operable ones) to get window specification (width, height, area, visible transmittance (VT)), and area-weighted VT
|
1340
|
+
surface.subSurfaces.sort.each do |subsurface|
|
1341
|
+
# puts subsurface.name.to_s
|
1342
|
+
if subsurface.subSurfaceType == "FixedWindow" || subsurface.subSurfaceType == "OperableWindow"
|
1343
|
+
window_vt = subsurface.visibleTransmittance
|
1344
|
+
window_vt = window_vt.get
|
1345
|
+
window_area = subsurface.netArea
|
1346
|
+
window_area_sum += window_area
|
1347
|
+
area_weighted_vt_handle += window_area * window_vt
|
1348
|
+
window_vertices = subsurface.vertices
|
1349
|
+
if window_vertices[0].z.round(2) == window_vertices[1].z.round(2)
|
1350
|
+
window_width = Math.sqrt((window_vertices[0].x - window_vertices[1].x) ** 2.0 + (window_vertices[0].y - window_vertices[1].y) ** 2.0)
|
1351
|
+
else
|
1352
|
+
window_width = Math.sqrt((window_vertices[1].x - window_vertices[2].x) ** 2.0 + (window_vertices[1].y - window_vertices[2].y) ** 2.0)
|
1353
|
+
end
|
1354
|
+
window_head_height = [window_vertices[0].z, window_vertices[1].z, window_vertices[2].z, window_vertices[3].z].max.round(2)
|
1355
|
+
primary_sidelighted_area_depth = [window_head_height, daylight_space_depth].min #as per NECB2011: 4.2.2.9.
|
1356
|
+
|
1357
|
+
##### Calculate the distance of the window to vertical walls on each side of the window (this is used to determine the sidelighted area's width).
|
1358
|
+
window_vertices_on_floor = []
|
1359
|
+
window_vertices.each do |vertex|
|
1360
|
+
window_vertices_on_floor << floor_surface.plane.project(vertex)
|
1361
|
+
end
|
1362
|
+
window_wall_distance_side1 = [Math.sqrt((wall_vertices_x_on_floor[0] - window_vertices_on_floor[0].x) ** 2.0 + (wall_vertices_y_on_floor[0] - window_vertices_on_floor[0].y) ** 2.0),
|
1363
|
+
Math.sqrt((wall_vertices_x_on_floor[0] - window_vertices_on_floor[2].x) ** 2.0 + (wall_vertices_y_on_floor[0] - window_vertices_on_floor[2].y) ** 2.0),
|
1364
|
+
0.6].min # 0.6 m as per NECB2011: 4.2.2.9.
|
1365
|
+
window_wall_distance_side2 = [Math.sqrt((wall_vertices_x_on_floor[1] - window_vertices_on_floor[0].x) ** 2.0 + (wall_vertices_y_on_floor[1] - window_vertices_on_floor[0].y) ** 2.0),
|
1366
|
+
Math.sqrt((wall_vertices_x_on_floor[1] - window_vertices_on_floor[2].x) ** 2.0 + (wall_vertices_y_on_floor[1] - window_vertices_on_floor[2].y) ** 2.0),
|
1367
|
+
0.6].min # 0.6 m as per NECB2011: 4.2.2.9.
|
1368
|
+
primary_sidelighted_area_width = window_wall_distance_side1 + window_width + window_wall_distance_side2
|
1369
|
+
primary_sidelighted_area = primary_sidelighted_area + primary_sidelighted_area_depth * primary_sidelighted_area_width
|
1370
|
+
end #if subsurface.subSurfaceType == "FixedWindow" || subsurface.subSurfaceType == "OperableWindow"
|
1371
|
+
end #surface.subSurfaces.each do |subsurface|
|
1372
|
+
end #if surface.outsideBoundaryCondition == "Outdoors" && surface.surfaceType == "Wall" && surface_z_min == floor_vertices[0][0].z
|
1373
|
+
end #daylight_space.surfaces.each do |surface|
|
1374
|
+
|
1375
|
+
return primary_sidelighted_area, area_weighted_vt_handle, window_area_sum
|
1376
|
+
end
|
1377
|
+
|
1378
|
+
##### The below method is for the 'model_add_daylighting_controls' method
|
1379
|
+
def get_parameters_skylight(daylight_space:, skylight_area_weighted_vt_handle:, skylight_area_sum:, daylighted_under_skylight_area:)
|
1380
|
+
|
1381
|
+
daylight_space.surfaces.sort.each do |surface|
|
1382
|
+
##### Get roof vertices
|
1383
|
+
if surface.outsideBoundaryCondition == "Outdoors" && surface.surfaceType == "RoofCeiling"
|
1384
|
+
roof_vertices = surface.vertices
|
1385
|
+
end
|
1386
|
+
|
1387
|
+
##### Loop through each subsurafce to calculate daylighted_area_under_skylights and skylight_effective_aperture for each daylight_space
|
1388
|
+
surface.subSurfaces.sort.each do |subsurface|
|
1389
|
+
if subsurface.subSurfaceType == "Skylight"
|
1390
|
+
skylight_vt = subsurface.visibleTransmittance
|
1391
|
+
skylight_vt = skylight_vt.get
|
1392
|
+
skylight_area = subsurface.netArea
|
1393
|
+
skylight_area_sum += skylight_area
|
1394
|
+
skylight_area_weighted_vt_handle += skylight_area * skylight_vt
|
1395
|
+
|
1396
|
+
##### Get skylight vertices
|
1397
|
+
skylight_vertices = subsurface.vertices
|
1398
|
+
|
1399
|
+
##### Calculate skylight width and height
|
1400
|
+
skylight_width = Math.sqrt((skylight_vertices[0].x - skylight_vertices[1].x) ** 2.0 + (skylight_vertices[0].y - skylight_vertices[1].y) ** 2.0)
|
1401
|
+
skylight_length = Math.sqrt((skylight_vertices[0].x - skylight_vertices[3].x) ** 2.0 + (skylight_vertices[0].y - skylight_vertices[3].y) ** 2.0)
|
1402
|
+
|
1403
|
+
##### Get ceiling height assuming the skylight is flush with the ceiling
|
1404
|
+
ceiling_height = skylight_vertices[0].z
|
1405
|
+
|
1406
|
+
##### Calculate roof lengths
|
1407
|
+
##### (Note: used OpenStudio BCL measure called "assign_ashrae_9012010_daylighting_controls" with some changes/correcctions)
|
1408
|
+
roof_length_0 = Math.sqrt((roof_vertices[0].x - roof_vertices[1].x) ** 2.0 + (roof_vertices[0].y - roof_vertices[1].y) ** 2.0)
|
1409
|
+
roof_length_1 = Math.sqrt((roof_vertices[1].x - roof_vertices[2].x) ** 2.0 + (roof_vertices[1].y - roof_vertices[2].y) ** 2.0)
|
1410
|
+
roof_length_2 = Math.sqrt((roof_vertices[2].x - roof_vertices[3].x) ** 2.0 + (roof_vertices[2].y - roof_vertices[3].y) ** 2.0)
|
1411
|
+
roof_length_3 = Math.sqrt((roof_vertices[3].x - roof_vertices[0].x) ** 2.0 + (roof_vertices[3].y - roof_vertices[0].y) ** 2.0)
|
1412
|
+
|
1413
|
+
##### Find the skylight point that is the closest one to roof_vertex_0
|
1414
|
+
##### (Note: used OpenStudio BCL measure called "assign_ashrae_9012010_daylighting_controls" with some changes/correcctions)
|
1415
|
+
roof_vertex_0_skylight_vertex_0 = Math.sqrt((roof_vertices[0].x - skylight_vertices[0].x) ** 2.0 + (roof_vertices[0].y - skylight_vertices[0].y) ** 2.0)
|
1416
|
+
roof_vertex_0_skylight_vertex_1 = Math.sqrt((roof_vertices[0].x - skylight_vertices[1].x) ** 2.0 + (roof_vertices[0].y - skylight_vertices[1].y) ** 2.0)
|
1417
|
+
roof_vertex_0_skylight_vertex_2 = Math.sqrt((roof_vertices[0].x - skylight_vertices[2].x) ** 2.0 + (roof_vertices[0].y - skylight_vertices[2].y) ** 2.0)
|
1418
|
+
roof_vertex_0_skylight_vertex_3 = Math.sqrt((roof_vertices[0].x - skylight_vertices[3].x) ** 2.0 + (roof_vertices[0].y - skylight_vertices[3].y) ** 2.0)
|
1419
|
+
roof_vertex_0_closest_distance = [roof_vertex_0_skylight_vertex_0, roof_vertex_0_skylight_vertex_1, roof_vertex_0_skylight_vertex_2, roof_vertex_0_skylight_vertex_3].min
|
1420
|
+
if roof_vertex_0_closest_distance == roof_vertex_0_skylight_vertex_0
|
1421
|
+
roof_vertex_0_closest_point = skylight_vertices[0]
|
1422
|
+
elsif roof_vertex_0_closest_distance == roof_vertex_0_skylight_vertex_1
|
1423
|
+
roof_vertex_0_closest_point = skylight_vertices[1]
|
1424
|
+
elsif roof_vertex_0_closest_distance == roof_vertex_0_skylight_vertex_2
|
1425
|
+
roof_vertex_0_closest_point = skylight_vertices[2]
|
1426
|
+
elsif roof_vertex_0_closest_distance == roof_vertex_0_skylight_vertex_3
|
1427
|
+
roof_vertex_0_closest_point = skylight_vertices[3]
|
1428
|
+
end
|
1429
|
+
|
1430
|
+
##### Find the skylight point that is the closest one to roof_vertex_2
|
1431
|
+
##### (Note: used OpenStudio BCL measure called "assign_ashrae_9012010_daylighting_controls" with some changes/correcctions)
|
1432
|
+
roof_vertex_2_skylight_vertex_0 = Math.sqrt((roof_vertices[2].x - skylight_vertices[0].x) ** 2.0 + (roof_vertices[2].y - skylight_vertices[0].y) ** 2.0)
|
1433
|
+
roof_vertex_2_skylight_vertex_1 = Math.sqrt((roof_vertices[2].x - skylight_vertices[1].x) ** 2.0 + (roof_vertices[2].y - skylight_vertices[1].y) ** 2.0)
|
1434
|
+
roof_vertex_2_skylight_vertex_2 = Math.sqrt((roof_vertices[2].x - skylight_vertices[2].x) ** 2.0 + (roof_vertices[2].y - skylight_vertices[2].y) ** 2.0)
|
1435
|
+
roof_vertex_2_skylight_vertex_3 = Math.sqrt((roof_vertices[2].x - skylight_vertices[3].x) ** 2.0 + (roof_vertices[2].y - skylight_vertices[3].y) ** 2.0)
|
1436
|
+
roof_vertex_2_closest_distance = [roof_vertex_2_skylight_vertex_0, roof_vertex_2_skylight_vertex_1, roof_vertex_2_skylight_vertex_2, roof_vertex_2_skylight_vertex_3].min
|
1437
|
+
if roof_vertex_2_closest_distance == roof_vertex_2_skylight_vertex_0
|
1438
|
+
roof_vertex_2_closest_point = skylight_vertices[0]
|
1439
|
+
elsif roof_vertex_2_closest_distance == roof_vertex_2_skylight_vertex_1
|
1440
|
+
roof_vertex_2_closest_point = skylight_vertices[1]
|
1441
|
+
elsif roof_vertex_2_closest_distance == roof_vertex_2_skylight_vertex_2
|
1442
|
+
roof_vertex_2_closest_point = skylight_vertices[2]
|
1443
|
+
elsif roof_vertex_2_closest_distance == roof_vertex_2_skylight_vertex_3
|
1444
|
+
roof_vertex_2_closest_point = skylight_vertices[3]
|
1445
|
+
end
|
1446
|
+
|
1447
|
+
##### Calculate the vertical distance from the closest skylight points (projection onto the roof) to the wall (projection onto the roof) for roof_vertex_0 and roof_vertex_2
|
1448
|
+
##### (Note: used OpenStudio BCL measure called "assign_ashrae_9012010_daylighting_controls" with some changes/correcctions)
|
1449
|
+
##### For the calculation of each vertical distance: (1) first the area of the triangle is calculated knowing the cooridantes of its three corners;
|
1450
|
+
##### (2) the vertical distance (i.e. triangle height) is calculated knowing the triangle area and the associated roof length.
|
1451
|
+
rv_0_triangle_0_area = 0.5 * (((roof_vertex_0_closest_point.x - roof_vertices[1].x) * (roof_vertex_0_closest_point.y - roof_vertices[0].y)) -
|
1452
|
+
((roof_vertex_0_closest_point.x - roof_vertices[0].x) * (roof_vertex_0_closest_point.y - roof_vertices[1].y))).abs
|
1453
|
+
rv_0_distance_0 = (2.0 * rv_0_triangle_0_area) / roof_length_0
|
1454
|
+
rv_0_triangle_3_area = 0.5 * (((roof_vertex_0_closest_point.x - roof_vertices[3].x) * (roof_vertex_0_closest_point.y - roof_vertices[0].y)) -
|
1455
|
+
((roof_vertex_0_closest_point.x - roof_vertices[0].x) * (roof_vertex_0_closest_point.y - roof_vertices[3].y))).abs
|
1456
|
+
rv_0_distance_3 = (2.0 * rv_0_triangle_3_area) / roof_length_3
|
1457
|
+
|
1458
|
+
rv_2_triangle_1_area = 0.5 * (((roof_vertex_2_closest_point.x - roof_vertices[1].x) * (roof_vertex_2_closest_point.y - roof_vertices[2].y)) -
|
1459
|
+
((roof_vertex_2_closest_point.x - roof_vertices[2].x) * (roof_vertex_2_closest_point.y - roof_vertices[1].y))).abs
|
1460
|
+
rv_2_distance_1 = (2.0 * rv_2_triangle_1_area) / roof_length_1
|
1461
|
+
rv_2_triangle_2_area = 0.5 * (((roof_vertex_2_closest_point.x - roof_vertices[3].x) * (roof_vertex_2_closest_point.y - roof_vertices[2].y)) -
|
1462
|
+
((roof_vertex_2_closest_point.x - roof_vertices[2].x) * (roof_vertex_2_closest_point.y - roof_vertices[3].y))).abs
|
1463
|
+
rv_2_distance_2 = (2.0 * rv_2_triangle_2_area) / roof_length_2
|
1464
|
+
|
1465
|
+
##### Set the vertical distances from the closest skylight points (projection onto the roof) to the wall (projection onto the roof) for roof_vertex_0 and roof_vertex_2
|
1466
|
+
distance_1 = rv_0_distance_0
|
1467
|
+
distance_2 = rv_0_distance_3
|
1468
|
+
distance_3 = rv_2_distance_1
|
1469
|
+
distance_4 = rv_2_distance_2
|
1470
|
+
|
1471
|
+
##### Calculate the width and length of the daylighted area under the skylight as per NECB2011: 4.2.2.5.
|
1472
|
+
##### Note: In the below loops, if any exterior walls has window(s), the width and length of the daylighted area under the skylight are re-calculated as per NECB2011: 4.2.2.5.
|
1473
|
+
daylighted_under_skylight_width = skylight_width + [0.7 * ceiling_height, distance_1].min + [0.7 * ceiling_height, distance_4].min
|
1474
|
+
daylighted_under_skylight_length = skylight_length + [0.7 * ceiling_height, distance_2].min + [0.7 * ceiling_height, distance_3].min
|
1475
|
+
|
1476
|
+
##### As noted above, the width and length of the daylighted area under the skylight are re-calculated (as per NECB2011: 4.2.2.5.), if any exterior walls has window(s).
|
1477
|
+
##### To this end, the window_head_height should be calculated, as below:
|
1478
|
+
daylight_space.surfaces.sort.each do |surface|
|
1479
|
+
if surface.outsideBoundaryCondition == "Outdoors" && surface.surfaceType == "Wall"
|
1480
|
+
wall_vertices_on_floor_x = []
|
1481
|
+
wall_vertices_on_floor_y = []
|
1482
|
+
wall_vertices = surface.vertices
|
1483
|
+
if wall_vertices[0].z == wall_vertices[1].z
|
1484
|
+
wall_vertices_on_floor_x << wall_vertices[0].x
|
1485
|
+
wall_vertices_on_floor_x << wall_vertices[1].x
|
1486
|
+
wall_vertices_on_floor_y << wall_vertices[0].y
|
1487
|
+
wall_vertices_on_floor_y << wall_vertices[1].y
|
1488
|
+
elsif wall_vertices[0].z == wall_vertices[3].z
|
1489
|
+
wall_vertices_on_floor_x << wall_vertices[0].x
|
1490
|
+
wall_vertices_on_floor_x << wall_vertices[3].x
|
1491
|
+
wall_vertices_on_floor_y << wall_vertices[0].y
|
1492
|
+
wall_vertices_on_floor_y << wall_vertices[3].y
|
1493
|
+
end
|
1494
|
+
window_vertices = subsurface.vertices
|
1495
|
+
window_head_height = [window_vertices[0].z, window_vertices[1].z, window_vertices[2].z, window_vertices[3].z].max.round(2)
|
1496
|
+
|
1497
|
+
##### Calculate the exterior wall length (on the floor)
|
1498
|
+
exterior_wall_length = Math.sqrt((wall_vertices_on_floor_x[0] - wall_vertices_on_floor_x[1]) ** 2.0 + (wall_vertices_on_floor_y[0] - wall_vertices_on_floor_y[1]) ** 2.0)
|
1499
|
+
|
1500
|
+
##### Calculate the vertical distance of skylight_vertices[0] projection onto the roof/floor to the exterior wall
|
1501
|
+
skylight_vertex_0_triangle_area = 0.5 * (((wall_vertices_on_floor_x[0] - wall_vertices_on_floor_x[1]) * (wall_vertices_on_floor_y[0] - skylight_vertices[0].y)) -
|
1502
|
+
((wall_vertices_on_floor_x[0] - skylight_vertices[0].x) * (wall_vertices_on_floor_y[0] - wall_vertices_on_floor_y[1]))).abs
|
1503
|
+
skylight_vertex_0_distance = (2.0 * skylight_vertex_0_triangle_area) / exterior_wall_length
|
1504
|
+
|
1505
|
+
##### Calculate the vertical distance of skylight_vertices[1] projection onto the roof/floor to the exterior wall
|
1506
|
+
skylight_vertex_1_triangle_area = 0.5 * (((wall_vertices_on_floor_x[0] - wall_vertices_on_floor_x[1]) * (wall_vertices_on_floor_y[0] - skylight_vertices[1].y)) -
|
1507
|
+
((wall_vertices_on_floor_x[0] - skylight_vertices[1].x) * (wall_vertices_on_floor_y[0] - wall_vertices_on_floor_y[1]))).abs
|
1508
|
+
skylight_vertex_1_distance = (2.0 * skylight_vertex_1_triangle_area) / exterior_wall_length
|
1509
|
+
|
1510
|
+
##### Calculate the vertical distance of skylight_vertices[3] projection onto the roof/floor to the exterior wall
|
1511
|
+
skylight_vertex_3_triangle_area = 0.5 * (((wall_vertices_on_floor_x[0] - wall_vertices_on_floor_x[1]) * (wall_vertices_on_floor_y[0] - skylight_vertices[3].y)) -
|
1512
|
+
((wall_vertices_on_floor_x[0] - skylight_vertices[3].x) * (wall_vertices_on_floor_y[0] - wall_vertices_on_floor_y[1]))).abs
|
1513
|
+
skylight_vertex_3_distance = (2.0 * skylight_vertex_3_triangle_area) / exterior_wall_length
|
1514
|
+
|
1515
|
+
##### Loop through the subsurfaces that has exterior windows to re-calculate the width and length of the daylighted area under the skylight
|
1516
|
+
surface.subSurfaces.sort.each do |subsurface|
|
1517
|
+
if subsurface.subSurfaceType == "FixedWindow" || subsurface.subSurfaceType == "OperableWindow"
|
1518
|
+
|
1519
|
+
if skylight_vertex_0_distance == skylight_vertex_1_distance #skylight_01 is in parellel to the exterior wall
|
1520
|
+
if skylight_vertex_0_distance.round(2) == distance_2.round(2)
|
1521
|
+
daylighted_under_skylight_length = skylight_length + [0.7 * ceiling_height, distance_2, distance_2 - window_head_height].min + [0.7 * ceiling_height, distance_3].min
|
1522
|
+
elsif skylight_vertex_0_distance.round(2) == distance_3.round(2)
|
1523
|
+
daylighted_under_skylight_length = skylight_length + [0.7 * ceiling_height, distance_2].min + [0.7 * ceiling_height, distance_3, distance_3 - window_head_height].min
|
1524
|
+
end
|
1525
|
+
elsif skylight_vertex_0_distance == skylight_vertex_3_distance #skylight_03 is in parellel to the exterior wall
|
1526
|
+
if skylight_vertex_0_distance.round(2) == distance_1.round(2)
|
1527
|
+
daylighted_under_skylight_width = skylight_width + [0.7 * ceiling_height, distance_1, distance_1 - window_head_height].min + [0.7 * ceiling_height, distance_4].min
|
1528
|
+
elsif skylight_vertex_0_distance.round(2) == distance_4.round(2)
|
1529
|
+
daylighted_under_skylight_width = skylight_width + [0.7 * ceiling_height, distance_1].min + [0.7 * ceiling_height, distance_4, distance_4 - window_head_height].min
|
1530
|
+
end
|
1531
|
+
end #if skylight_vertex_0_distance == skylight_vertex_1_distance
|
1532
|
+
|
1533
|
+
daylighted_under_skylight_area += daylighted_under_skylight_length * daylighted_under_skylight_width
|
1534
|
+
|
1535
|
+
end #if subsurface.subSurfaceType == "FixedWindow" || subsurface.subSurfaceType == "OperableWindow"
|
1536
|
+
end #surface.subSurfaces.each do |subsurface|
|
1537
|
+
end #if surface.outsideBoundaryCondition == "Outdoors" && surface.surfaceType == "Wall"
|
1538
|
+
end #daylight_space.surfaces.each do |surface|
|
1539
|
+
|
1540
|
+
end #if subsurface.subSurfaceType == "Skylight"
|
1541
|
+
end #surface.subSurfaces.each do |subsurface|
|
1542
|
+
end #daylight_space.surfaces.each do |surface|
|
1543
|
+
|
1544
|
+
return daylighted_under_skylight_area, skylight_area_weighted_vt_handle, skylight_area_sum
|
1545
|
+
end
|
1546
|
+
|
521
1547
|
end
|