openstudio-geb 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/measures/GEB Metrics Report/resources/os_lib_reporting.rb +5 -24
- data/lib/measures/add_fan_assist_night_ventilation_with_hybrid_control/LICENSE.md +13 -0
- data/lib/measures/add_fan_assist_night_ventilation_with_hybrid_control/README.md +152 -0
- data/lib/measures/add_fan_assist_night_ventilation_with_hybrid_control/README.md.erb +45 -0
- data/lib/measures/add_fan_assist_night_ventilation_with_hybrid_control/docs/.gitkeep +0 -0
- data/lib/measures/add_fan_assist_night_ventilation_with_hybrid_control/measure.rb +604 -0
- data/lib/measures/add_fan_assist_night_ventilation_with_hybrid_control/measure.xml +265 -0
- data/lib/measures/add_fan_assist_night_ventilation_with_hybrid_control/tests/USA_NY_Buffalo.Niagara.Intl.AP.725280_TMY3.epw +8768 -0
- data/lib/measures/add_fan_assist_night_ventilation_with_hybrid_control/tests/add_fan_assist_night_ventilation_with_hybrid_control_test.rb +94 -0
- data/lib/measures/add_fan_assist_night_ventilation_with_hybrid_control/tests/medium_office_with_internal_windows.osm +13459 -0
- data/lib/measures/add_heat_pump_water_heater/measure.rb +2 -2
- data/lib/measures/apply_dynamic_coating_to_roof_wall/LICENSE.md +1 -0
- data/lib/measures/apply_dynamic_coating_to_roof_wall/README.md +101 -0
- data/lib/measures/apply_dynamic_coating_to_roof_wall/README.md.erb +45 -0
- data/lib/measures/apply_dynamic_coating_to_roof_wall/docs/.gitkeep +0 -0
- data/lib/measures/apply_dynamic_coating_to_roof_wall/measure.rb +421 -0
- data/lib/measures/apply_dynamic_coating_to_roof_wall/measure.xml +204 -0
- data/lib/measures/apply_dynamic_coating_to_roof_wall/tests/MediumOffice-90.1-2010-ASHRAE 169-2013-5A.osm +13669 -0
- data/lib/measures/apply_dynamic_coating_to_roof_wall/tests/SF-CACZ6-HPWH-pre1978.osm +16130 -0
- data/lib/measures/apply_dynamic_coating_to_roof_wall/tests/USA_NY_Buffalo.Niagara.Intl.AP.725280_TMY3.epw +8768 -0
- data/lib/measures/apply_dynamic_coating_to_roof_wall/tests/apply_dynamic_coating_to_roof_wall_test.rb +81 -0
- data/lib/openstudio/geb/run.rb +34 -0
- data/lib/openstudio/geb/version.rb +1 -1
- metadata +23 -3
@@ -0,0 +1,604 @@
|
|
1
|
+
# insert your copyright here
|
2
|
+
|
3
|
+
# see the URL below for information on how to write OpenStudio measures
|
4
|
+
# http://nrel.github.io/OpenStudio-user-documentation/reference/measure_writing_guide/
|
5
|
+
|
6
|
+
# start the measure
|
7
|
+
class AddFanAssistNightVentilationWithHybridControl < OpenStudio::Measure::ModelMeasure
|
8
|
+
require 'time'
|
9
|
+
require 'json'
|
10
|
+
|
11
|
+
# human readable name
|
12
|
+
def name
|
13
|
+
# Measure name should be the title case of the class name.
|
14
|
+
return 'Add fan assist night ventilation with hybrid control'
|
15
|
+
end
|
16
|
+
|
17
|
+
# human readable description
|
18
|
+
def description
|
19
|
+
return 'This measure is modified based on the OS measure "fan_assist_night_ventilation" from "openstudio-ee-gem". ' \
|
20
|
+
'It adds night ventilation that is enabled by opening windows assisted by exhaust fans. Hybrid ventilation ' \
|
21
|
+
'control is added to avoid simultaneous operation of windows and HVAC.'
|
22
|
+
end
|
23
|
+
|
24
|
+
# human readable description of modeling approach
|
25
|
+
def modeler_description
|
26
|
+
return "This measure adds a zone ventilation object to each zone with operable windwos. The measure will first " \
|
27
|
+
"look for a celing opening to find a connection for zone a zone mixing object. If a ceiling isn't found, " \
|
28
|
+
"then it looks for a wall. The end result is zone ventilation object followed by a path of zone mixing objects. " \
|
29
|
+
"The exhaust fan consumption is modeled in the zone ventilation object, but no heat is brought in from the fan. \n " \
|
30
|
+
"Different from the original 'fan_assist_night_ventilation' measure, this measure can be applied to models " \
|
31
|
+
"with mechenical systems. HybridVentilationAvailabilityManager is added to airloops and zonal systems to avoid " \
|
32
|
+
"simultaneous operation of windows and HVAC. The zone ventilation is controlled by a combination of schedule, " \
|
33
|
+
"indoor and outdoor temperature, and wind speed."
|
34
|
+
end
|
35
|
+
|
36
|
+
# define the arguments that the user will input
|
37
|
+
def arguments(model)
|
38
|
+
args = OpenStudio::Measure::OSArgumentVector.new
|
39
|
+
|
40
|
+
# make an argument for night ventilation air change rate
|
41
|
+
design_night_vent_ach = OpenStudio::Measure::OSArgument::makeDoubleArgument('design_night_vent_ach', true)
|
42
|
+
design_night_vent_ach.setDisplayName('Design night ventilation air change rate defined by ACH-air changes per hour')
|
43
|
+
design_night_vent_ach.setDefaultValue(3)
|
44
|
+
args << design_night_vent_ach
|
45
|
+
|
46
|
+
# add argument for exhaust fan pressure rise
|
47
|
+
fan_pressure_rise = OpenStudio::Measure::OSArgument.makeDoubleArgument('fan_pressure_rise', true)
|
48
|
+
fan_pressure_rise.setDisplayName('Fan Pressure Rise')
|
49
|
+
fan_pressure_rise.setUnits('Pa')
|
50
|
+
fan_pressure_rise.setDefaultValue(500.0)
|
51
|
+
args << fan_pressure_rise
|
52
|
+
|
53
|
+
# add argument for exhaust fan efficiency
|
54
|
+
efficiency = OpenStudio::Measure::OSArgument.makeDoubleArgument('efficiency', true)
|
55
|
+
efficiency.setDisplayName('Fan Total Efficiency')
|
56
|
+
efficiency.setDefaultValue(0.65)
|
57
|
+
args << efficiency
|
58
|
+
|
59
|
+
# make an argument for min indoor temp
|
60
|
+
min_indoor_temp = OpenStudio::Measure::OSArgument::makeDoubleArgument('min_indoor_temp', false)
|
61
|
+
min_indoor_temp.setDisplayName('Minimum Indoor Temperature (degC)')
|
62
|
+
min_indoor_temp.setDescription('The indoor temperature below which ventilation is shutoff.')
|
63
|
+
min_indoor_temp.setDefaultValue(20)
|
64
|
+
args << min_indoor_temp
|
65
|
+
|
66
|
+
# make an argument for maximum indoor temperature
|
67
|
+
max_indoor_temp = OpenStudio::Measure::OSArgument::makeDoubleArgument('max_indoor_temp', false)
|
68
|
+
max_indoor_temp.setDisplayName('Maximum Indoor Temperature (degC)')
|
69
|
+
max_indoor_temp.setDescription('The indoor temperature above which ventilation is shutoff.')
|
70
|
+
max_indoor_temp.setDefaultValue(26)
|
71
|
+
args << max_indoor_temp
|
72
|
+
|
73
|
+
# make an argument for delta temperature
|
74
|
+
delta_temp = OpenStudio::Measure::OSArgument::makeDoubleArgument('delta_temp', false)
|
75
|
+
delta_temp.setDisplayName('Minimum Indoor-Outdoor Temperature Difference (degC)')
|
76
|
+
delta_temp.setDescription('This is the temperature difference between the indoor and outdoor air dry-bulb '\
|
77
|
+
'temperatures below which ventilation is shutoff. For example, a delta temperature '\
|
78
|
+
'of 2 degC means ventilation is available if the outside air temperature is at least '\
|
79
|
+
'2 degC cooler than the zone air temperature. Values can be negative.')
|
80
|
+
delta_temp.setDefaultValue(2.0)
|
81
|
+
args << delta_temp
|
82
|
+
|
83
|
+
# make an argument for minimum outdoor temperature
|
84
|
+
min_outdoor_temp = OpenStudio::Measure::OSArgument::makeDoubleArgument('min_outdoor_temp', true)
|
85
|
+
min_outdoor_temp.setDisplayName('Minimum Outdoor Temperature (degC)')
|
86
|
+
min_outdoor_temp.setDescription('The outdoor temperature below which ventilation is shut off.')
|
87
|
+
min_outdoor_temp.setDefaultValue(18)
|
88
|
+
args << min_outdoor_temp
|
89
|
+
|
90
|
+
# make an argument for maximum outdoor temperature
|
91
|
+
max_outdoor_temp = OpenStudio::Measure::OSArgument::makeDoubleArgument('max_outdoor_temp', true)
|
92
|
+
max_outdoor_temp.setDisplayName('Maximum Outdoor Temperature (degC)')
|
93
|
+
max_outdoor_temp.setDescription('The outdoor temperature above which ventilation is shut off.')
|
94
|
+
max_outdoor_temp.setDefaultValue(26)
|
95
|
+
args << max_outdoor_temp
|
96
|
+
|
97
|
+
# make an argument for maximum wind speed
|
98
|
+
max_wind_speed = OpenStudio::Measure::OSArgument::makeDoubleArgument('max_wind_speed', false)
|
99
|
+
max_wind_speed.setDisplayName('Maximum Wind Speed (m/s)')
|
100
|
+
max_wind_speed.setDescription('This is the wind speed above which ventilation is shut off. The default values assume windows are closed when wind is above a gentle breeze to avoid blowing around papers in the space.')
|
101
|
+
max_wind_speed.setDefaultValue(40)
|
102
|
+
args << max_wind_speed
|
103
|
+
|
104
|
+
# make an argument for the start time of natural ventilation
|
105
|
+
night_vent_starttime = OpenStudio::Measure::OSArgument.makeStringArgument('night_vent_starttime', false)
|
106
|
+
night_vent_starttime.setDisplayName('Daily Start Time for natural ventilation')
|
107
|
+
night_vent_starttime.setDescription('Use 24 hour format (HR:MM)')
|
108
|
+
night_vent_starttime.setDefaultValue('20:00')
|
109
|
+
args << night_vent_starttime
|
110
|
+
|
111
|
+
# make an argument for the end time of natural ventilation
|
112
|
+
night_vent_endtime = OpenStudio::Measure::OSArgument.makeStringArgument('night_vent_endtime', false)
|
113
|
+
night_vent_endtime.setDisplayName('Daily End Time for natural ventilation')
|
114
|
+
night_vent_endtime.setDescription('Use 24 hour format (HR:MM)')
|
115
|
+
night_vent_endtime.setDefaultValue('08:00')
|
116
|
+
args << night_vent_endtime
|
117
|
+
|
118
|
+
# make an argument for the start date of natural ventilation
|
119
|
+
night_vent_startdate = OpenStudio::Measure::OSArgument.makeStringArgument('night_vent_startdate', false)
|
120
|
+
night_vent_startdate.setDisplayName('Start Date for natural ventilation')
|
121
|
+
night_vent_startdate.setDescription('In MM-DD format')
|
122
|
+
night_vent_startdate.setDefaultValue('03-01')
|
123
|
+
args << night_vent_startdate
|
124
|
+
|
125
|
+
# make an argument for the end date of natural ventilation
|
126
|
+
night_vent_enddate = OpenStudio::Measure::OSArgument.makeStringArgument('night_vent_enddate', false)
|
127
|
+
night_vent_enddate.setDisplayName('End Date for natural ventilation')
|
128
|
+
night_vent_enddate.setDescription('In MM-DD format')
|
129
|
+
night_vent_enddate.setDefaultValue('10-31')
|
130
|
+
args << night_vent_enddate
|
131
|
+
|
132
|
+
# Make boolean arguments for natural ventilation schedule on weekends
|
133
|
+
wknds = OpenStudio::Measure::OSArgument.makeBoolArgument('wknds', false)
|
134
|
+
wknds.setDisplayName('Allow night time ventilation on weekends')
|
135
|
+
wknds.setDefaultValue(true)
|
136
|
+
args << wknds
|
137
|
+
|
138
|
+
return args
|
139
|
+
end
|
140
|
+
|
141
|
+
# define what happens when the measure is run
|
142
|
+
def run(model, runner, user_arguments)
|
143
|
+
super(model, runner, user_arguments)
|
144
|
+
|
145
|
+
# use the built-in error checking
|
146
|
+
if !runner.validateUserArguments(arguments(model), user_arguments)
|
147
|
+
return false
|
148
|
+
end
|
149
|
+
|
150
|
+
# assign the user inputs to variables
|
151
|
+
design_night_vent_ach = runner.getDoubleArgumentValue('design_night_vent_ach', user_arguments)
|
152
|
+
fan_pressure_rise = runner.getDoubleArgumentValue('fan_pressure_rise', user_arguments)
|
153
|
+
efficiency = runner.getDoubleArgumentValue('efficiency', user_arguments)
|
154
|
+
min_indoor_temp = runner.getDoubleArgumentValue('min_indoor_temp', user_arguments)
|
155
|
+
max_indoor_temp = runner.getDoubleArgumentValue('max_indoor_temp', user_arguments)
|
156
|
+
delta_temp = runner.getDoubleArgumentValue('delta_temp', user_arguments)
|
157
|
+
min_outdoor_temp = runner.getDoubleArgumentValue('min_outdoor_temp', user_arguments)
|
158
|
+
max_outdoor_temp = runner.getDoubleArgumentValue('max_outdoor_temp', user_arguments)
|
159
|
+
max_wind_speed = runner.getDoubleArgumentValue('max_wind_speed', user_arguments)
|
160
|
+
night_vent_starttime = runner.getStringArgumentValue('night_vent_starttime', user_arguments)
|
161
|
+
night_vent_endtime = runner.getStringArgumentValue('night_vent_endtime', user_arguments)
|
162
|
+
night_vent_startdate = runner.getStringArgumentValue('night_vent_startdate', user_arguments)
|
163
|
+
night_vent_enddate = runner.getStringArgumentValue('night_vent_enddate', user_arguments)
|
164
|
+
wknds = runner.getBoolArgumentValue('wknds', user_arguments)
|
165
|
+
|
166
|
+
|
167
|
+
# check night ventilation ach input
|
168
|
+
if design_night_vent_ach <= 0
|
169
|
+
runner.registerError('Design night ventilation ACH should be positive. Please double check your input.')
|
170
|
+
return false
|
171
|
+
elsif design_night_vent_ach > 10
|
172
|
+
runner.registerWarning("Design night ventilation ACH #{design_night_vent_ach} is higher than 10, which is unusually large. Please double check your input.")
|
173
|
+
elsif design_night_vent_ach < 0.3
|
174
|
+
runner.registerWarning("Design night ventilation ACH #{design_night_vent_ach} is lower than 0.3, which is unusually small. Please double check your input.")
|
175
|
+
end
|
176
|
+
|
177
|
+
# check fan efficiency input
|
178
|
+
if efficiency <= 0
|
179
|
+
runner.registerError('Exhaust fan efficiency should be positive. Please double check your input.')
|
180
|
+
return false
|
181
|
+
elsif efficiency > 1
|
182
|
+
runner.registerError("Exhaust fan efficiency #{efficiency} is larger than 1. Exhaust fan efficiency should be between 0 and 1. Please double check your input.")
|
183
|
+
return false
|
184
|
+
end
|
185
|
+
|
186
|
+
# check temp limit inputs
|
187
|
+
if min_indoor_temp >= max_indoor_temp
|
188
|
+
runner.registerError('Minimum indoor temperature should be lower than maximum outdoor temperature. Please double check your input.')
|
189
|
+
return false
|
190
|
+
end
|
191
|
+
|
192
|
+
if min_outdoor_temp >= max_outdoor_temp
|
193
|
+
runner.registerError('Minimum outdoor temperature should be lower than maximum outdoor temperature. Please double check your input.')
|
194
|
+
return false
|
195
|
+
end
|
196
|
+
|
197
|
+
# check max wind speed input
|
198
|
+
if max_wind_speed <= 0
|
199
|
+
runner.registerError('Maximum wind speed should be positive. Please double check your input.')
|
200
|
+
return false
|
201
|
+
end
|
202
|
+
|
203
|
+
# check delta temperature input
|
204
|
+
if delta_temp < 0
|
205
|
+
runner.registerWarning("delta_temp #{delta_temp} is negative. Normally delta temperature should be positive "\
|
206
|
+
"or at least 0 to enable free cooling and avoid introducing extra cooling load. "\
|
207
|
+
"Please double check your input.")
|
208
|
+
end
|
209
|
+
|
210
|
+
# check time format
|
211
|
+
begin
|
212
|
+
night_vent_starttime = Time.strptime(night_vent_starttime, '%H:%M')
|
213
|
+
night_vent_endtime = Time.strptime(night_vent_endtime, '%H:%M')
|
214
|
+
rescue ArgumentError
|
215
|
+
runner.registerError('Natural ventilation start and end time are required, and should be in format of %H:%M, e.g., 16:00.')
|
216
|
+
return false
|
217
|
+
end
|
218
|
+
if night_vent_starttime < night_vent_endtime
|
219
|
+
runner.registerWarning('Night ventilation end time is later than start time, referring to non-overnight ' \
|
220
|
+
'natural ventilation. Make sure this is expected.')
|
221
|
+
end
|
222
|
+
|
223
|
+
# check date format
|
224
|
+
md = /(\d\d)-(\d\d)/.match(night_vent_startdate)
|
225
|
+
if md
|
226
|
+
night_vent_start_month = md[1].to_i
|
227
|
+
night_vent_start_day = md[2].to_i
|
228
|
+
else
|
229
|
+
runner.registerError('Start date must be in MM-DD format.')
|
230
|
+
return false
|
231
|
+
end
|
232
|
+
|
233
|
+
md = /(\d\d)-(\d\d)/.match(night_vent_enddate)
|
234
|
+
if md
|
235
|
+
night_vent_end_month = md[1].to_i
|
236
|
+
night_vent_end_day = md[2].to_i
|
237
|
+
else
|
238
|
+
runner.registerError('End date must be in MM-DD format.')
|
239
|
+
return false
|
240
|
+
end
|
241
|
+
|
242
|
+
night_vent_startdate_os = model.getYearDescription.makeDate(night_vent_start_month, night_vent_start_day)
|
243
|
+
night_vent_enddate_os = model.getYearDescription.makeDate(night_vent_end_month, night_vent_end_day)
|
244
|
+
|
245
|
+
# if_overnight: 1 or 0; wknds (if applicable to weekends): 1 or 0
|
246
|
+
# by default schedule on value is 1, sometimes need specify, e.g., natural ventilation window open area fraction
|
247
|
+
def create_sch(model, sch_name, start_time, end_time, start_date, end_date, wknds, sch_on_value=1, sch_off_value=0)
|
248
|
+
day_start_time = Time.strptime("00:00", '%H:%M')
|
249
|
+
# create discharging schedule
|
250
|
+
new_sch_ruleset = OpenStudio::Model::ScheduleRuleset.new(model)
|
251
|
+
new_sch_ruleset.setName(sch_name)
|
252
|
+
new_sch_ruleset.defaultDaySchedule.setName(sch_name + ' default')
|
253
|
+
if start_time > end_time
|
254
|
+
if_overnight = sch_on_value # assigned as schedule on value
|
255
|
+
else
|
256
|
+
if_overnight = sch_off_value # assigned as schedule off value
|
257
|
+
end
|
258
|
+
|
259
|
+
for min in 1..24*60
|
260
|
+
if ((end_time - day_start_time)/60).to_i == min
|
261
|
+
time = OpenStudio::Time.new(0, 0, min)
|
262
|
+
new_sch_ruleset.defaultDaySchedule.addValue(time, sch_on_value)
|
263
|
+
elsif ((start_time - day_start_time)/60).to_i == min
|
264
|
+
time = OpenStudio::Time.new(0, 0, min)
|
265
|
+
new_sch_ruleset.defaultDaySchedule.addValue(time, sch_off_value)
|
266
|
+
elsif min == 24*60
|
267
|
+
time = OpenStudio::Time.new(0, 0, min)
|
268
|
+
new_sch_ruleset.defaultDaySchedule.addValue(time, if_overnight)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
start_month = start_date.monthOfYear.value
|
273
|
+
start_day = start_date.dayOfMonth
|
274
|
+
end_month = end_date.monthOfYear.value
|
275
|
+
end_day = end_date.dayOfMonth
|
276
|
+
ts_rule = OpenStudio::Model::ScheduleRule.new(new_sch_ruleset, new_sch_ruleset.defaultDaySchedule)
|
277
|
+
ts_rule.setName("#{new_sch_ruleset.name} #{start_month}/#{start_day}-#{end_month}/#{end_day} Rule")
|
278
|
+
ts_rule.setStartDate(start_date)
|
279
|
+
ts_rule.setEndDate(end_date)
|
280
|
+
ts_rule.setApplyWeekdays(true)
|
281
|
+
if wknds
|
282
|
+
ts_rule.setApplyWeekends(true)
|
283
|
+
else
|
284
|
+
ts_rule.setApplyWeekends(false)
|
285
|
+
end
|
286
|
+
|
287
|
+
unless start_month == 1 && start_day == 1
|
288
|
+
new_rule_day = OpenStudio::Model::ScheduleDay.new(model)
|
289
|
+
new_rule_day.addValue(OpenStudio::Time.new(0,24), 0)
|
290
|
+
new_rule = OpenStudio::Model::ScheduleRule.new(new_sch_ruleset, new_rule_day)
|
291
|
+
new_rule.setName("#{new_sch_ruleset.name} 01/01-#{start_month}/#{start_day} Rule")
|
292
|
+
new_rule.setStartDate(model.getYearDescription.makeDate(1, 1))
|
293
|
+
new_rule.setEndDate(model.getYearDescription.makeDate(start_month, start_day))
|
294
|
+
new_rule.setApplyAllDays(true)
|
295
|
+
end
|
296
|
+
|
297
|
+
unless end_month == 12 && end_day == 31
|
298
|
+
new_rule_day = OpenStudio::Model::ScheduleDay.new(model)
|
299
|
+
new_rule_day.addValue(OpenStudio::Time.new(0,24), 0)
|
300
|
+
new_rule = OpenStudio::Model::ScheduleRule.new(new_sch_ruleset, new_rule_day)
|
301
|
+
new_rule.setName("#{new_sch_ruleset.name} #{end_month}/#{end_day}-12/31 Rule")
|
302
|
+
new_rule.setStartDate(model.getYearDescription.makeDate(end_month, end_day))
|
303
|
+
new_rule.setEndDate(model.getYearDescription.makeDate(12, 31))
|
304
|
+
new_rule.setApplyAllDays(true)
|
305
|
+
end
|
306
|
+
|
307
|
+
return new_sch_ruleset
|
308
|
+
end
|
309
|
+
|
310
|
+
def inspect_airflow_surfaces(zone)
|
311
|
+
array = [] # [adjacent_zone,surfaceType]
|
312
|
+
zone.spaces.each do |space|
|
313
|
+
space.surfaces.each do |surface|
|
314
|
+
next if surface.adjacentSurface.is_initialized != true
|
315
|
+
next if !surface.adjacentSurface.get.space.is_initialized
|
316
|
+
next if !surface.adjacentSurface.get.space.get.thermalZone.is_initialized
|
317
|
+
adjacent_zone = surface.adjacentSurface.get.space.get.thermalZone.get
|
318
|
+
if surface.surfaceType == 'RoofCeiling' || surface.surfaceType == 'Wall'
|
319
|
+
if surface.isAirWall
|
320
|
+
array << [adjacent_zone, surface.surfaceType]
|
321
|
+
else
|
322
|
+
surface.subSurfaces.each do |sub_surface|
|
323
|
+
next if sub_surface.adjacentSubSurface.is_initialized != true
|
324
|
+
next if !sub_surface.adjacentSubSurface.get.surface.get.space.is_initialized
|
325
|
+
next if !sub_surface.adjacentSubSurface.get.surface.get.space.get.thermalZone.is_initialized
|
326
|
+
adjacent_zone = sub_surface.adjacentSubSurface.get.surface.get.space.get.thermalZone.get
|
327
|
+
# available subsurfacetypes: "FixedWindow", "OperableWindow", "Door", "GlassDoor", "OverheadDoor", "Skylight", "TubularDaylightDome", "TubularDaylightDiffuser"
|
328
|
+
# Often windows are assigned as FixedWindow by default but not indicating it cannot be opened.
|
329
|
+
if sub_surface.isAirWall || sub_surface.subSurfaceType == 'OperableWindow' || sub_surface.subSurfaceType == 'FixedWindow' ||
|
330
|
+
sub_surface.subSurfaceType == 'Door' || sub_surface.subSurfaceType == 'GlassDoor'
|
331
|
+
array << [adjacent_zone, surface.surfaceType]
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
return array
|
340
|
+
end
|
341
|
+
|
342
|
+
# report initial condition of model
|
343
|
+
runner.registerInitialCondition("The building started with #{model.getZoneVentilationDesignFlowRates.size} "\
|
344
|
+
" zone ventilation design flow rate objects and #{model.getZoneMixings.size} zone mixing objects.")
|
345
|
+
|
346
|
+
|
347
|
+
#################### STEP 1: find ventilation path and create exhaust zones ################
|
348
|
+
# setup hash to hold path objects and exhaust zones
|
349
|
+
path_objects = {}
|
350
|
+
exhaust_zones = {}
|
351
|
+
zones_w_ext_windows = []
|
352
|
+
nv_zone_list = {} # save zones with exterior windows and their zone ventilation object info
|
353
|
+
|
354
|
+
model.getSpaces.each do |space|
|
355
|
+
next if space.thermalZone.empty?
|
356
|
+
thermal_zone = space.thermalZone.get
|
357
|
+
|
358
|
+
# store airflow paths for future use
|
359
|
+
path_objects[thermal_zone] = inspect_airflow_surfaces(thermal_zone)
|
360
|
+
|
361
|
+
# get the list of zones with exterior windows
|
362
|
+
space.surfaces.sort.each do |surface|
|
363
|
+
surface.subSurfaces.sort.each do |subsurface|
|
364
|
+
if (subsurface.subSurfaceType == 'OperableWindow' || subsurface.subSurfaceType == 'FixedWindow') && subsurface.outsideBoundaryCondition == 'Outdoors'
|
365
|
+
zones_w_ext_windows << thermal_zone unless zones_w_ext_windows.include?(thermal_zone)
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
#################### STEP 2: add zone ventilation objects and zone mixing objects ################
|
372
|
+
# setup has to store paths
|
373
|
+
flow_paths = {}
|
374
|
+
# setup night ventilation schedule
|
375
|
+
night_vent_sch = create_sch(model, "night ventilation sch", night_vent_starttime, night_vent_endtime, night_vent_startdate_os, night_vent_enddate_os, wknds)
|
376
|
+
|
377
|
+
# return as NA if no exterior operable windows
|
378
|
+
if zones_w_ext_windows.empty?
|
379
|
+
runner.registerAsNotApplicable('No zones with exterior operable windows were found. The model will not be altered')
|
380
|
+
return true
|
381
|
+
end
|
382
|
+
|
383
|
+
# Loop through zones in hash and make natural ventilation objects so the sum equals the user specified target
|
384
|
+
zones_w_ext_windows.each do |zone|
|
385
|
+
zone_ventilation = OpenStudio::Model::ZoneVentilationDesignFlowRate.new(model)
|
386
|
+
zone_ventilation.setName("PathStart_#{zone.name}")
|
387
|
+
zone_ventilation.addToThermalZone(zone)
|
388
|
+
zone_ventilation.setVentilationType('Exhaust') # switched from Natural to use power. Need to set fan properties. Used exhaust so no heat from fan in stream
|
389
|
+
zone_ventilation.setAirChangesperHour(design_night_vent_ach)
|
390
|
+
|
391
|
+
# inputs used for fan power
|
392
|
+
zone_ventilation.setFanPressureRise(fan_pressure_rise)
|
393
|
+
zone_ventilation.setFanTotalEfficiency(efficiency)
|
394
|
+
|
395
|
+
# set schedule from user arg
|
396
|
+
zone_ventilation.setSchedule(night_vent_sch)
|
397
|
+
|
398
|
+
# set ventilation control thresholds
|
399
|
+
zone_ventilation.setMinimumIndoorTemperature(min_indoor_temp)
|
400
|
+
zone_ventilation.setMaximumIndoorTemperature(max_indoor_temp)
|
401
|
+
zone_ventilation.setMinimumOutdoorTemperature(min_outdoor_temp)
|
402
|
+
zone_ventilation.setMaximumOutdoorTemperature(max_outdoor_temp)
|
403
|
+
zone_ventilation.setDeltaTemperature(delta_temp)
|
404
|
+
zone_ventilation.setMaximumWindSpeed(max_wind_speed)
|
405
|
+
nv_zone_list[zone.name.to_s] = zone_ventilation
|
406
|
+
runner.registerInfo("Added natural ventilation object to #{zone.name} of #{design_night_vent_ach} air change rate per hour.")
|
407
|
+
|
408
|
+
# start trace of path adding air mixing objects
|
409
|
+
found_path_end = false
|
410
|
+
flow_paths[zone] = []
|
411
|
+
current_zone = zone
|
412
|
+
zones_used_for_this_path = [current_zone]
|
413
|
+
until found_path_end
|
414
|
+
found_ceiling = false
|
415
|
+
found_wall = false
|
416
|
+
path_objects[current_zone].each do |object|
|
417
|
+
next if zones_used_for_this_path.include?(object[0])
|
418
|
+
next if object[1].to_s != 'RoofCeiling'
|
419
|
+
next if zones_w_ext_windows.include?(object[0])
|
420
|
+
if found_ceiling
|
421
|
+
runner.registerWarning("Found more than one possible airflow path for #{current_zone.name}")
|
422
|
+
else
|
423
|
+
flow_paths[zone] << object[0]
|
424
|
+
current_zone = object[0]
|
425
|
+
zones_used_for_this_path << object[0]
|
426
|
+
found_ceiling = true
|
427
|
+
end
|
428
|
+
end
|
429
|
+
unless found_ceiling
|
430
|
+
path_objects[current_zone].each do |object|
|
431
|
+
next if zones_used_for_this_path.include?(object[0])
|
432
|
+
next if object[1].to_s != 'Wall'
|
433
|
+
next if zones_w_ext_windows.include?(object[0])
|
434
|
+
if found_wall
|
435
|
+
runner.registerWarning("Found more than one possible airflow path for #{current_zone.name}")
|
436
|
+
else
|
437
|
+
flow_paths[zone] << object[0]
|
438
|
+
current_zone = object[0]
|
439
|
+
zones_used_for_this_path << object[0]
|
440
|
+
found_wall = true
|
441
|
+
end
|
442
|
+
end
|
443
|
+
end
|
444
|
+
if !found_ceiling && !found_wall
|
445
|
+
found_path_end = true
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
# add one way air mixing objects along path zones
|
450
|
+
zone_path_string_array = [zone.name]
|
451
|
+
vent_zone = zone
|
452
|
+
source_zone = zone
|
453
|
+
flow_paths[zone].each do |zone|
|
454
|
+
zone_mixing = OpenStudio::Model::ZoneMixing.new(zone)
|
455
|
+
zone_mixing.setName("PathStart_#{vent_zone.name}_#{source_zone.name}")
|
456
|
+
zone_mixing.setSourceZone(source_zone)
|
457
|
+
zone_mixing.setAirChangesperHour(design_night_vent_ach)
|
458
|
+
|
459
|
+
# set min outdoor temp schedule
|
460
|
+
min_outdoor_sch = OpenStudio::Model::ScheduleConstant.new(model)
|
461
|
+
min_outdoor_sch.setValue(min_outdoor_temp)
|
462
|
+
zone_mixing.setMinimumOutdoorTemperatureSchedule(min_outdoor_sch)
|
463
|
+
|
464
|
+
# set schedule from user arg
|
465
|
+
zone_mixing.setSchedule(night_vent_sch)
|
466
|
+
|
467
|
+
# change source zone to what was just target zone
|
468
|
+
zone_path_string_array << zone.name
|
469
|
+
source_zone = zone
|
470
|
+
end
|
471
|
+
runner.registerInfo("Added Zone Mixing Path: #{zone_path_string_array.join(' > ')}")
|
472
|
+
|
473
|
+
# add ach to exhaust zones
|
474
|
+
if !flow_paths[zone].empty?
|
475
|
+
if exhaust_zones.include? flow_paths[zone].last
|
476
|
+
exhaust_zones[flow_paths[zone].last] += design_night_vent_ach
|
477
|
+
else
|
478
|
+
exhaust_zones[flow_paths[zone].last] = design_night_vent_ach
|
479
|
+
end
|
480
|
+
else
|
481
|
+
# extra code if there is no path from entry zone
|
482
|
+
if exhaust_zones.include? zone
|
483
|
+
exhaust_zones[zone] += design_night_vent_ach
|
484
|
+
else
|
485
|
+
exhaust_zones[zone] = design_night_vent_ach
|
486
|
+
runner.registerWarning("#{zone.name} doesn't have path to other zones. Exhaust assumed to be with the same zone as air enters.")
|
487
|
+
end
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
# report how much air (by ach) exhausts to each exhaust zone
|
492
|
+
# when I add an exhaust fan to the top floor I want it to use energy but I don't want to move any additional air.
|
493
|
+
# The air is already being brought into the zone by the zone mixing objects
|
494
|
+
exhaust_zones.each do |zone, ach|
|
495
|
+
runner.registerInfo("Zone Mixing flow rate into #{zone.name} is #{ach} air change per hour. Fan Consumption included with zone ventilation zones.")
|
496
|
+
|
497
|
+
# check for exterior surface area
|
498
|
+
if zone.exteriorSurfaceArea == 0
|
499
|
+
runner.registerWarning("Exhaust Zone #{zone.name} doesn't appear to have any exterior exposure. Review the paths to see that this is the expected result.")
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
# warn if zone multiplier are used
|
504
|
+
non_default_multiplier = []
|
505
|
+
model.getThermalZones.each do |zone|
|
506
|
+
if zone.multiplier > 1
|
507
|
+
non_default_multiplier << zone
|
508
|
+
end
|
509
|
+
end
|
510
|
+
if !non_default_multiplier.empty?
|
511
|
+
runner.registerWarning("This measure is not intended to be use when thermal zones have a non 1 multiplier. #{non_default_multiplier.size} zones in this model have multipliers greater than one. Results are likley invalid.")
|
512
|
+
end
|
513
|
+
|
514
|
+
|
515
|
+
#################### STEP 3: add hybrid ventilation control objects ################
|
516
|
+
|
517
|
+
# TODO: Simple Airflow Control Type Schedule Name set as 1 denote group control
|
518
|
+
# if a zone has more than one windows, group control allows them to be operated simultaneously
|
519
|
+
# if an airloopHVAC controls more than one zone, only one AvailabilityManagerHybridVentilation is allowed for an airloop, group control will
|
520
|
+
# decides the zones controlled by this airloop based on the selected ZoneVentilation object input
|
521
|
+
vent_control_sch = create_sch(model, "ventilation control sch", night_vent_starttime, night_vent_endtime, night_vent_startdate_os, night_vent_enddate_os, wknds, 1, 0)
|
522
|
+
simple_airflow_control_type_sch = OpenStudio::Model::ScheduleConstant.new(model)
|
523
|
+
simple_airflow_control_type_sch.setName("simple airflow control type sch - group control")
|
524
|
+
simple_airflow_control_type_sch.setValue(1)
|
525
|
+
|
526
|
+
# part1: loop through all the airloopHVAC and add hybrid ventilation control
|
527
|
+
model.getAirLoopHVACs.sort.each do |air_loop|
|
528
|
+
max_zone_area = 0
|
529
|
+
nv_zone_with_max_area = nil
|
530
|
+
air_loop.thermalZones.each do |thermal_zone|
|
531
|
+
if max_zone_area < thermal_zone.floorArea && nv_zone_list.key?(thermal_zone.name.to_s)
|
532
|
+
max_zone_area = thermal_zone.floorArea
|
533
|
+
nv_zone_with_max_area = thermal_zone.name.to_s
|
534
|
+
end
|
535
|
+
end
|
536
|
+
next if nv_zone_with_max_area.nil? # if there is no NV zone in this airloop, skip
|
537
|
+
has_hybrid_avail_manager = false
|
538
|
+
air_loop.availabilityManagers.sort.each do |avail_mgr|
|
539
|
+
next if avail_mgr.to_AvailabilityManagerHybridVentilation.empty?
|
540
|
+
if avail_mgr.to_AvailabilityManagerHybridVentilation.is_initialized
|
541
|
+
has_hybrid_avail_manager = true
|
542
|
+
avail_mgr_hybr_vent = avail_mgr.to_AvailabilityManagerHybridVentilation.get
|
543
|
+
avail_mgr_hybr_vent.setVentilationControlModeSchedule(vent_control_sch)
|
544
|
+
avail_mgr_hybr_vent.setControlledZone(model.getThermalZoneByName(nv_zone_with_max_area).get)
|
545
|
+
avail_mgr_hybr_vent.setMinimumOutdoorTemperature(min_outdoor_temp)
|
546
|
+
avail_mgr_hybr_vent.setMaximumOutdoorTemperature(max_outdoor_temp)
|
547
|
+
avail_mgr_hybr_vent.setMaximumWindSpeed(max_wind_speed)
|
548
|
+
avail_mgr_hybr_vent.setSimpleAirflowControlTypeSchedule(simple_airflow_control_type_sch)
|
549
|
+
avail_mgr_hybr_vent.setZoneVentilationObject(nv_zone_list[nv_zone_with_max_area])
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
unless has_hybrid_avail_manager
|
554
|
+
avail_mgr_hybr_vent = OpenStudio::Model::AvailabilityManagerHybridVentilation.new(model)
|
555
|
+
avail_mgr_hybr_vent.setName(air_loop.name.to_s + " HybridVentilation AvailabilityManager")
|
556
|
+
avail_mgr_hybr_vent.setVentilationControlModeSchedule(vent_control_sch)
|
557
|
+
avail_mgr_hybr_vent.setControlledZone(model.getThermalZoneByName(nv_zone_with_max_area).get)
|
558
|
+
avail_mgr_hybr_vent.setMinimumOutdoorTemperature(min_outdoor_temp)
|
559
|
+
avail_mgr_hybr_vent.setMaximumOutdoorTemperature(max_outdoor_temp)
|
560
|
+
avail_mgr_hybr_vent.setMaximumWindSpeed(max_wind_speed)
|
561
|
+
avail_mgr_hybr_vent.setSimpleAirflowControlTypeSchedule(simple_airflow_control_type_sch)
|
562
|
+
avail_mgr_hybr_vent.setZoneVentilationObject(nv_zone_list[nv_zone_with_max_area])
|
563
|
+
air_loop.addAvailabilityManager(avail_mgr_hybr_vent)
|
564
|
+
end
|
565
|
+
|
566
|
+
# remove thermal zones in this airloop from the nv_zone_list hash
|
567
|
+
air_loop.thermalZones.each do |thermal_zone|
|
568
|
+
if nv_zone_list.key?(thermal_zone.name.to_s)
|
569
|
+
nv_zone_list.delete(thermal_zone.name.to_s)
|
570
|
+
end
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
574
|
+
# part2: loop through all spaces, add hybrid vent control to zones that are not connected to airloopHVAC but uses zonal equipment
|
575
|
+
nv_zone_list.each do |zone_name, nv_obj|
|
576
|
+
avail_mgr_hybr_vent = OpenStudio::Model::AvailabilityManagerHybridVentilation.new(model)
|
577
|
+
avail_mgr_hybr_vent.setName(zone_name + " HybridVentilation AvailabilityManager")
|
578
|
+
avail_mgr_hybr_vent.setVentilationControlModeSchedule(vent_control_sch)
|
579
|
+
avail_mgr_hybr_vent.setControlledZone(model.getThermalZoneByName(zone_name).get)
|
580
|
+
avail_mgr_hybr_vent.setMinimumOutdoorTemperature(min_outdoor_temp)
|
581
|
+
avail_mgr_hybr_vent.setMaximumOutdoorTemperature(max_outdoor_temp)
|
582
|
+
avail_mgr_hybr_vent.setMaximumWindSpeed(max_wind_speed)
|
583
|
+
avail_mgr_hybr_vent.setSimpleAirflowControlTypeSchedule(simple_airflow_control_type_sch)
|
584
|
+
avail_mgr_hybr_vent.setZoneVentilationObject(nv_obj)
|
585
|
+
end
|
586
|
+
|
587
|
+
# echo added AvailabilityManagerHybridVentilation object number to the user
|
588
|
+
runner.registerInfo("A total of #{model.getAvailabilityManagerHybridVentilations.size} AvailabilityManagerHybridVentilations were added.")
|
589
|
+
|
590
|
+
# report final condition of model
|
591
|
+
runner.registerFinalCondition("The building finished with #{model.getZoneVentilationDesignFlowRates.size} zone ventilation design flow rate objects and #{model.getZoneMixings.size} zone mixing objects.")
|
592
|
+
|
593
|
+
# adding useful output variables for diagnostics
|
594
|
+
OpenStudio::Model::OutputVariable.new('Zone Mixing Current Density Air Volume Flow Rate', model)
|
595
|
+
OpenStudio::Model::OutputVariable.new('Zone Ventilation Current Density Volume Flow Rate', model)
|
596
|
+
OpenStudio::Model::OutputVariable.new('Zone Ventilation Fan Electric Energy', model)
|
597
|
+
OpenStudio::Model::OutputVariable.new('Zone Outdoor Air Drybulb Temperature', model)
|
598
|
+
|
599
|
+
return true
|
600
|
+
end
|
601
|
+
end
|
602
|
+
|
603
|
+
# register the measure to be used by the application
|
604
|
+
AddFanAssistNightVentilationWithHybridControl.new.registerWithApplication
|