openstudio-extension 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +14 -0
- data/LICENSE.md +1 -1
- data/README.md +2 -0
- data/lib/openstudio/extension/runner.rb +12 -8
- data/lib/openstudio/extension/runner_config.rb +33 -6
- data/lib/openstudio/extension/version.rb +1 -1
- data/openstudio-extension.gemspec +6 -6
- metadata +15 -66
- data/lib/openstudio/extension/core/CreateResults.rb +0 -1033
- data/lib/openstudio/extension/core/check_air_sys_temps.rb +0 -160
- data/lib/openstudio/extension/core/check_calibration.rb +0 -125
- data/lib/openstudio/extension/core/check_cond_zns.rb +0 -54
- data/lib/openstudio/extension/core/check_domestic_hot_water.rb +0 -304
- data/lib/openstudio/extension/core/check_envelope_conductance.rb +0 -423
- data/lib/openstudio/extension/core/check_eui_by_end_use.rb +0 -132
- data/lib/openstudio/extension/core/check_eui_reasonableness.rb +0 -105
- data/lib/openstudio/extension/core/check_fan_pwr.rb +0 -68
- data/lib/openstudio/extension/core/check_internal_loads.rb +0 -363
- data/lib/openstudio/extension/core/check_mech_sys_capacity.rb +0 -196
- data/lib/openstudio/extension/core/check_mech_sys_efficiency.rb +0 -296
- data/lib/openstudio/extension/core/check_mech_sys_part_load_eff.rb +0 -434
- data/lib/openstudio/extension/core/check_mech_sys_type.rb +0 -109
- data/lib/openstudio/extension/core/check_part_loads.rb +0 -421
- data/lib/openstudio/extension/core/check_placeholder.rb +0 -45
- data/lib/openstudio/extension/core/check_plant_cap.rb +0 -93
- data/lib/openstudio/extension/core/check_plant_temps.rb +0 -129
- data/lib/openstudio/extension/core/check_plenum_loads.rb +0 -57
- data/lib/openstudio/extension/core/check_pump_pwr.rb +0 -78
- data/lib/openstudio/extension/core/check_sch_coord.rb +0 -211
- data/lib/openstudio/extension/core/check_schedules.rb +0 -281
- data/lib/openstudio/extension/core/check_simultaneous_heating_and_cooling.rb +0 -128
- data/lib/openstudio/extension/core/check_supply_air_and_thermostat_temp_difference.rb +0 -118
- data/lib/openstudio/extension/core/check_weather_files.rb +0 -102
- data/lib/openstudio/extension/core/deer_vintages.rb +0 -281
- data/lib/openstudio/extension/core/os_lib_aedg_measures.rb +0 -461
- data/lib/openstudio/extension/core/os_lib_constructions.rb +0 -353
- data/lib/openstudio/extension/core/os_lib_geometry.rb +0 -1169
- data/lib/openstudio/extension/core/os_lib_helper_methods.rb +0 -383
- data/lib/openstudio/extension/core/os_lib_hvac.rb +0 -2163
- data/lib/openstudio/extension/core/os_lib_lighting_and_equipment.rb +0 -184
- data/lib/openstudio/extension/core/os_lib_model_generation.rb +0 -3584
- data/lib/openstudio/extension/core/os_lib_model_simplification.rb +0 -1019
- data/lib/openstudio/extension/core/os_lib_outdoorair_and_infiltration.rb +0 -135
- data/lib/openstudio/extension/core/os_lib_reporting_qaqc.rb +0 -170
- data/lib/openstudio/extension/core/os_lib_schedules.rb +0 -933
@@ -1,281 +0,0 @@
|
|
1
|
-
# *******************************************************************************
|
2
|
-
# OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC.
|
3
|
-
# See also https://openstudio.net/license
|
4
|
-
# *******************************************************************************
|
5
|
-
|
6
|
-
module OsLib_QAQC
|
7
|
-
# include any general notes about QAQC method here
|
8
|
-
|
9
|
-
# checks the number of unmet hours in the model
|
10
|
-
def check_schedules(category, target_standard, min_pass, max_pass, name_only = false)
|
11
|
-
# summary of the check
|
12
|
-
check_elems = OpenStudio::AttributeVector.new
|
13
|
-
check_elems << OpenStudio::Attribute.new('name', 'Schedules')
|
14
|
-
check_elems << OpenStudio::Attribute.new('category', category)
|
15
|
-
check_elems << OpenStudio::Attribute.new('description', 'Check schedules for lighting, ventilation, occupant density, plug loads, and equipment based on DOE reference building schedules in terms of full load hours per year.')
|
16
|
-
|
17
|
-
# stop here if only name is requested this is used to populate display name for arguments
|
18
|
-
if name_only == true
|
19
|
-
results = []
|
20
|
-
check_elems.each do |elem|
|
21
|
-
results << elem.valueAsString
|
22
|
-
end
|
23
|
-
return results
|
24
|
-
end
|
25
|
-
|
26
|
-
# Versions of OpenStudio greater than 2.4.0 use a modified version of
|
27
|
-
# openstudio-standards with different method calls. These methods
|
28
|
-
# require a "Standard" object instead of the standard being passed into method calls.
|
29
|
-
# This Standard object is used throughout the QAQC check.
|
30
|
-
if OpenStudio::VersionString.new(OpenStudio.openStudioVersion) < OpenStudio::VersionString.new('2.4.3')
|
31
|
-
use_old_gem_code = true
|
32
|
-
else
|
33
|
-
use_old_gem_code = false
|
34
|
-
std = Standard.build(target_standard)
|
35
|
-
end
|
36
|
-
|
37
|
-
begin
|
38
|
-
# loop through all space types used in the model
|
39
|
-
@model.getSpaceTypes.each do |space_type|
|
40
|
-
next if space_type.floorArea <= 0
|
41
|
-
|
42
|
-
# load in standard info for this space type
|
43
|
-
if use_old_gem_code
|
44
|
-
data = space_type.get_standards_data(target_standard)
|
45
|
-
else
|
46
|
-
data = std.space_type_get_standards_data(space_type)
|
47
|
-
end
|
48
|
-
|
49
|
-
if data.nil? || data.empty?
|
50
|
-
|
51
|
-
# skip if all spaces using this space type are plenums
|
52
|
-
all_spaces_plenums = true
|
53
|
-
space_type.spaces.each do |space|
|
54
|
-
if use_old_gem_code
|
55
|
-
if !space.plenum?
|
56
|
-
all_spaces_plenums = false
|
57
|
-
next
|
58
|
-
end
|
59
|
-
else
|
60
|
-
if !std.space_plenum?(space)
|
61
|
-
all_spaces_plenums = false
|
62
|
-
next
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
if !all_spaces_plenums
|
68
|
-
check_elems << OpenStudio::Attribute.new('flag', "Unexpected standards type for #{space_type.name}, can't validate schedules.")
|
69
|
-
end
|
70
|
-
|
71
|
-
next
|
72
|
-
end
|
73
|
-
|
74
|
-
# temp model to hold schedules to check
|
75
|
-
model_temp = OpenStudio::Model::Model.new
|
76
|
-
|
77
|
-
# check lighting schedules
|
78
|
-
data['lighting_per_area'].nil? ? (target_ip = 0.0) : (target_ip = data['lighting_per_area'])
|
79
|
-
if target_ip.to_f > 0
|
80
|
-
if use_old_gem_code
|
81
|
-
schedule_target = model_temp.add_schedule(data['lighting_schedule'])
|
82
|
-
else
|
83
|
-
schedule_target = std.model_add_schedule(model_temp, data['lighting_schedule'])
|
84
|
-
end
|
85
|
-
if !schedule_target
|
86
|
-
check_elems << OpenStudio::Attribute.new('flag', "Didn't find schedule named #{data['lighting_schedule']} in standards json.")
|
87
|
-
else
|
88
|
-
# loop through and test individual load instances
|
89
|
-
if use_old_gem_code
|
90
|
-
target_hrs = schedule_target.annual_equivalent_full_load_hrs
|
91
|
-
else
|
92
|
-
target_hrs = std.schedule_ruleset_annual_equivalent_full_load_hrs(schedule_target)
|
93
|
-
end
|
94
|
-
space_type.lights.each do |load_inst|
|
95
|
-
inst_sch_check = generate_load_insc_sch_check_attribute(target_hrs, load_inst, space_type, check_elems, min_pass, max_pass)
|
96
|
-
if inst_sch_check then check_elems << inst_sch_check end
|
97
|
-
end
|
98
|
-
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
# check electric equipment schedules
|
103
|
-
data['electric_equipment_per_area'].nil? ? (target_ip = 0.0) : (target_ip = data['electric_equipment_per_area'])
|
104
|
-
if target_ip.to_f > 0
|
105
|
-
if use_old_gem_code
|
106
|
-
schedule_target = model_temp.add_schedule(data['electric_equipment_schedule'])
|
107
|
-
else
|
108
|
-
schedule_target = std.model_add_schedule(model_temp, data['electric_equipment_schedule'])
|
109
|
-
end
|
110
|
-
if !schedule_target
|
111
|
-
check_elems << OpenStudio::Attribute.new('flag', "Didn't find schedule named #{data['electric_equipment_schedule']} in standards json.")
|
112
|
-
else
|
113
|
-
# loop through and test individual load instances
|
114
|
-
if use_old_gem_code
|
115
|
-
target_hrs = schedule_target.annual_equivalent_full_load_hrs
|
116
|
-
else
|
117
|
-
target_hrs = std.schedule_ruleset_annual_equivalent_full_load_hrs(schedule_target)
|
118
|
-
end
|
119
|
-
|
120
|
-
space_type.electricEquipment.each do |load_inst|
|
121
|
-
inst_sch_check = generate_load_insc_sch_check_attribute(target_hrs, load_inst, space_type, check_elems, min_pass, max_pass)
|
122
|
-
if inst_sch_check then check_elems << inst_sch_check end
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
# check gas equipment schedules
|
128
|
-
# todo - update measure test to with space type to check this
|
129
|
-
data['gas_equipment_per_area'].nil? ? (target_ip = 0.0) : (target_ip = data['gas_equipment_per_area'])
|
130
|
-
if target_ip.to_f > 0
|
131
|
-
if use_old_gem_code
|
132
|
-
schedule_target = model_temp.add_schedule(data['gas_equipment_schedule'])
|
133
|
-
else
|
134
|
-
schedule_target = std.model_add_schedule(model_temp, data['gas_equipment_schedule'])
|
135
|
-
end
|
136
|
-
if !schedule_target
|
137
|
-
check_elems << OpenStudio::Attribute.new('flag', "Didn't find schedule named #{data['gas_equipment_schedule']} in standards json.")
|
138
|
-
else
|
139
|
-
# loop through and test individual load instances
|
140
|
-
if use_old_gem_code
|
141
|
-
target_hrs = schedule_target.annual_equivalent_full_load_hrs
|
142
|
-
else
|
143
|
-
target_hrs = std.schedule_ruleset_annual_equivalent_full_load_hrs(schedule_target)
|
144
|
-
end
|
145
|
-
space_type.gasEquipment.each do |load_inst|
|
146
|
-
inst_sch_check = generate_load_insc_sch_check_attribute(target_hrs, load_inst, space_type, check_elems, min_pass, max_pass)
|
147
|
-
if inst_sch_check then check_elems << inst_sch_check end
|
148
|
-
end
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
# check occupancy schedules
|
153
|
-
data['occupancy_per_area'].nil? ? (target_ip = 0.0) : (target_ip = data['occupancy_per_area'])
|
154
|
-
if target_ip.to_f > 0
|
155
|
-
if use_old_gem_code
|
156
|
-
schedule_target = model_temp.add_schedule(data['occupancy_schedule'])
|
157
|
-
else
|
158
|
-
schedule_target = std.model_add_schedule(model_temp, data['occupancy_schedule'])
|
159
|
-
end
|
160
|
-
if !schedule_target
|
161
|
-
check_elems << OpenStudio::Attribute.new('flag', "Didn't find schedule named #{data['occupancy_schedule']} in standards json.")
|
162
|
-
else
|
163
|
-
# loop through and test individual load instances
|
164
|
-
if use_old_gem_code
|
165
|
-
target_hrs = schedule_target.annual_equivalent_full_load_hrs
|
166
|
-
else
|
167
|
-
target_hrs = std.schedule_ruleset_annual_equivalent_full_load_hrs(schedule_target)
|
168
|
-
end
|
169
|
-
space_type.people.each do |load_inst|
|
170
|
-
inst_sch_check = generate_load_insc_sch_check_attribute(target_hrs, load_inst, space_type, check_elems, min_pass, max_pass)
|
171
|
-
if inst_sch_check then check_elems << inst_sch_check end
|
172
|
-
end
|
173
|
-
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
# TODO: - check ventilation schedules
|
178
|
-
# if objects are in the model should they just be always on schedule, or have a 8760 annual equiv value
|
179
|
-
# oa_schedule should not exist, or if it does shoudl be always on or have 8760 annual equiv value
|
180
|
-
if space_type.designSpecificationOutdoorAir.is_initialized
|
181
|
-
oa = space_type.designSpecificationOutdoorAir.get
|
182
|
-
if oa.outdoorAirFlowRateFractionSchedule.is_initialized
|
183
|
-
# TODO: - update measure test to check this
|
184
|
-
target_hrs = 8760
|
185
|
-
inst_sch_check = generate_load_insc_sch_check_attribute(target_hrs, oa, space_type, check_elems, min_pass, max_pass)
|
186
|
-
if inst_sch_check then check_elems << inst_sch_check end
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
# notes
|
191
|
-
# current logic only looks at 8760 values and not design days
|
192
|
-
# when multiple instances of a type currently check every schedule by itself. In future could do weighted avg. merge
|
193
|
-
# not looking at infiltration schedules
|
194
|
-
# not looking at luminaires
|
195
|
-
# not looking at space loads, only loads at space type
|
196
|
-
# only checking schedules where standard shows non zero load value
|
197
|
-
# model load for space type where standards doesn't have one wont throw flag about mis-matched schedules
|
198
|
-
end
|
199
|
-
|
200
|
-
# warn if there are spaces in model that don't use space type unless they appear to be plenums
|
201
|
-
@model.getSpaces.each do |space|
|
202
|
-
if use_old_gem_code
|
203
|
-
next if space.plenum?
|
204
|
-
else
|
205
|
-
next if std.space_plenum?(space)
|
206
|
-
end
|
207
|
-
if !space.spaceType.is_initialized
|
208
|
-
check_elems << OpenStudio::Attribute.new('flag', "#{space.name} doesn't have a space type assigned, can't validate schedules.")
|
209
|
-
end
|
210
|
-
end
|
211
|
-
rescue StandardError => e
|
212
|
-
# brief description of ruby error
|
213
|
-
check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
|
214
|
-
|
215
|
-
# backtrace of ruby error for diagnostic use
|
216
|
-
if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
|
217
|
-
end
|
218
|
-
|
219
|
-
# add check_elms to new attribute
|
220
|
-
check_elem = OpenStudio::Attribute.new('check', check_elems)
|
221
|
-
|
222
|
-
return check_elem
|
223
|
-
# note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb
|
224
|
-
end
|
225
|
-
|
226
|
-
# code for each load instance for different load types will pass through here
|
227
|
-
# will return nill or a single attribute
|
228
|
-
def generate_load_insc_sch_check_attribute(target_hrs, load_inst, space_type, check_elems, min_pass, max_pass)
|
229
|
-
# Versions of OpenStudio greater than 2.4.0 use a modified version of
|
230
|
-
# openstudio-standards with different method calls. These methods
|
231
|
-
# require a "Standard" object instead of the standard being passed into method calls.
|
232
|
-
# This Standard object is used throughout the QAQC check.
|
233
|
-
if OpenStudio::VersionString.new(OpenStudio.openStudioVersion) < OpenStudio::VersionString.new('2.4.3')
|
234
|
-
use_old_gem_code = true
|
235
|
-
else
|
236
|
-
use_old_gem_code = false
|
237
|
-
std = Standard.build('90.1-2013')
|
238
|
-
end
|
239
|
-
|
240
|
-
schedule_inst = nil
|
241
|
-
inst_hrs = nil
|
242
|
-
|
243
|
-
# get schedule
|
244
|
-
if (load_inst.class.to_s == 'OpenStudio::Model::People') && load_inst.numberofPeopleSchedule.is_initialized
|
245
|
-
schedule_inst = load_inst.numberofPeopleSchedule.get
|
246
|
-
elsif (load_inst.class.to_s == 'OpenStudio::Model::DesignSpecificationOutdoorAir') && load_inst.outdoorAirFlowRateFractionSchedule.is_initialized
|
247
|
-
schedule_inst = load_inst.outdoorAirFlowRateFractionSchedule .get
|
248
|
-
elsif load_inst.schedule.is_initialized
|
249
|
-
schedule_inst = load_inst.schedule.get
|
250
|
-
else
|
251
|
-
return OpenStudio::Attribute.new('flag', "#{load_inst.name} in #{space_type.name} doesn't have a schedule assigned.")
|
252
|
-
end
|
253
|
-
|
254
|
-
# get annual equiv for model schedule
|
255
|
-
if schedule_inst.to_ScheduleRuleset.is_initialized
|
256
|
-
if use_old_gem_code
|
257
|
-
inst_hrs = schedule_inst.to_ScheduleRuleset.get.annual_equivalent_full_load_hrs
|
258
|
-
else
|
259
|
-
inst_hrs = std.schedule_ruleset_annual_equivalent_full_load_hrs(schedule_inst.to_ScheduleRuleset.get)
|
260
|
-
end
|
261
|
-
elsif schedule_inst.to_ScheduleConstant.is_initialized
|
262
|
-
if use_old_gem_code
|
263
|
-
inst_hrs = schedule_inst.to_ScheduleConstant.get.annual_equivalent_full_load_hrs
|
264
|
-
else
|
265
|
-
inst_hrs = std.schedule_constant_annual_equivalent_full_load_hrs(schedule_inst.to_ScheduleConstant.get)
|
266
|
-
end
|
267
|
-
else
|
268
|
-
return OpenStudio::Attribute.new('flag', "#{schedule_inst.name} isn't a Ruleset or Constant schedule. Can't calculate annual equivalent full load hours.")
|
269
|
-
end
|
270
|
-
|
271
|
-
# check instance against target
|
272
|
-
if inst_hrs < target_hrs * (1.0 - min_pass)
|
273
|
-
return OpenStudio::Attribute.new('flag', "#{inst_hrs.round} annual equivalent full load hours for #{schedule_inst.name} in #{space_type.name} is more than #{min_pass * 100} (%) below the typical value of #{target_hrs.round} hours from the DOE Prototype building.")
|
274
|
-
elsif inst_hrs > target_hrs * (1.0 + max_pass)
|
275
|
-
return OpenStudio::Attribute.new('flag', "#{inst_hrs.round} annual equivalent full load hours for #{schedule_inst.name} in #{space_type.name} is more than #{max_pass * 100} (%) above the typical value of #{target_hrs.round} hours DOE Prototype building.")
|
276
|
-
end
|
277
|
-
|
278
|
-
# will get to this if no flag was thrown
|
279
|
-
return false
|
280
|
-
end
|
281
|
-
end
|
@@ -1,128 +0,0 @@
|
|
1
|
-
# *******************************************************************************
|
2
|
-
# OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC.
|
3
|
-
# See also https://openstudio.net/license
|
4
|
-
# *******************************************************************************
|
5
|
-
|
6
|
-
module OsLib_QAQC
|
7
|
-
# include any general notes about QAQC method here
|
8
|
-
|
9
|
-
# checks the number of unmet hours in the model
|
10
|
-
def check_simultaneous_heating_and_cooling(category, max_pass, name_only = false)
|
11
|
-
# summary of the check
|
12
|
-
check_elems = OpenStudio::AttributeVector.new
|
13
|
-
check_elems << OpenStudio::Attribute.new('name', 'Simultaneous Heating and Cooling')
|
14
|
-
check_elems << OpenStudio::Attribute.new('category', category)
|
15
|
-
check_elems << OpenStudio::Attribute.new('description', 'Check for simultaneous heating and cooling by looping through all Single Duct VAV Reheat Air Terminals and analyzing hourly data when there is a cooling load. ')
|
16
|
-
|
17
|
-
# stop here if only name is requested this is used to populate display name for arguments
|
18
|
-
if name_only == true
|
19
|
-
results = []
|
20
|
-
check_elems.each do |elem|
|
21
|
-
results << elem.valueAsString
|
22
|
-
end
|
23
|
-
return results
|
24
|
-
end
|
25
|
-
|
26
|
-
begin
|
27
|
-
# get the weather file run period (as opposed to design day run period)
|
28
|
-
ann_env_pd = nil
|
29
|
-
@sql.availableEnvPeriods.each do |env_pd|
|
30
|
-
env_type = @sql.environmentType(env_pd)
|
31
|
-
if env_type.is_initialized
|
32
|
-
if env_type.get == OpenStudio::EnvironmentType.new('WeatherRunPeriod')
|
33
|
-
ann_env_pd = env_pd
|
34
|
-
break
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
# only try to get the annual timeseries if an annual simulation was run
|
40
|
-
if ann_env_pd.nil?
|
41
|
-
check_elems << OpenStudio::Attribute.new('flag', 'Cannot find the annual simulation run period, cannot determine simultaneous heating and cooling.')
|
42
|
-
return check_elem
|
43
|
-
end
|
44
|
-
|
45
|
-
# For each VAV reheat terminal, calculate
|
46
|
-
# the annual total % reheat hours.
|
47
|
-
@model.getAirTerminalSingleDuctVAVReheats.each do |term|
|
48
|
-
# Reheat coil heating rate
|
49
|
-
rht_coil = term.reheatCoil
|
50
|
-
key_value = rht_coil.name.get.to_s.upcase # must be in all caps.
|
51
|
-
time_step = 'Hourly' # "Zone Timestep", "Hourly", "HVAC System Timestep"
|
52
|
-
variable_name = 'Heating Coil Heating Rate'
|
53
|
-
variable_name_alt = 'Heating Coil Air Heating Rate'
|
54
|
-
rht_rate_ts = @sql.timeSeries(ann_env_pd, time_step, variable_name, key_value) # key value would go at the end if we used it.
|
55
|
-
|
56
|
-
# try and alternate variable name
|
57
|
-
if rht_rate_ts.empty?
|
58
|
-
rht_rate_ts = @sql.timeSeries(ann_env_pd, time_step, variable_name_alt, key_value) # key value would go at the end if we used it.
|
59
|
-
end
|
60
|
-
|
61
|
-
if rht_rate_ts.empty?
|
62
|
-
check_elems << OpenStudio::Attribute.new('flag', "Heating Coil (Air) Heating Rate Timeseries not found for #{key_value}.")
|
63
|
-
else
|
64
|
-
|
65
|
-
rht_rate_ts = rht_rate_ts.get.values
|
66
|
-
# Put timeseries into array
|
67
|
-
rht_rate_vals = []
|
68
|
-
for i in 0..(rht_rate_ts.size - 1)
|
69
|
-
rht_rate_vals << rht_rate_ts[i]
|
70
|
-
end
|
71
|
-
|
72
|
-
# Zone Air Terminal Sensible Heating Rate
|
73
|
-
key_value = "ADU #{term.name.get.to_s.upcase}" # must be in all caps.
|
74
|
-
time_step = 'Hourly' # "Zone Timestep", "Hourly", "HVAC System Timestep"
|
75
|
-
variable_name = 'Zone Air Terminal Sensible Cooling Rate'
|
76
|
-
clg_rate_ts = @sql.timeSeries(ann_env_pd, time_step, variable_name, key_value) # key value would go at the end if we used it.
|
77
|
-
if clg_rate_ts.empty?
|
78
|
-
check_elems << OpenStudio::Attribute.new('flag', "Zone Air Terminal Sensible Cooling Rate Timeseries not found for #{key_value}.")
|
79
|
-
else
|
80
|
-
|
81
|
-
clg_rate_ts = clg_rate_ts.get.values
|
82
|
-
# Put timeseries into array
|
83
|
-
clg_rate_vals = []
|
84
|
-
for i in 0..(clg_rate_ts.size - 1)
|
85
|
-
clg_rate_vals << clg_rate_ts[i]
|
86
|
-
end
|
87
|
-
|
88
|
-
# Loop through each timestep and calculate the hourly
|
89
|
-
# % reheat value.
|
90
|
-
ann_rht_hrs = 0
|
91
|
-
ann_clg_hrs = 0
|
92
|
-
ann_pcts = []
|
93
|
-
rht_rate_vals.zip(clg_rate_vals).each do |rht_w, clg_w|
|
94
|
-
# Skip hours with no cooling (in heating mode)
|
95
|
-
next if clg_w == 0
|
96
|
-
pct_overcool_rht = rht_w / (rht_w + clg_w)
|
97
|
-
ann_rht_hrs += pct_overcool_rht # implied * 1hr b/c hrly results
|
98
|
-
ann_clg_hrs += 1
|
99
|
-
ann_pcts << pct_overcool_rht.round(3)
|
100
|
-
end
|
101
|
-
|
102
|
-
# Calculate annual % reheat hours
|
103
|
-
ann_pct_reheat = ((ann_rht_hrs / ann_clg_hrs) * 100).round(1)
|
104
|
-
|
105
|
-
# Compare to limit
|
106
|
-
if ann_pct_reheat > max_pass * 100.0
|
107
|
-
check_elems << OpenStudio::Attribute.new('flag', "#{term.name} has #{ann_pct_reheat}% overcool-reheat, which is greater than the limit of #{max_pass * 100.0}%. This terminal is in cooling mode for #{ann_clg_hrs} hours of the year.")
|
108
|
-
end
|
109
|
-
|
110
|
-
end
|
111
|
-
|
112
|
-
end
|
113
|
-
end
|
114
|
-
rescue StandardError => e
|
115
|
-
# brief description of ruby error
|
116
|
-
check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
|
117
|
-
|
118
|
-
# backtrace of ruby error for diagnostic use
|
119
|
-
if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
|
120
|
-
end
|
121
|
-
|
122
|
-
# add check_elms to new attribute
|
123
|
-
check_elem = OpenStudio::Attribute.new('check', check_elems)
|
124
|
-
|
125
|
-
return check_elem
|
126
|
-
# note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb
|
127
|
-
end
|
128
|
-
end
|
@@ -1,118 +0,0 @@
|
|
1
|
-
# *******************************************************************************
|
2
|
-
# OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC.
|
3
|
-
# See also https://openstudio.net/license
|
4
|
-
# *******************************************************************************
|
5
|
-
|
6
|
-
module OsLib_QAQC
|
7
|
-
# include any general notes about QAQC method here
|
8
|
-
|
9
|
-
# checks the number of unmet hours in the model
|
10
|
-
def check_supply_air_and_thermostat_temp_difference(category, target_standard, max_delta, name_only = false)
|
11
|
-
# G3.1.2.9 requires a 20 degree F delta between supply air temperature and zone temperature.
|
12
|
-
target_clg_delta = 20.0
|
13
|
-
|
14
|
-
# summary of the check
|
15
|
-
check_elems = OpenStudio::AttributeVector.new
|
16
|
-
check_elems << OpenStudio::Attribute.new('name', 'Supply and Zone Air Temperature')
|
17
|
-
check_elems << OpenStudio::Attribute.new('category', category)
|
18
|
-
if @utility_name.nil?
|
19
|
-
check_elems << OpenStudio::Attribute.new('description', "Check if fans modeled to ASHRAE 90.1 2013 Section G3.1.2.9 requirements. Compare the supply air temperature for each thermal zone against the thermostat setpoints. Throw flag if temperature difference excedes threshold of #{target_clg_delta}F plus the selected tolerance.")
|
20
|
-
else
|
21
|
-
check_elems << OpenStudio::Attribute.new('description', "Check if fans modeled to ASHRAE 90.1 2013 Section G3.1.2.9 requirements. Compare the supply air temperature for each thermal zone against the thermostat setpoints. Throw flag if temperature difference excedes threshold set by #{@utility_name}.")
|
22
|
-
end
|
23
|
-
|
24
|
-
# stop here if only name is requested this is used to populate display name for arguments
|
25
|
-
if name_only == true
|
26
|
-
results = []
|
27
|
-
check_elems.each do |elem|
|
28
|
-
results << elem.valueAsString
|
29
|
-
end
|
30
|
-
return results
|
31
|
-
end
|
32
|
-
|
33
|
-
# Versions of OpenStudio greater than 2.4.0 use a modified version of
|
34
|
-
# openstudio-standards with different method calls. These methods
|
35
|
-
# require a "Standard" object instead of the standard being passed into method calls.
|
36
|
-
# This Standard object is used throughout the QAQC check.
|
37
|
-
if OpenStudio::VersionString.new(OpenStudio.openStudioVersion) < OpenStudio::VersionString.new('2.4.3')
|
38
|
-
use_old_gem_code = true
|
39
|
-
else
|
40
|
-
use_old_gem_code = false
|
41
|
-
std = Standard.build(target_standard)
|
42
|
-
end
|
43
|
-
|
44
|
-
begin
|
45
|
-
# loop through thermal zones
|
46
|
-
@model.getThermalZones.sort.each do |thermal_zone|
|
47
|
-
model_clg_min = nil
|
48
|
-
|
49
|
-
# populate thermostat ranges
|
50
|
-
if thermal_zone.thermostatSetpointDualSetpoint.is_initialized
|
51
|
-
|
52
|
-
thermostat = thermal_zone.thermostatSetpointDualSetpoint.get
|
53
|
-
if thermostat.coolingSetpointTemperatureSchedule.is_initialized
|
54
|
-
|
55
|
-
clg_sch = thermostat.coolingSetpointTemperatureSchedule.get
|
56
|
-
schedule_values = nil
|
57
|
-
if clg_sch.to_ScheduleRuleset.is_initialized
|
58
|
-
if use_old_gem_code
|
59
|
-
schedule_values = clg_sch.to_ScheduleRuleset.get.annual_min_max_value
|
60
|
-
else
|
61
|
-
schedule_values = std.schedule_ruleset_annual_min_max_value(clg_sch.to_ScheduleRuleset.get)
|
62
|
-
end
|
63
|
-
elsif clg_sch.to_ScheduleConstant.is_initialized
|
64
|
-
if use_old_gem_code
|
65
|
-
schedule_values = clg_sch.to_ScheduleConstant.get.annual_min_max_value
|
66
|
-
else
|
67
|
-
schedule_values = std.schedule_constant_annual_min_max_value(clg_sch.to_ScheduleConstant.get)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
unless schedule_values.nil?
|
72
|
-
model_clg_min = schedule_values['min']
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
else
|
77
|
-
# go to next zone if not conditioned
|
78
|
-
next
|
79
|
-
|
80
|
-
end
|
81
|
-
|
82
|
-
# flag if there is setpoint schedule can't be inspected (isn't ruleset)
|
83
|
-
if model_clg_min.nil?
|
84
|
-
check_elems << OpenStudio::Attribute.new('flag', "Can't inspect thermostat schedules for #{thermal_zone.name}")
|
85
|
-
else
|
86
|
-
|
87
|
-
# get supply air temps from thermal zone sizing
|
88
|
-
sizing_zone = thermal_zone.sizingZone
|
89
|
-
clg_supply_air_temp = sizing_zone.zoneCoolingDesignSupplyAirTemperature
|
90
|
-
|
91
|
-
# convert model values to IP
|
92
|
-
model_clg_min_ip = OpenStudio.convert(model_clg_min, 'C', 'F').get
|
93
|
-
clg_supply_air_temp_ip = OpenStudio.convert(clg_supply_air_temp, 'C', 'F').get
|
94
|
-
|
95
|
-
# check supply air against zone temperature (only check against min setpoint, assume max is night setback)
|
96
|
-
if model_clg_min_ip - clg_supply_air_temp_ip > target_clg_delta + max_delta
|
97
|
-
check_elems << OpenStudio::Attribute.new('flag', "For #{thermal_zone.name} the delta temp between the cooling supply air temp of #{clg_supply_air_temp_ip.round(2)} (F) and the minimum thermostat cooling temp of #{model_clg_min_ip.round(2)} (F) is more than #{max_delta} (F) larger than the expected delta of #{target_clg_delta} (F)")
|
98
|
-
elsif model_clg_min_ip - clg_supply_air_temp_ip < target_clg_delta - max_delta
|
99
|
-
check_elems << OpenStudio::Attribute.new('flag', "For #{thermal_zone.name} the delta temp between the cooling supply air temp of #{clg_supply_air_temp_ip.round(2)} (F) and the minimum thermostat cooling temp of #{model_clg_min_ip.round(2)} (F) is more than #{max_delta} (F) smaller than the expected delta of #{target_clg_delta} (F)")
|
100
|
-
end
|
101
|
-
|
102
|
-
end
|
103
|
-
end
|
104
|
-
rescue StandardError => e
|
105
|
-
# brief description of ruby error
|
106
|
-
check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
|
107
|
-
|
108
|
-
# backtrace of ruby error for diagnostic use
|
109
|
-
if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
|
110
|
-
end
|
111
|
-
|
112
|
-
# add check_elms to new attribute
|
113
|
-
check_elem = OpenStudio::Attribute.new('check', check_elems)
|
114
|
-
|
115
|
-
return check_elem
|
116
|
-
# note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb
|
117
|
-
end
|
118
|
-
end
|
@@ -1,102 +0,0 @@
|
|
1
|
-
# *******************************************************************************
|
2
|
-
# OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC.
|
3
|
-
# See also https://openstudio.net/license
|
4
|
-
# *******************************************************************************
|
5
|
-
|
6
|
-
module OsLib_QAQC
|
7
|
-
# include any general notes about QAQC method here
|
8
|
-
|
9
|
-
# checks the number of unmet hours in the model
|
10
|
-
def check_weather_files(category, options, name_only = false)
|
11
|
-
# summary of the check
|
12
|
-
check_elems = OpenStudio::AttributeVector.new
|
13
|
-
check_elems << OpenStudio::Attribute.new('name', 'Weather Files')
|
14
|
-
check_elems << OpenStudio::Attribute.new('category', category)
|
15
|
-
check_elems << OpenStudio::Attribute.new('description', "Check weather file, design days, and climate zone against #{@utility_name} list of allowable options.")
|
16
|
-
|
17
|
-
# stop here if only name is requested this is used to populate display name for arguments
|
18
|
-
if name_only == true
|
19
|
-
results = []
|
20
|
-
check_elems.each do |elem|
|
21
|
-
results << elem.valueAsString
|
22
|
-
end
|
23
|
-
return results
|
24
|
-
end
|
25
|
-
|
26
|
-
begin
|
27
|
-
# get weather file
|
28
|
-
model_epw = nil
|
29
|
-
if @model.getWeatherFile.url.is_initialized
|
30
|
-
raw_epw = @model.getWeatherFile.url.get
|
31
|
-
end_path_index = raw_epw.rindex('/')
|
32
|
-
model_epw = raw_epw.slice!(end_path_index + 1, raw_epw.length) # everything right of last forward slash
|
33
|
-
end
|
34
|
-
|
35
|
-
# check design days (model must have one or more of the required summer and winter design days)
|
36
|
-
# get design days names from model
|
37
|
-
model_summer_dd_names = []
|
38
|
-
model_winter_dd_names = []
|
39
|
-
@model.getDesignDays.each do |design_day|
|
40
|
-
if design_day.dayType == 'SummerDesignDay'
|
41
|
-
model_summer_dd_names << design_day.name.to_s
|
42
|
-
elsif design_day.dayType == 'WinterDesignDay'
|
43
|
-
model_winter_dd_names << design_day.name.to_s
|
44
|
-
else
|
45
|
-
puts "unexpected day type of #{design_day.dayType} wont' be included in check"
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
# find matching weather file from options, as well as design days and climate zone
|
50
|
-
if options.key?(model_epw)
|
51
|
-
required_summer_dd = options[model_epw]['summer']
|
52
|
-
required_winter_dd = options[model_epw]['winter']
|
53
|
-
valid_climate_zones = [options[model_epw]['climate_zone']]
|
54
|
-
|
55
|
-
# check for intersection betwen model valid design days
|
56
|
-
summer_intersection = (required_summer_dd & model_summer_dd_names)
|
57
|
-
winter_intersection = (required_winter_dd & model_winter_dd_names)
|
58
|
-
if summer_intersection.empty? && !required_summer_dd.empty?
|
59
|
-
check_elems << OpenStudio::Attribute.new('flag', "Didn't find any of the expected summer design days for #{model_epw}")
|
60
|
-
end
|
61
|
-
if winter_intersection.empty? && !required_winter_dd.empty?
|
62
|
-
check_elems << OpenStudio::Attribute.new('flag', "Didn't find any of the expected winter design days for #{model_epw}")
|
63
|
-
end
|
64
|
-
|
65
|
-
else
|
66
|
-
check_elems << OpenStudio::Attribute.new('flag', "#{model_epw} is not a an expected weather file.")
|
67
|
-
check_elems << OpenStudio::Attribute.new('flag', "Model doesn't have expected epw file, as a result can't validate design days.")
|
68
|
-
valid_climate_zones = []
|
69
|
-
options.each do |lookup_epw, value|
|
70
|
-
valid_climate_zones << value['climate_zone']
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
# get ashrae climate zone from model
|
75
|
-
model_climate_zone = nil
|
76
|
-
climateZones = @model.getClimateZones
|
77
|
-
climateZones.climateZones.each do |climateZone|
|
78
|
-
if climateZone.institution == 'ASHRAE'
|
79
|
-
model_climate_zone = climateZone.value
|
80
|
-
next
|
81
|
-
end
|
82
|
-
end
|
83
|
-
if model_climate_zone == ''
|
84
|
-
check_elems << OpenStudio::Attribute.new('flag', "The model's ASHRAE climate zone has not been defined. Expected climate zone was #{valid_climate_zones.uniq.join(',')}.")
|
85
|
-
elsif !valid_climate_zones.include?(model_climate_zone)
|
86
|
-
check_elems << OpenStudio::Attribute.new('flag', "The model's ASHRAE climate zone was #{model_climate_zone}. Expected climate zone was #{valid_climate_zones.uniq.join(',')}.")
|
87
|
-
end
|
88
|
-
rescue StandardError => e
|
89
|
-
# brief description of ruby error
|
90
|
-
check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
|
91
|
-
|
92
|
-
# backtrace of ruby error for diagnostic use
|
93
|
-
if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
|
94
|
-
end
|
95
|
-
|
96
|
-
# add check_elms to new attribute
|
97
|
-
check_elem = OpenStudio::Attribute.new('check', check_elems)
|
98
|
-
|
99
|
-
return check_elem
|
100
|
-
# note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb
|
101
|
-
end
|
102
|
-
end
|