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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +9 -0
  3. data/.rubocop.yml +9 -0
  4. data/Gemfile +3 -1
  5. data/Jenkinsfile +10 -0
  6. data/README.md +230 -12
  7. data/Rakefile +88 -3
  8. data/bin/console +3 -3
  9. data/doc_templates/LICENSE.md +27 -0
  10. data/doc_templates/README.md.erb +42 -0
  11. data/doc_templates/copyright_erb.txt +36 -0
  12. data/doc_templates/copyright_js.txt +4 -0
  13. data/doc_templates/copyright_ruby.txt +34 -0
  14. data/init_templates/README.md +37 -0
  15. data/init_templates/gemspec.txt +32 -0
  16. data/init_templates/openstudio_module.rb +50 -0
  17. data/init_templates/spec.rb +47 -0
  18. data/init_templates/spec_helper.rb +49 -0
  19. data/init_templates/template_gemfile.txt +17 -0
  20. data/init_templates/template_rakefile.txt +15 -0
  21. data/init_templates/version.rb +40 -0
  22. data/lib/files/openstudio-extension-gem-test.ddy +536 -0
  23. data/lib/files/openstudio-extension-gem-test.epw +8768 -0
  24. data/lib/files/openstudio-extension-gem-test.stat +554 -0
  25. data/lib/measures/openstudio_extension_test_measure/LICENSE.md +27 -0
  26. data/lib/measures/openstudio_extension_test_measure/README.md +26 -0
  27. data/lib/measures/openstudio_extension_test_measure/README.md.erb +42 -0
  28. data/lib/measures/openstudio_extension_test_measure/measure.rb +72 -0
  29. data/lib/measures/openstudio_extension_test_measure/measure.xml +83 -0
  30. data/lib/measures/openstudio_extension_test_measure/resources/os_lib_helper_methods.rb +399 -0
  31. data/lib/measures/openstudio_extension_test_measure/tests/OpenStudioExtensionTestMeasure_Test.rb +75 -0
  32. data/lib/openstudio/extension.rb +220 -0
  33. data/lib/openstudio/extension/core/CreateResults.rb +879 -0
  34. data/lib/openstudio/extension/core/check_air_sys_temps.rb +190 -0
  35. data/lib/openstudio/extension/core/check_calibration.rb +155 -0
  36. data/lib/openstudio/extension/core/check_cond_zns.rb +84 -0
  37. data/lib/openstudio/extension/core/check_domestic_hot_water.rb +334 -0
  38. data/lib/openstudio/extension/core/check_envelope_conductance.rb +453 -0
  39. data/lib/openstudio/extension/core/check_eui_by_end_use.rb +162 -0
  40. data/lib/openstudio/extension/core/check_eui_reasonableness.rb +135 -0
  41. data/lib/openstudio/extension/core/check_fan_pwr.rb +98 -0
  42. data/lib/openstudio/extension/core/check_internal_loads.rb +393 -0
  43. data/lib/openstudio/extension/core/check_mech_sys_capacity.rb +226 -0
  44. data/lib/openstudio/extension/core/check_mech_sys_efficiency.rb +326 -0
  45. data/lib/openstudio/extension/core/check_mech_sys_part_load_eff.rb +464 -0
  46. data/lib/openstudio/extension/core/check_mech_sys_type.rb +139 -0
  47. data/lib/openstudio/extension/core/check_part_loads.rb +451 -0
  48. data/lib/openstudio/extension/core/check_placeholder.rb +75 -0
  49. data/lib/openstudio/extension/core/check_plant_cap.rb +123 -0
  50. data/lib/openstudio/extension/core/check_plant_temps.rb +159 -0
  51. data/lib/openstudio/extension/core/check_plenum_loads.rb +87 -0
  52. data/lib/openstudio/extension/core/check_pump_pwr.rb +108 -0
  53. data/lib/openstudio/extension/core/check_sch_coord.rb +241 -0
  54. data/lib/openstudio/extension/core/check_schedules.rb +311 -0
  55. data/lib/openstudio/extension/core/check_simultaneous_heating_and_cooling.rb +158 -0
  56. data/lib/openstudio/extension/core/check_supply_air_and_thermostat_temp_difference.rb +148 -0
  57. data/lib/openstudio/extension/core/check_weather_files.rb +132 -0
  58. data/lib/openstudio/extension/core/deer_vintages.rb +311 -0
  59. data/lib/openstudio/extension/core/os_lib_aedg_measures.rb +491 -0
  60. data/lib/openstudio/extension/core/os_lib_cofee.rb +259 -0
  61. data/lib/openstudio/extension/core/os_lib_constructions.rb +378 -0
  62. data/lib/openstudio/extension/core/os_lib_geometry.rb +1022 -0
  63. data/lib/openstudio/extension/core/os_lib_helper_methods.rb +399 -0
  64. data/lib/openstudio/extension/core/os_lib_hvac.rb +2171 -0
  65. data/lib/openstudio/extension/core/os_lib_lighting_and_equipment.rb +214 -0
  66. data/lib/openstudio/extension/core/os_lib_model_generation.rb +817 -0
  67. data/lib/openstudio/extension/core/os_lib_model_simplification.rb +1049 -0
  68. data/lib/openstudio/extension/core/os_lib_outdoorair_and_infiltration.rb +165 -0
  69. data/lib/openstudio/extension/core/os_lib_reporting.rb +4652 -0
  70. data/lib/openstudio/extension/core/os_lib_reporting_qaqc.rb +200 -0
  71. data/lib/openstudio/extension/core/os_lib_schedules.rb +963 -0
  72. data/lib/openstudio/extension/rake_task.rb +149 -0
  73. data/lib/openstudio/extension/runner.rb +644 -0
  74. data/lib/openstudio/extension/version.rb +40 -0
  75. data/openstudio-extension.gemspec +20 -15
  76. metadata +150 -14
  77. data/.travis.yml +0 -7
  78. data/lib/OpenStudio/Extension/rake_task.rb +0 -84
  79. data/lib/OpenStudio/Extension/version.rb +0 -33
  80. 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