openstudio-extension 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +9 -0
- data/.rubocop.yml +9 -0
- data/Gemfile +3 -1
- data/Jenkinsfile +10 -0
- data/README.md +230 -12
- data/Rakefile +88 -3
- data/bin/console +3 -3
- data/doc_templates/LICENSE.md +27 -0
- data/doc_templates/README.md.erb +42 -0
- data/doc_templates/copyright_erb.txt +36 -0
- data/doc_templates/copyright_js.txt +4 -0
- data/doc_templates/copyright_ruby.txt +34 -0
- data/init_templates/README.md +37 -0
- data/init_templates/gemspec.txt +32 -0
- data/init_templates/openstudio_module.rb +50 -0
- data/init_templates/spec.rb +47 -0
- data/init_templates/spec_helper.rb +49 -0
- data/init_templates/template_gemfile.txt +17 -0
- data/init_templates/template_rakefile.txt +15 -0
- data/init_templates/version.rb +40 -0
- data/lib/files/openstudio-extension-gem-test.ddy +536 -0
- data/lib/files/openstudio-extension-gem-test.epw +8768 -0
- data/lib/files/openstudio-extension-gem-test.stat +554 -0
- data/lib/measures/openstudio_extension_test_measure/LICENSE.md +27 -0
- data/lib/measures/openstudio_extension_test_measure/README.md +26 -0
- data/lib/measures/openstudio_extension_test_measure/README.md.erb +42 -0
- data/lib/measures/openstudio_extension_test_measure/measure.rb +72 -0
- data/lib/measures/openstudio_extension_test_measure/measure.xml +83 -0
- data/lib/measures/openstudio_extension_test_measure/resources/os_lib_helper_methods.rb +399 -0
- data/lib/measures/openstudio_extension_test_measure/tests/OpenStudioExtensionTestMeasure_Test.rb +75 -0
- data/lib/openstudio/extension.rb +220 -0
- data/lib/openstudio/extension/core/CreateResults.rb +879 -0
- data/lib/openstudio/extension/core/check_air_sys_temps.rb +190 -0
- data/lib/openstudio/extension/core/check_calibration.rb +155 -0
- data/lib/openstudio/extension/core/check_cond_zns.rb +84 -0
- data/lib/openstudio/extension/core/check_domestic_hot_water.rb +334 -0
- data/lib/openstudio/extension/core/check_envelope_conductance.rb +453 -0
- data/lib/openstudio/extension/core/check_eui_by_end_use.rb +162 -0
- data/lib/openstudio/extension/core/check_eui_reasonableness.rb +135 -0
- data/lib/openstudio/extension/core/check_fan_pwr.rb +98 -0
- data/lib/openstudio/extension/core/check_internal_loads.rb +393 -0
- data/lib/openstudio/extension/core/check_mech_sys_capacity.rb +226 -0
- data/lib/openstudio/extension/core/check_mech_sys_efficiency.rb +326 -0
- data/lib/openstudio/extension/core/check_mech_sys_part_load_eff.rb +464 -0
- data/lib/openstudio/extension/core/check_mech_sys_type.rb +139 -0
- data/lib/openstudio/extension/core/check_part_loads.rb +451 -0
- data/lib/openstudio/extension/core/check_placeholder.rb +75 -0
- data/lib/openstudio/extension/core/check_plant_cap.rb +123 -0
- data/lib/openstudio/extension/core/check_plant_temps.rb +159 -0
- data/lib/openstudio/extension/core/check_plenum_loads.rb +87 -0
- data/lib/openstudio/extension/core/check_pump_pwr.rb +108 -0
- data/lib/openstudio/extension/core/check_sch_coord.rb +241 -0
- data/lib/openstudio/extension/core/check_schedules.rb +311 -0
- data/lib/openstudio/extension/core/check_simultaneous_heating_and_cooling.rb +158 -0
- data/lib/openstudio/extension/core/check_supply_air_and_thermostat_temp_difference.rb +148 -0
- data/lib/openstudio/extension/core/check_weather_files.rb +132 -0
- data/lib/openstudio/extension/core/deer_vintages.rb +311 -0
- data/lib/openstudio/extension/core/os_lib_aedg_measures.rb +491 -0
- data/lib/openstudio/extension/core/os_lib_cofee.rb +259 -0
- data/lib/openstudio/extension/core/os_lib_constructions.rb +378 -0
- data/lib/openstudio/extension/core/os_lib_geometry.rb +1022 -0
- data/lib/openstudio/extension/core/os_lib_helper_methods.rb +399 -0
- data/lib/openstudio/extension/core/os_lib_hvac.rb +2171 -0
- data/lib/openstudio/extension/core/os_lib_lighting_and_equipment.rb +214 -0
- data/lib/openstudio/extension/core/os_lib_model_generation.rb +817 -0
- data/lib/openstudio/extension/core/os_lib_model_simplification.rb +1049 -0
- data/lib/openstudio/extension/core/os_lib_outdoorair_and_infiltration.rb +165 -0
- data/lib/openstudio/extension/core/os_lib_reporting.rb +4652 -0
- data/lib/openstudio/extension/core/os_lib_reporting_qaqc.rb +200 -0
- data/lib/openstudio/extension/core/os_lib_schedules.rb +963 -0
- data/lib/openstudio/extension/rake_task.rb +149 -0
- data/lib/openstudio/extension/runner.rb +644 -0
- data/lib/openstudio/extension/version.rb +40 -0
- data/openstudio-extension.gemspec +20 -15
- metadata +150 -14
- data/.travis.yml +0 -7
- data/lib/OpenStudio/Extension/rake_task.rb +0 -84
- data/lib/OpenStudio/Extension/version.rb +0 -33
- data/lib/OpenStudio/extension.rb +0 -65
@@ -0,0 +1,226 @@
|
|
1
|
+
# *******************************************************************************
|
2
|
+
# OpenStudio(R), Copyright (c) 2008-2019, Alliance for Sustainable Energy, LLC.
|
3
|
+
# All rights reserved.
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are met:
|
6
|
+
#
|
7
|
+
# (1) Redistributions of source code must retain the above copyright notice,
|
8
|
+
# this list of conditions and the following disclaimer.
|
9
|
+
#
|
10
|
+
# (2) Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
#
|
14
|
+
# (3) Neither the name of the copyright holder nor the names of any contributors
|
15
|
+
# may be used to endorse or promote products derived from this software without
|
16
|
+
# specific prior written permission from the respective party.
|
17
|
+
#
|
18
|
+
# (4) Other than as required in clauses (1) and (2), distributions in any form
|
19
|
+
# of modifications or other derivative works may not use the "OpenStudio"
|
20
|
+
# trademark, "OS", "os", or any other confusingly similar designation without
|
21
|
+
# specific prior written permission from Alliance for Sustainable Energy, LLC.
|
22
|
+
#
|
23
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
|
24
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
25
|
+
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
26
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
|
27
|
+
# UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
|
28
|
+
# THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
29
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
30
|
+
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
31
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
32
|
+
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
33
|
+
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
34
|
+
# *******************************************************************************
|
35
|
+
|
36
|
+
module OsLib_QAQC
|
37
|
+
# include any general notes about QAQC method here
|
38
|
+
|
39
|
+
# checks the number of unmet hours in the model
|
40
|
+
def check_mech_sys_capacity(category, options, target_standard, name_only = false)
|
41
|
+
# summary of the check
|
42
|
+
check_elems = OpenStudio::AttributeVector.new
|
43
|
+
check_elems << OpenStudio::Attribute.new('name', 'Mechanical System Capacity')
|
44
|
+
check_elems << OpenStudio::Attribute.new('category', category)
|
45
|
+
check_elems << OpenStudio::Attribute.new('description', 'Check HVAC capacity against ASHRAE rules of thumb for chiller max flow rate, air loop max flow rate, air loop cooling capciaty, and zone heating capcaity. Zone heating check will skip thermal zones without any exterior exposure, and thermal zones that are not conditioned.')
|
46
|
+
|
47
|
+
# stop here if only name is requested this is used to populate display name for arguments
|
48
|
+
if name_only == true
|
49
|
+
results = []
|
50
|
+
check_elems.each do |elem|
|
51
|
+
results << elem.valueAsString
|
52
|
+
end
|
53
|
+
return results
|
54
|
+
end
|
55
|
+
|
56
|
+
# Versions of OpenStudio greater than 2.4.0 use a modified version of
|
57
|
+
# openstudio-standards with different method calls. These methods
|
58
|
+
# require a "Standard" object instead of the standard being passed into method calls.
|
59
|
+
# This Standard object is used throughout the QAQC check.
|
60
|
+
if OpenStudio::VersionString.new(OpenStudio.openStudioVersion) < OpenStudio::VersionString.new('2.4.3')
|
61
|
+
use_old_gem_code = true
|
62
|
+
else
|
63
|
+
use_old_gem_code = false
|
64
|
+
std = Standard.build(target_standard)
|
65
|
+
end
|
66
|
+
|
67
|
+
begin
|
68
|
+
# check max flow rate of chillers in model
|
69
|
+
@model.getPlantLoops.sort.each do |plant_loop|
|
70
|
+
# next if no chiller on plant loop
|
71
|
+
chillers = []
|
72
|
+
plant_loop.supplyComponents.each do |sc|
|
73
|
+
if sc.to_ChillerElectricEIR.is_initialized
|
74
|
+
chillers << sc.to_ChillerElectricEIR.get
|
75
|
+
end
|
76
|
+
end
|
77
|
+
next if chillers.empty?
|
78
|
+
|
79
|
+
# gather targets for chiller capacity
|
80
|
+
chiller_max_flow_rate_target = options['chiller_max_flow_rate']['target']
|
81
|
+
chiller_max_flow_rate_fraction_min = options['chiller_max_flow_rate']['min']
|
82
|
+
chiller_max_flow_rate_fraction_max = options['chiller_max_flow_rate']['max']
|
83
|
+
chiller_max_flow_rate_units_ip = options['chiller_max_flow_rate']['units'] # gal/ton*min
|
84
|
+
# string above or display only, for converstion 12000 Btu/h per ton
|
85
|
+
|
86
|
+
# get capacity of loop (not individual chiller but entire loop)
|
87
|
+
if use_old_gem_code
|
88
|
+
total_cooling_capacity_w = plant_loop.total_cooling_capacity
|
89
|
+
else
|
90
|
+
total_cooling_capacity_w = std.plant_loop_total_cooling_capacity(plant_loop)
|
91
|
+
end
|
92
|
+
total_cooling_capacity_ton = OpenStudio.convert(total_cooling_capacity_w, 'W', 'Btu/h').get / 12000.0
|
93
|
+
|
94
|
+
# get the max flow rate (through plant, not specific chiller)
|
95
|
+
if use_old_gem_code
|
96
|
+
maximum_loop_flow_rate = plant_loop.find_maximum_loop_flow_rate
|
97
|
+
else
|
98
|
+
maximum_loop_flow_rate = std.plant_loop_find_maximum_loop_flow_rate(plant_loop)
|
99
|
+
end
|
100
|
+
maximum_loop_flow_rate_ip = OpenStudio.convert(maximum_loop_flow_rate, 'm^3/s', 'gal/min').get
|
101
|
+
|
102
|
+
# calculate the flow per tons of cooling
|
103
|
+
model_flow_rate_per_ton_cooling_ip = maximum_loop_flow_rate_ip / total_cooling_capacity_ton
|
104
|
+
|
105
|
+
# check flow rate per capacity
|
106
|
+
if model_flow_rate_per_ton_cooling_ip < chiller_max_flow_rate_target * (1.0 - chiller_max_flow_rate_fraction_min)
|
107
|
+
check_elems << OpenStudio::Attribute.new('flag', "Flow Rate of #{model_flow_rate_per_ton_cooling_ip.round(2)} #{chiller_max_flow_rate_units_ip} for #{plant_loop.name.get} is more than #{chiller_max_flow_rate_fraction_min * 100} % below the typical value of #{chiller_max_flow_rate_target.round(2)} #{chiller_max_flow_rate_units_ip}.")
|
108
|
+
elsif model_flow_rate_per_ton_cooling_ip > chiller_max_flow_rate_target * (1.0 + chiller_max_flow_rate_fraction_max)
|
109
|
+
check_elems << OpenStudio::Attribute.new('flag', "Flow Rate of #{model_flow_rate_per_ton_cooling_ip.round(2)} #{chiller_max_flow_rate_units_ip} for #{plant_loop.name.get} is more than #{chiller_max_flow_rate_fraction_max * 100} % above the typical value of #{chiller_max_flow_rate_target.round(2)} #{chiller_max_flow_rate_units_ip}.")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# loop through air loops to get max flor rate and cooling capacity.
|
114
|
+
@model.getAirLoopHVACs.sort.each do |air_loop|
|
115
|
+
# TODO: - check if DOAS, don't check airflow or cooling capacity if it is (why not check OA for DOAS? would it be different target)
|
116
|
+
|
117
|
+
# gather argument options for air_loop_max_flow_rate checks
|
118
|
+
air_loop_max_flow_rate_target = options['air_loop_max_flow_rate']['target']
|
119
|
+
air_loop_max_flow_rate_fraction_min = options['air_loop_max_flow_rate']['min']
|
120
|
+
air_loop_max_flow_rate_fraction_max = options['air_loop_max_flow_rate']['max']
|
121
|
+
air_loop_max_flow_rate_units_ip = options['air_loop_max_flow_rate']['units']
|
122
|
+
air_loop_max_flow_rate_units_si = 'm^3/m^2*s'
|
123
|
+
|
124
|
+
# get values from model for air loop checks
|
125
|
+
if use_old_gem_code
|
126
|
+
floor_area_served = air_loop.floor_area_served # m^2
|
127
|
+
else
|
128
|
+
floor_area_served = std.air_loop_hvac_floor_area_served(air_loop) # m^2
|
129
|
+
end
|
130
|
+
|
131
|
+
if use_old_gem_code
|
132
|
+
design_supply_air_flow_rate = air_loop.find_design_supply_air_flow_rate # m^3/s
|
133
|
+
else
|
134
|
+
design_supply_air_flow_rate = std.air_loop_hvac_find_design_supply_air_flow_rate(air_loop) # m^3/s
|
135
|
+
end
|
136
|
+
|
137
|
+
# check max flow rate of air loops in the model
|
138
|
+
model_normalized_flow_rate_si = design_supply_air_flow_rate / floor_area_served
|
139
|
+
model_normalized_flow_rate_ip = OpenStudio.convert(model_normalized_flow_rate_si, air_loop_max_flow_rate_units_si, air_loop_max_flow_rate_units_ip).get
|
140
|
+
if model_normalized_flow_rate_ip < air_loop_max_flow_rate_target * (1.0 - air_loop_max_flow_rate_fraction_min)
|
141
|
+
check_elems << OpenStudio::Attribute.new('flag', "Flow Rate of #{model_normalized_flow_rate_ip.round(2)} #{air_loop_max_flow_rate_units_ip} for #{air_loop.name.get} is more than #{air_loop_max_flow_rate_fraction_min * 100} % below the typical value of #{air_loop_max_flow_rate_target.round(2)} #{air_loop_max_flow_rate_units_ip}.")
|
142
|
+
elsif model_normalized_flow_rate_ip > air_loop_max_flow_rate_target * (1.0 + air_loop_max_flow_rate_fraction_max)
|
143
|
+
check_elems << OpenStudio::Attribute.new('flag', "Flow Rate of #{model_normalized_flow_rate_ip.round(2)} #{air_loop_max_flow_rate_units_ip} for #{air_loop.name.get} is more than #{air_loop_max_flow_rate_fraction_max * 100} % above the typical value of #{air_loop_max_flow_rate_target.round(2)} #{air_loop_max_flow_rate_units_ip}.")
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# loop through air loops to get max flor rate and cooling capacity.
|
148
|
+
@model.getAirLoopHVACs.sort.each do |air_loop|
|
149
|
+
# check if DOAS, don't check airflow or cooling capacity if it is
|
150
|
+
sizing_system = air_loop.sizingSystem
|
151
|
+
next if sizing_system.typeofLoadtoSizeOn.to_s == 'VentilationRequirement'
|
152
|
+
|
153
|
+
# gather argument options for air_loop_cooling_capacity checks
|
154
|
+
air_loop_cooling_capacity_target = options['air_loop_cooling_capacity']['target']
|
155
|
+
air_loop_cooling_capacity_fraction_min = options['air_loop_cooling_capacity']['min']
|
156
|
+
air_loop_cooling_capacity_fraction_max = options['air_loop_cooling_capacity']['max']
|
157
|
+
air_loop_cooling_capacity_units_ip = options['air_loop_cooling_capacity']['units'] # tons/ft^2
|
158
|
+
# string above or display only, for converstion 12000 Btu/h per ton
|
159
|
+
air_loop_cooling_capacity_units_si = 'W/m^2'
|
160
|
+
|
161
|
+
# get values from model for air loop checks
|
162
|
+
if use_old_gem_code
|
163
|
+
floor_area_served = air_loop.floor_area_served # m^2
|
164
|
+
else
|
165
|
+
floor_area_served = std.air_loop_hvac_floor_area_served(air_loop) # m^2
|
166
|
+
end
|
167
|
+
|
168
|
+
if use_old_gem_code
|
169
|
+
capacity = air_loop.total_cooling_capacity # W
|
170
|
+
else
|
171
|
+
capacity = std.air_loop_hvac_total_cooling_capacity(air_loop) # W
|
172
|
+
end
|
173
|
+
|
174
|
+
# check cooling capacity of air loops in the model
|
175
|
+
model_normalized_capacity_si = capacity / floor_area_served
|
176
|
+
model_normalized_capacity_ip = OpenStudio.convert(model_normalized_capacity_si, air_loop_cooling_capacity_units_si, 'Btu/ft^2*h').get / 12000.0 # hard coded to get tons from Btu/h
|
177
|
+
|
178
|
+
# want to display in tons/ft^2 so invert number and display for checks
|
179
|
+
model_tons_per_area_ip = 1.0 / model_normalized_capacity_ip
|
180
|
+
target_tons_per_area_ip = 1.0 / air_loop_cooling_capacity_target
|
181
|
+
inverted_units = 'ft^2/ton'
|
182
|
+
|
183
|
+
if model_tons_per_area_ip < target_tons_per_area_ip * (1.0 - air_loop_cooling_capacity_fraction_max)
|
184
|
+
check_elems << OpenStudio::Attribute.new('flag', "Cooling Capacity of #{model_tons_per_area_ip.round} #{inverted_units} for #{air_loop.name.get} is more than #{air_loop_cooling_capacity_fraction_max * 100} % below the typical value of #{target_tons_per_area_ip.round} #{inverted_units}.")
|
185
|
+
elsif model_tons_per_area_ip > target_tons_per_area_ip * (1.0 + air_loop_cooling_capacity_fraction_min)
|
186
|
+
check_elems << OpenStudio::Attribute.new('flag', "Cooling Capacity of #{model_tons_per_area_ip.round} #{inverted_units} for #{air_loop.name.get} is more than #{air_loop_cooling_capacity_fraction_min * 100} % above the typical value of #{target_tons_per_area_ip.round} #{inverted_units}.")
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# check heating capacity of thermal zones in the model with exterior exposure
|
191
|
+
report_name = 'HVACSizingSummary'
|
192
|
+
table_name = 'Zone Sensible Heating'
|
193
|
+
column_name = 'User Design Load per Area'
|
194
|
+
target = options['zone_heating_capacity']['target']
|
195
|
+
fraction_min = options['zone_heating_capacity']['min']
|
196
|
+
fraction_max = options['zone_heating_capacity']['max']
|
197
|
+
units_ip = options['zone_heating_capacity']['units']
|
198
|
+
units_si = 'W/m^2'
|
199
|
+
@model.getThermalZones.sort.each do |thermal_zone|
|
200
|
+
next if thermal_zone.canBePlenum
|
201
|
+
next if thermal_zone.exteriorSurfaceArea == 0.0
|
202
|
+
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}' and RowName= '#{thermal_zone.name.get.upcase}' and ColumnName= '#{column_name}'"
|
203
|
+
results = @sql.execAndReturnFirstDouble(query) # W/m^2
|
204
|
+
model_zone_heating_capacity_ip = OpenStudio.convert(results.to_f, units_si, units_ip).get
|
205
|
+
# check actual against target
|
206
|
+
if model_zone_heating_capacity_ip < target * (1.0 - fraction_min)
|
207
|
+
check_elems << OpenStudio::Attribute.new('flag', "Heating Capacity of #{model_zone_heating_capacity_ip.round(2)} Btu/ft^2*h for #{thermal_zone.name.get} is more than #{fraction_min * 100} % below the typical value of #{target.round(2)} Btu/ft^2*h.")
|
208
|
+
elsif model_zone_heating_capacity_ip > target * (1.0 + fraction_max)
|
209
|
+
check_elems << OpenStudio::Attribute.new('flag', "Heating Capacity of #{model_zone_heating_capacity_ip.round(2)} Btu/ft^2*h for #{thermal_zone.name.get} is more than #{fraction_max * 100} % above the typical value of #{target.round(2)} Btu/ft^2*h.")
|
210
|
+
end
|
211
|
+
end
|
212
|
+
rescue StandardError => e
|
213
|
+
# brief description of ruby error
|
214
|
+
check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
|
215
|
+
|
216
|
+
# backtrace of ruby error for diagnostic use
|
217
|
+
if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
|
218
|
+
end
|
219
|
+
|
220
|
+
# add check_elms to new attribute
|
221
|
+
check_elem = OpenStudio::Attribute.new('check', check_elems)
|
222
|
+
|
223
|
+
return check_elem
|
224
|
+
# note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,326 @@
|
|
1
|
+
# *******************************************************************************
|
2
|
+
# OpenStudio(R), Copyright (c) 2008-2019, Alliance for Sustainable Energy, LLC.
|
3
|
+
# All rights reserved.
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are met:
|
6
|
+
#
|
7
|
+
# (1) Redistributions of source code must retain the above copyright notice,
|
8
|
+
# this list of conditions and the following disclaimer.
|
9
|
+
#
|
10
|
+
# (2) Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
#
|
14
|
+
# (3) Neither the name of the copyright holder nor the names of any contributors
|
15
|
+
# may be used to endorse or promote products derived from this software without
|
16
|
+
# specific prior written permission from the respective party.
|
17
|
+
#
|
18
|
+
# (4) Other than as required in clauses (1) and (2), distributions in any form
|
19
|
+
# of modifications or other derivative works may not use the "OpenStudio"
|
20
|
+
# trademark, "OS", "os", or any other confusingly similar designation without
|
21
|
+
# specific prior written permission from Alliance for Sustainable Energy, LLC.
|
22
|
+
#
|
23
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
|
24
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
25
|
+
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
26
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
|
27
|
+
# UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
|
28
|
+
# THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
29
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
30
|
+
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
31
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
32
|
+
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
33
|
+
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
34
|
+
# *******************************************************************************
|
35
|
+
|
36
|
+
module OsLib_QAQC
|
37
|
+
# include any general notes about QAQC method here
|
38
|
+
|
39
|
+
# checks the number of unmet hours in the model
|
40
|
+
def check_mech_sys_efficiency(category, target_standard, min_pass, max_pass, name_only = false)
|
41
|
+
component_type_array = ['ChillerElectricEIR', 'CoilCoolingDXSingleSpeed', 'CoilCoolingDXTwoSpeed', 'CoilHeatingDXSingleSpeed', 'BoilerHotWater', 'FanConstantVolume', 'FanVariableVolume', 'PumpConstantSpeed', 'PumpVariableSpeed']
|
42
|
+
|
43
|
+
# summary of the check
|
44
|
+
check_elems = OpenStudio::AttributeVector.new
|
45
|
+
check_elems << OpenStudio::Attribute.new('name', 'Mechanical System Efficiency')
|
46
|
+
check_elems << OpenStudio::Attribute.new('category', category)
|
47
|
+
|
48
|
+
if target_standard.include?('90.1-2013')
|
49
|
+
display_standard = "ASHRAE #{target_standard}"
|
50
|
+
check_elems << OpenStudio::Attribute.new('description', "Check against #{display_standard} Tables 6.8.1 A-K for the following component types: #{component_type_array.join(', ')}.")
|
51
|
+
else
|
52
|
+
# TODO: - could add more elsifs if want to dsiplay tables and sections for additional 90.1 standards
|
53
|
+
if target_standard.include?('90.1')
|
54
|
+
display_standard = "ASHRAE #{target_standard}"
|
55
|
+
else
|
56
|
+
display_standard = target_standard
|
57
|
+
end
|
58
|
+
check_elems << OpenStudio::Attribute.new('description', "Check against #{display_standard} for the following component types: #{component_type_array.join(', ')}.")
|
59
|
+
end
|
60
|
+
|
61
|
+
# stop here if only name is requested this is used to populate display name for arguments
|
62
|
+
if name_only == true
|
63
|
+
results = []
|
64
|
+
check_elems.each do |elem|
|
65
|
+
results << elem.valueAsString
|
66
|
+
end
|
67
|
+
return results
|
68
|
+
end
|
69
|
+
|
70
|
+
# Versions of OpenStudio greater than 2.4.0 use a modified version of
|
71
|
+
# openstudio-standards with different method calls. These methods
|
72
|
+
# require a "Standard" object instead of the standard being passed into method calls.
|
73
|
+
# This Standard object is used throughout the QAQC check.
|
74
|
+
if OpenStudio::VersionString.new(OpenStudio.openStudioVersion) < OpenStudio::VersionString.new('2.4.3')
|
75
|
+
use_old_gem_code = true
|
76
|
+
else
|
77
|
+
use_old_gem_code = false
|
78
|
+
std = Standard.build(target_standard)
|
79
|
+
end
|
80
|
+
|
81
|
+
begin
|
82
|
+
# check ChillerElectricEIR objects (will also have curve check in different script)
|
83
|
+
@model.getChillerElectricEIRs.each do |component|
|
84
|
+
# eff values from model
|
85
|
+
reference_COP = component.referenceCOP
|
86
|
+
|
87
|
+
# get eff values from standards (if name doesn't have expected strings find object returns first object of multiple)
|
88
|
+
|
89
|
+
if use_old_gem_code
|
90
|
+
standard_minimum_full_load_efficiency = component.standard_minimum_full_load_efficiency(target_standard)
|
91
|
+
else
|
92
|
+
standard_minimum_full_load_efficiency = std.chiller_electric_eir_standard_minimum_full_load_efficiency(component)
|
93
|
+
end
|
94
|
+
|
95
|
+
# check actual against target
|
96
|
+
if standard_minimum_full_load_efficiency.nil?
|
97
|
+
check_elems << OpenStudio::Attribute.new('flag', "Can't find target full load efficiency for #{component.name}.")
|
98
|
+
elsif reference_COP < standard_minimum_full_load_efficiency * (1.0 - min_pass)
|
99
|
+
check_elems << OpenStudio::Attribute.new('flag', "COP of #{reference_COP.round(2)} for #{component.name} is more than #{min_pass * 100} % below the expected value of #{standard_minimum_full_load_efficiency.round(2)}.")
|
100
|
+
elsif reference_COP > standard_minimum_full_load_efficiency * (1.0 + max_pass)
|
101
|
+
check_elems << OpenStudio::Attribute.new('flag', "COP of #{reference_COP.round(2)} for #{component.name} is more than #{max_pass * 100} % above the expected value of #{standard_minimum_full_load_efficiency.round(2)}.")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# check CoilCoolingDXSingleSpeed objects (will also have curve check in different script)
|
106
|
+
@model.getCoilCoolingDXSingleSpeeds.each do |component|
|
107
|
+
# eff values from model
|
108
|
+
rated_COP = component.ratedCOP.get
|
109
|
+
|
110
|
+
# get eff values from standards
|
111
|
+
if use_old_gem_code
|
112
|
+
standard_minimum_cop = component.standard_minimum_cop(target_standard)
|
113
|
+
else
|
114
|
+
standard_minimum_cop = std.coil_cooling_dx_single_speed_standard_minimum_cop(component)
|
115
|
+
end
|
116
|
+
|
117
|
+
# check actual against target
|
118
|
+
if standard_minimum_cop.nil?
|
119
|
+
check_elems << OpenStudio::Attribute.new('flag', "Can't find target COP for #{component.name}.")
|
120
|
+
elsif rated_COP < standard_minimum_cop * (1.0 - min_pass)
|
121
|
+
check_elems << OpenStudio::Attribute.new('flag', "The COP of #{rated_COP.round(2)} for #{component.name} is more than #{min_pass * 100} % below the expected value of #{standard_minimum_cop.round(2)} for #{display_standard}.")
|
122
|
+
elsif rated_COP > standard_minimum_cop * (1.0 + max_pass)
|
123
|
+
check_elems << OpenStudio::Attribute.new('flag', "The COP of #{rated_COP.round(2)} for #{component.name} is more than #{max_pass * 100} % above the expected value of #{standard_minimum_cop.round(2)} for #{display_standard}.")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# check CoilCoolingDXTwoSpeed objects (will also have curve check in different script)
|
128
|
+
@model.getCoilCoolingDXTwoSpeeds.each do |component|
|
129
|
+
# eff values from model
|
130
|
+
rated_high_speed_COP = component.ratedHighSpeedCOP.get
|
131
|
+
rated_low_speed_COP = component.ratedLowSpeedCOP.get
|
132
|
+
|
133
|
+
# get eff values from standards
|
134
|
+
if use_old_gem_code
|
135
|
+
standard_minimum_cop = component.standard_minimum_cop(target_standard)
|
136
|
+
else
|
137
|
+
standard_minimum_cop = std.coil_cooling_dx_two_speed_standard_minimum_cop(component)
|
138
|
+
end
|
139
|
+
|
140
|
+
# check actual against target
|
141
|
+
if standard_minimum_cop.nil?
|
142
|
+
check_elems << OpenStudio::Attribute.new('flag', "Can't find target COP for #{component.name}.")
|
143
|
+
elsif rated_high_speed_COP < standard_minimum_cop * (1.0 - min_pass)
|
144
|
+
check_elems << OpenStudio::Attribute.new('flag', "The high speed COP of #{rated_high_speed_COP.round(2)} for #{component.name} is more than #{min_pass * 100} % below the expected value of #{standard_minimum_cop.round(2)} for #{display_standard}.")
|
145
|
+
elsif rated_high_speed_COP > standard_minimum_cop * (1.0 + max_pass)
|
146
|
+
check_elems << OpenStudio::Attribute.new('flag', "The high speed COP of #{rated_high_speed_COP.round(2)} for #{component.name} is more than #{max_pass * 100} % above the expected value of #{standard_minimum_cop.round(2)} for #{display_standard}.")
|
147
|
+
end
|
148
|
+
if standard_minimum_cop.nil?
|
149
|
+
check_elems << OpenStudio::Attribute.new('flag', "Can't find target COP for #{component.name}.")
|
150
|
+
elsif rated_low_speed_COP < standard_minimum_cop * (1.0 - min_pass)
|
151
|
+
check_elems << OpenStudio::Attribute.new('flag', "The low speed COP of #{rated_low_speed_COP.round(2)} for #{component.name} is more than #{min_pass * 100} % below the expected value of #{standard_minimum_cop.round(2)} for #{display_standard}.")
|
152
|
+
elsif rated_low_speed_COP > standard_minimum_cop * (1.0 + max_pass)
|
153
|
+
check_elems << OpenStudio::Attribute.new('flag', "The low speed COP of #{rated_low_speed_COP.round(2)} for #{component.name} is more than #{max_pass * 100} % above the expected value of #{standard_minimum_cop.round(2)} for #{display_standard}.")
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# check CoilHeatingDXSingleSpeed objects
|
158
|
+
# todo - need to test this once json file populated for this data
|
159
|
+
@model.getCoilHeatingDXSingleSpeeds.each do |component|
|
160
|
+
# eff values from model
|
161
|
+
rated_COP = component.ratedCOP
|
162
|
+
|
163
|
+
# get eff values from standards
|
164
|
+
if use_old_gem_code
|
165
|
+
standard_minimum_cop = component.standard_minimum_cop(target_standard)
|
166
|
+
else
|
167
|
+
standard_minimum_cop = std.coil_heating_dx_single_speed_standard_minimum_cop(component)
|
168
|
+
end
|
169
|
+
|
170
|
+
# check actual against target
|
171
|
+
if standard_minimum_cop.nil?
|
172
|
+
check_elems << OpenStudio::Attribute.new('flag', "Can't find target COP for #{component.name}.")
|
173
|
+
elsif rated_COP < standard_minimum_cop * (1.0 - min_pass)
|
174
|
+
check_elems << OpenStudio::Attribute.new('flag', "The COP of #{rated_COP.round(2)} for #{component.name} is more than #{min_pass * 100} % below the expected value of #{standard_minimum_cop.round(2)} for #{display_standard}.")
|
175
|
+
elsif rated_COP > standard_minimum_cop * (1.0 + max_pass)
|
176
|
+
check_elems << OpenStudio::Attribute.new('flag', "The COP of #{rated_COP.round(2)} for #{component.name} is more than #{max_pass * 100} % above the expected value of #{standard_minimum_cop.round(2)}. for #{display_standard}")
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# check BoilerHotWater
|
181
|
+
@model.getBoilerHotWaters.each do |component|
|
182
|
+
# eff values from model
|
183
|
+
nominal_thermal_efficiency = component.nominalThermalEfficiency
|
184
|
+
|
185
|
+
# get eff values from standards
|
186
|
+
if use_old_gem_code
|
187
|
+
standard_minimum_thermal_efficiency = component.standard_minimum_thermal_efficiency(target_standard)
|
188
|
+
else
|
189
|
+
standard_minimum_thermal_efficiency = std.boiler_hot_water_standard_minimum_thermal_efficiency(component)
|
190
|
+
end
|
191
|
+
|
192
|
+
# check actual against target
|
193
|
+
if standard_minimum_thermal_efficiency.nil?
|
194
|
+
check_elems << OpenStudio::Attribute.new('flag', "Can't find target thermal efficiency for #{component.name}.")
|
195
|
+
elsif nominal_thermal_efficiency < standard_minimum_thermal_efficiency * (1.0 - min_pass)
|
196
|
+
check_elems << OpenStudio::Attribute.new('flag', "Nominal thermal efficiency of #{nominal_thermal_efficiency.round(2)} for #{component.name} is more than #{min_pass * 100} % below the expected value of #{standard_minimum_thermal_efficiency.round(2)} for #{display_standard}.")
|
197
|
+
elsif nominal_thermal_efficiency > standard_minimum_thermal_efficiency * (1.0 + max_pass)
|
198
|
+
check_elems << OpenStudio::Attribute.new('flag', "Nominal thermal efficiency of #{nominal_thermal_efficiency.round(2)} for #{component.name} is more than #{max_pass * 100} % above the expected value of #{standard_minimum_thermal_efficiency.round(2)} for #{display_standard}.")
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# check FanConstantVolume
|
203
|
+
@model.getFanConstantVolumes.each do |component|
|
204
|
+
# eff values from model
|
205
|
+
motor_eff = component.motorEfficiency
|
206
|
+
|
207
|
+
# get eff values from standards
|
208
|
+
if use_old_gem_code
|
209
|
+
motor_bhp = component.brake_horsepower
|
210
|
+
else
|
211
|
+
motor_bhp = std.fan_brake_horsepower(component)
|
212
|
+
end
|
213
|
+
|
214
|
+
if use_old_gem_code
|
215
|
+
standard_minimum_motor_efficiency_and_size = component.standard_minimum_motor_efficiency_and_size(target_standard, motor_bhp)[0]
|
216
|
+
else
|
217
|
+
standard_minimum_motor_efficiency_and_size = std.fan_standard_minimum_motor_efficiency_and_size(component, motor_bhp)[0]
|
218
|
+
end
|
219
|
+
|
220
|
+
# check actual against target
|
221
|
+
if motor_eff < standard_minimum_motor_efficiency_and_size * (1.0 - min_pass)
|
222
|
+
check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{min_pass * 100} % below the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{display_standard}.")
|
223
|
+
elsif motor_eff > standard_minimum_motor_efficiency_and_size * (1.0 + max_pass)
|
224
|
+
check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{max_pass * 100} % above the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{display_standard}.")
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# check FanVariableVolume
|
229
|
+
@model.getFanVariableVolumes.each do |component|
|
230
|
+
# eff values from model
|
231
|
+
motor_eff = component.motorEfficiency
|
232
|
+
|
233
|
+
# get eff values from standards
|
234
|
+
if use_old_gem_code
|
235
|
+
motor_bhp = component.brake_horsepower
|
236
|
+
else
|
237
|
+
motor_bhp = std.fan_brake_horsepower(component)
|
238
|
+
end
|
239
|
+
if use_old_gem_code
|
240
|
+
standard_minimum_motor_efficiency_and_size = component.standard_minimum_motor_efficiency_and_size(target_standard, motor_bhp)[0]
|
241
|
+
else
|
242
|
+
standard_minimum_motor_efficiency_and_size = std.fan_standard_minimum_motor_efficiency_and_size(component, motor_bhp)[0]
|
243
|
+
end
|
244
|
+
|
245
|
+
# check actual against target
|
246
|
+
if motor_eff < standard_minimum_motor_efficiency_and_size * (1.0 - min_pass)
|
247
|
+
check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{min_pass * 100} % below the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{display_standard}.")
|
248
|
+
elsif motor_eff > standard_minimum_motor_efficiency_and_size * (1.0 + max_pass)
|
249
|
+
check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{max_pass * 100} % above the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{display_standard}.")
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# check PumpConstantSpeed
|
254
|
+
@model.getPumpConstantSpeeds.each do |component|
|
255
|
+
# eff values from model
|
256
|
+
motor_eff = component.motorEfficiency
|
257
|
+
|
258
|
+
# get eff values from standards
|
259
|
+
if use_old_gem_code
|
260
|
+
motor_bhp = component.brake_horsepower
|
261
|
+
else
|
262
|
+
motor_bhp = std.pump_brake_horsepower(component)
|
263
|
+
end
|
264
|
+
next if motor_bhp == 0.0
|
265
|
+
if use_old_gem_code
|
266
|
+
standard_minimum_motor_efficiency_and_size = component.standard_minimum_motor_efficiency_and_size(target_standard, motor_bhp)[0]
|
267
|
+
else
|
268
|
+
standard_minimum_motor_efficiency_and_size = std.pump_standard_minimum_motor_efficiency_and_size(component, motor_bhp)[0]
|
269
|
+
end
|
270
|
+
|
271
|
+
# check actual against target
|
272
|
+
if motor_eff < standard_minimum_motor_efficiency_and_size * (1.0 - min_pass)
|
273
|
+
check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{min_pass * 100} % below the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{display_standard}.")
|
274
|
+
elsif motor_eff > standard_minimum_motor_efficiency_and_size * (1.0 + max_pass)
|
275
|
+
check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{max_pass * 100} % above the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{display_standard}.")
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
# check PumpVariableSpeed
|
280
|
+
@model.getPumpVariableSpeeds.each do |component|
|
281
|
+
# eff values from model
|
282
|
+
motor_eff = component.motorEfficiency
|
283
|
+
|
284
|
+
# get eff values from standards
|
285
|
+
if use_old_gem_code
|
286
|
+
motor_bhp = component.brake_horsepower
|
287
|
+
else
|
288
|
+
motor_bhp = std.pump_brake_horsepower(component)
|
289
|
+
end
|
290
|
+
next if motor_bhp == 0.0
|
291
|
+
if use_old_gem_code
|
292
|
+
standard_minimum_motor_efficiency_and_size = component.standard_minimum_motor_efficiency_and_size(target_standard, motor_bhp)[0]
|
293
|
+
else
|
294
|
+
standard_minimum_motor_efficiency_and_size = std.pump_standard_minimum_motor_efficiency_and_size(component, motor_bhp)[0]
|
295
|
+
end
|
296
|
+
|
297
|
+
# check actual against target
|
298
|
+
if motor_eff < standard_minimum_motor_efficiency_and_size * (1.0 - min_pass)
|
299
|
+
check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{min_pass * 100} % below the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{display_standard}.")
|
300
|
+
elsif motor_eff > standard_minimum_motor_efficiency_and_size * (1.0 + max_pass)
|
301
|
+
check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{max_pass * 100} % above the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{display_standard}.")
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
# TODO: - should I throw flag if any other component types are in the model
|
306
|
+
|
307
|
+
# BasicOfficeTest_Mueller.osm test model current exercises the following component types
|
308
|
+
# (CoilCoolingDXTwoSpeed,FanVariableVolume,PumpConstantSpeed)
|
309
|
+
|
310
|
+
# BasicOfficeTest_Mueller_altHVAC_a checks these component types
|
311
|
+
# (ChillerElectricEIR,CoilCoolingDXSingleSpeed,CoilHeatingDXSingleSpeed,BoilerHotWater,FanConstantVolume,PumpVariableSpeed)
|
312
|
+
rescue StandardError => e
|
313
|
+
# brief description of ruby error
|
314
|
+
check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
|
315
|
+
|
316
|
+
# backtrace of ruby error for diagnostic use
|
317
|
+
if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
|
318
|
+
end
|
319
|
+
|
320
|
+
# add check_elms to new attribute
|
321
|
+
check_elem = OpenStudio::Attribute.new('check', check_elems)
|
322
|
+
|
323
|
+
return check_elem
|
324
|
+
# note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb
|
325
|
+
end
|
326
|
+
end
|