openstudio-extension 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.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,190 @@
|
|
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
|
+
# Check the air loop and zone operational vs. sizing temperatures
|
38
|
+
# and make sure everything is coordinated. This identifies problems
|
39
|
+
# caused by sizing to one set of conditions and operating at a different set.
|
40
|
+
def check_air_sys_temps(category, target_standard, max_sizing_temp_delta = 0.1, name_only = false)
|
41
|
+
# summary of the check
|
42
|
+
check_elems = OpenStudio::AttributeVector.new
|
43
|
+
check_elems << OpenStudio::Attribute.new('name', 'Air System Temperatures')
|
44
|
+
check_elems << OpenStudio::Attribute.new('category', category)
|
45
|
+
check_elems << OpenStudio::Attribute.new('description', 'Check that air system sizing and operation temperatures are coordinated.')
|
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
|
+
std = Standard.build(target_standard)
|
57
|
+
|
58
|
+
begin
|
59
|
+
# Check each air loop in the model
|
60
|
+
@model.getAirLoopHVACs.sort.each do |airloop|
|
61
|
+
loop_name = airloop.name.to_s
|
62
|
+
|
63
|
+
# Get the central heating and cooling SAT for sizing
|
64
|
+
sizing_system = airloop.sizingSystem
|
65
|
+
loop_siz_htg_f = OpenStudio.convert(sizing_system.centralHeatingDesignSupplyAirTemperature, 'C', 'F').get
|
66
|
+
loop_siz_clg_f = OpenStudio.convert(sizing_system.centralCoolingDesignSupplyAirTemperature, 'C', 'F').get
|
67
|
+
|
68
|
+
# Compare air loop to zone sizing temperatures
|
69
|
+
airloop.thermalZones.each do |zone|
|
70
|
+
# If this zone has a reheat terminal, get the reheat temp for comparison
|
71
|
+
reheat_op_f = nil
|
72
|
+
reheat_zone = false
|
73
|
+
zone.equipment.each do |equip|
|
74
|
+
obj_type = equip.iddObjectType.valueName.to_s
|
75
|
+
case obj_type
|
76
|
+
when 'OS_AirTerminal_SingleDuct_ConstantVolume_Reheat'
|
77
|
+
term = equip.to_AirTerminalSingleDuctConstantVolumeReheat.get
|
78
|
+
reheat_op_f = OpenStudio.convert(term.maximumReheatAirTemperature, 'C', 'F').get
|
79
|
+
reheat_zone = true
|
80
|
+
when 'OS_AirTerminal_SingleDuct_VAV_HeatAndCool_Reheat'
|
81
|
+
term = equip.to_AirTerminalSingleDuctVAVHeatAndCoolReheat.get
|
82
|
+
reheat_op_f = OpenStudio.convert(term.maximumReheatAirTemperature, 'C', 'F').get
|
83
|
+
reheat_zone = true
|
84
|
+
when 'OS_AirTerminal_SingleDuct_VAV_Reheat'
|
85
|
+
term = equip.to_AirTerminalSingleDuctVAVReheat.get
|
86
|
+
reheat_op_f = OpenStudio.convert(term.maximumReheatAirTemperature, 'C', 'F').get
|
87
|
+
reheat_zone = true
|
88
|
+
when 'OS_AirTerminal_SingleDuct_ParallelPIU_Reheat'
|
89
|
+
term = equip.to_AirTerminalSingleDuctParallelPIUReheat.get
|
90
|
+
# reheat_op_f = # Not an OpenStudio input
|
91
|
+
reheat_zone = true
|
92
|
+
when 'OS_AirTerminal_SingleDuct_SeriesPIU_Reheat'
|
93
|
+
term = equip.to_AirTerminalSingleDuctSeriesPIUReheat.get
|
94
|
+
# reheat_op_f = # Not an OpenStudio input
|
95
|
+
reheat_zone = true
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Get the zone heating and cooling SAT for sizing
|
100
|
+
sizing_zone = zone.sizingZone
|
101
|
+
zone_siz_htg_f = OpenStudio.convert(sizing_zone.zoneHeatingDesignSupplyAirTemperature, 'C', 'F').get
|
102
|
+
zone_siz_clg_f = OpenStudio.convert(sizing_zone.zoneCoolingDesignSupplyAirTemperature, 'C', 'F').get
|
103
|
+
|
104
|
+
# Check cooling temperatures
|
105
|
+
if ((loop_siz_clg_f - zone_siz_clg_f) / loop_siz_clg_f).abs > max_sizing_temp_delta
|
106
|
+
check_elems << OpenStudio::Attribute.new('flag', "For #{zone.name}, the sizing for the air loop is done with a cooling supply air temp of #{loop_siz_clg_f.round(2)}F, but the sizing for the zone is done with a cooling supply air temp of #{zone_siz_clg_f.round(2)}F. These are farther apart than the acceptable #{(max_sizing_temp_delta * 100.0).round(2)}% difference.")
|
107
|
+
end
|
108
|
+
|
109
|
+
# Check heating temperatures
|
110
|
+
if reheat_zone && reheat_op_f
|
111
|
+
if ((reheat_op_f - zone_siz_htg_f) / reheat_op_f).abs > max_sizing_temp_delta
|
112
|
+
check_elems << OpenStudio::Attribute.new('flag', "For #{zone.name}, the reheat air temp is set to #{reheat_op_f.round(2)}F, but the sizing for the zone is done with a heating supply air temp of #{zone_siz_htg_f.round(2)}F. These are farther apart than the acceptable #{(max_sizing_temp_delta * 100.0).round(2)}% difference.")
|
113
|
+
end
|
114
|
+
elsif reheat_zone && !reheat_op_f
|
115
|
+
# Don't perform the check if it is a reheat zone but the reheat temperature
|
116
|
+
# is not available from the model inputs
|
117
|
+
else
|
118
|
+
if ((loop_siz_htg_f - zone_siz_htg_f) / loop_siz_htg_f).abs > max_sizing_temp_delta
|
119
|
+
check_elems << OpenStudio::Attribute.new('flag', "For #{zone.name}, the sizing for the air loop is done with a heating supply air temp of #{loop_siz_htg_f.round(2)}F, but the sizing for the zone is done with a heating supply air temp of #{zone_siz_htg_f.round(2)}F. These are farther apart than the acceptable #{(max_sizing_temp_delta * 100.0).round(2)}% difference.")
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Determine the min and max operational temperatures
|
125
|
+
loop_op_min_f = nil
|
126
|
+
loop_op_max_f = nil
|
127
|
+
airloop.supplyOutletNode.setpointManagers.each do |spm|
|
128
|
+
obj_type = spm.iddObjectType.valueName.to_s
|
129
|
+
case obj_type
|
130
|
+
when 'OS_SetpointManager_Scheduled'
|
131
|
+
sch = spm.to_SetpointManagerScheduled.get.schedule
|
132
|
+
if sch.to_ScheduleRuleset.is_initialized
|
133
|
+
min_c = std.schedule_ruleset_annual_min_max_value(sch.to_ScheduleRuleset.get)['min']
|
134
|
+
max_c = std.schedule_ruleset_annual_min_max_value(sch.to_ScheduleRuleset.get)['max']
|
135
|
+
elsif sch.to_ScheduleConstant.is_initialized
|
136
|
+
min_c = std.schedule_constant_annual_min_max_value(sch.to_ScheduleConstant.get)['min']
|
137
|
+
max_c = std.schedule_constant_annual_min_max_value(sch.to_ScheduleConstant.get)['max']
|
138
|
+
else
|
139
|
+
next
|
140
|
+
end
|
141
|
+
loop_op_min_f = OpenStudio.convert(min_c, 'C', 'F').get
|
142
|
+
loop_op_max_f = OpenStudio.convert(max_c, 'C', 'F').get
|
143
|
+
when 'OS_SetpointManager_SingleZoneReheat'
|
144
|
+
spm = spm.to_SetpointManagerSingleZoneReheat.get
|
145
|
+
loop_op_min_f = OpenStudio.convert(spm.minimumSupplyAirTemperature, 'C', 'F').get
|
146
|
+
loop_op_max_f = OpenStudio.convert(spm.maximumSupplyAirTemperature, 'C', 'F').get
|
147
|
+
when 'OS_SetpointManager_Warmest'
|
148
|
+
spm = spm.to_SetpointManagerSingleZoneReheat.get
|
149
|
+
loop_op_min_f = OpenStudio.convert(spm.minimumSetpointTemperature, 'C', 'F').get
|
150
|
+
loop_op_max_f = OpenStudio.convert(spm.maximumSetpointTemperature, 'C', 'F').get
|
151
|
+
when 'OS_SetpointManager_WarmestTemperatureFlow'
|
152
|
+
spm = spm.to_SetpointManagerSingleZoneReheat.get
|
153
|
+
loop_op_min_f = OpenStudio.convert(spm.minimumSetpointTemperature, 'C', 'F').get
|
154
|
+
loop_op_max_f = OpenStudio.convert(spm.maximumSetpointTemperature, 'C', 'F').get
|
155
|
+
else
|
156
|
+
next # Only check the commonly used setpoint managers
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Compare air loop sizing temperatures to operational temperatures
|
161
|
+
|
162
|
+
# Cooling
|
163
|
+
if loop_op_min_f
|
164
|
+
if ((loop_op_min_f - loop_siz_clg_f) / loop_op_min_f).abs > max_sizing_temp_delta
|
165
|
+
check_elems << OpenStudio::Attribute.new('flag', "For #{airloop.name}, the sizing is done with a cooling supply air temp of #{loop_siz_clg_f.round(2)}F, but the setpoint manager controlling the loop operates down to #{loop_op_min_f.round(2)}F. These are farther apart than the acceptable #{(max_sizing_temp_delta * 100.0).round(2)}% difference.")
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Heating
|
170
|
+
if loop_op_max_f
|
171
|
+
if ((loop_op_max_f - loop_siz_htg_f) / loop_op_max_f).abs > max_sizing_temp_delta
|
172
|
+
check_elems << OpenStudio::Attribute.new('flag', "For #{airloop.name}, the sizing is done with a heating supply air temp of #{loop_siz_htg_f.round(2)}F, but the setpoint manager controlling the loop operates up to #{loop_op_max_f.round(2)}F. These are farther apart than the acceptable #{(max_sizing_temp_delta * 100.0).round(2)}% difference.")
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
rescue StandardError => e
|
177
|
+
# brief description of ruby error
|
178
|
+
check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
|
179
|
+
|
180
|
+
# backtrace of ruby error for diagnostic use
|
181
|
+
if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
|
182
|
+
end
|
183
|
+
|
184
|
+
# add check_elms to new attribute
|
185
|
+
check_elem = OpenStudio::Attribute.new('check', check_elems)
|
186
|
+
|
187
|
+
return check_elem
|
188
|
+
# note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,155 @@
|
|
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
|
+
# Check the calibration against utility bills.
|
38
|
+
def check_calibration(category, target_standard, max_nmbe, max_cvrmse, name_only = false)
|
39
|
+
# summary of the check
|
40
|
+
check_elems = OpenStudio::AttributeVector.new
|
41
|
+
check_elems << OpenStudio::Attribute.new('name', 'Calibration')
|
42
|
+
check_elems << OpenStudio::Attribute.new('category', category)
|
43
|
+
check_elems << OpenStudio::Attribute.new('description', 'Check that the model is calibrated to the utility bills.')
|
44
|
+
|
45
|
+
# stop here if only name is requested this is used to populate display name for arguments
|
46
|
+
if name_only == true
|
47
|
+
results = []
|
48
|
+
check_elems.each do |elem|
|
49
|
+
results << elem.valueAsString
|
50
|
+
end
|
51
|
+
return results
|
52
|
+
end
|
53
|
+
|
54
|
+
std = Standard.build(target_standard)
|
55
|
+
|
56
|
+
begin
|
57
|
+
# Check that there are utility bills in the model
|
58
|
+
if @model.getUtilityBills.empty?
|
59
|
+
check_elems << OpenStudio::Attribute.new('flag', 'Model contains no utility bills, cannot check calibration.')
|
60
|
+
end
|
61
|
+
|
62
|
+
# Check the calibration for each utility bill
|
63
|
+
@model.getUtilityBills.each do |bill|
|
64
|
+
bill_name = bill.name.get
|
65
|
+
fuel = bill.fuelType.valueDescription
|
66
|
+
|
67
|
+
# Consumption
|
68
|
+
|
69
|
+
# NMBE
|
70
|
+
if bill.NMBE.is_initialized
|
71
|
+
nmbe = bill.NMBE.get
|
72
|
+
if nmbe > max_nmbe || nmbe < -1.0 * max_nmbe
|
73
|
+
check_elems << OpenStudio::Attribute.new('flag', "For the #{fuel} bill called #{bill_name}, the consumption NMBE of #{nmbe.round(1)}% is outside the limit of +/- #{max_nmbe}%, so the model is not calibrated.")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# CVRMSE
|
78
|
+
if bill.CVRMSE.is_initialized
|
79
|
+
cvrmse = bill.CVRMSE.get
|
80
|
+
if cvrmse > max_cvrmse
|
81
|
+
check_elems << OpenStudio::Attribute.new('flag', "For the #{fuel} bill called #{bill_name}, the consumption CVRMSE of #{cvrmse.round(1)}% is above the limit of #{max_cvrmse}%, so the model is not calibrated.")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Peak Demand (for some fuels)
|
86
|
+
if bill.peakDemandUnitConversionFactor.is_initialized
|
87
|
+
peak_conversion = bill.peakDemandUnitConversionFactor.get
|
88
|
+
|
89
|
+
# Get modeled and actual values
|
90
|
+
actual_vals = []
|
91
|
+
modeled_vals = []
|
92
|
+
bill.billingPeriods.each do |billing_period|
|
93
|
+
actual_peak = billing_period.peakDemand
|
94
|
+
if actual_peak.is_initialized
|
95
|
+
actual_vals << actual_peak.get
|
96
|
+
end
|
97
|
+
|
98
|
+
modeled_peak = billing_period.modelPeakDemand
|
99
|
+
if modeled_peak.is_initialized
|
100
|
+
modeled_vals << modeled_peak.get
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Check that both arrays are the same size
|
105
|
+
unless actual_vals.size == modeled_vals.size
|
106
|
+
check_elems << OpenStudio::Attribute.new('flag', "For the #{fuel} bill called #{bill_name}, cannot get the same number of modeled and actual peak demand values, cannot check peak demand calibration.")
|
107
|
+
end
|
108
|
+
|
109
|
+
# NMBE and CMRMSE
|
110
|
+
ysum = 0
|
111
|
+
sum_err = 0
|
112
|
+
squared_err = 0
|
113
|
+
n = actual_vals.size
|
114
|
+
|
115
|
+
actual_vals.each_with_index do |actual, i|
|
116
|
+
modeled = modeled_vals[i]
|
117
|
+
actual *= peak_conversion # Convert actual demand to model units
|
118
|
+
ysum += actual
|
119
|
+
sum_err += (actual - modeled)
|
120
|
+
squared_err += (actual - modeled)**2
|
121
|
+
end
|
122
|
+
|
123
|
+
if n > 1
|
124
|
+
ybar = ysum / n
|
125
|
+
|
126
|
+
# NMBE
|
127
|
+
demand_nmbe = 100.0 * (sum_err / (n - 1)) / ybar
|
128
|
+
if demand_nmbe > max_nmbe || demand_nmbe < -1.0 * max_nmbe
|
129
|
+
check_elems << OpenStudio::Attribute.new('flag', "For the #{fuel} bill called #{bill_name}, the peak demand NMBE of #{demand_nmbe.round(1)}% is outside the limit of +/- #{max_nmbe}%, so the model is not calibrated.")
|
130
|
+
end
|
131
|
+
|
132
|
+
# CVRMSE
|
133
|
+
demand_cvrmse = 100.0 * (squared_err / (n - 1))**0.5 / ybar
|
134
|
+
if demand_cvrmse > max_cvrmse
|
135
|
+
check_elems << OpenStudio::Attribute.new('flag', "For the #{fuel} bill called #{bill_name}, the peak demand CVRMSE of #{demand_cvrmse.round(1)}% is above the limit of #{max_cvrmse}%, so the model is not calibrated.")
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
141
|
+
rescue StandardError => e
|
142
|
+
# brief description of ruby error
|
143
|
+
check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
|
144
|
+
|
145
|
+
# backtrace of ruby error for diagnostic use
|
146
|
+
if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
|
147
|
+
end
|
148
|
+
|
149
|
+
# add check_elms to new attribute
|
150
|
+
check_elem = OpenStudio::Attribute.new('check', check_elems)
|
151
|
+
|
152
|
+
return check_elem
|
153
|
+
# note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,84 @@
|
|
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
|
+
# Check that all zones with people are conditioned (have a thermostat with setpoints)
|
38
|
+
def check_cond_zns(category, target_standard, name_only = false)
|
39
|
+
# summary of the check
|
40
|
+
check_elems = OpenStudio::AttributeVector.new
|
41
|
+
check_elems << OpenStudio::Attribute.new('name', 'Conditioned Zones')
|
42
|
+
check_elems << OpenStudio::Attribute.new('category', category)
|
43
|
+
check_elems << OpenStudio::Attribute.new('description', 'Check that all zones with people have thermostats.')
|
44
|
+
|
45
|
+
# stop here if only name is requested this is used to populate display name for arguments
|
46
|
+
if name_only == true
|
47
|
+
results = []
|
48
|
+
check_elems.each do |elem|
|
49
|
+
results << elem.valueAsString
|
50
|
+
end
|
51
|
+
return results
|
52
|
+
end
|
53
|
+
|
54
|
+
std = Standard.build(target_standard)
|
55
|
+
|
56
|
+
begin
|
57
|
+
@model.getThermalZones.each do |zone|
|
58
|
+
# Only check zones that have people
|
59
|
+
num_ppl = zone.numberOfPeople
|
60
|
+
next unless zone.numberOfPeople > 0
|
61
|
+
|
62
|
+
# Check that the zone is heated (at a minimum)
|
63
|
+
# by checking that the heating setpoint is at least 41F.
|
64
|
+
# Sometimes people include thermostats but set the setpoints
|
65
|
+
# such that the system never comes on. This check attempts to catch that.
|
66
|
+
unless std.thermal_zone_heated?(zone)
|
67
|
+
check_elems << OpenStudio::Attribute.new('flag', "#{zone.name} has #{num_ppl} people but is not heated. Zones containing people are expected to be conditioned, heated-only at a minimum. Heating setpoint must be at least 41F to be considered heated.")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
rescue StandardError => e
|
71
|
+
# brief description of ruby error
|
72
|
+
check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
|
73
|
+
|
74
|
+
# backtrace of ruby error for diagnostic use
|
75
|
+
if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
|
76
|
+
end
|
77
|
+
|
78
|
+
# add check_elms to new attribute
|
79
|
+
check_elem = OpenStudio::Attribute.new('check', check_elems)
|
80
|
+
|
81
|
+
return check_elem
|
82
|
+
# note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,334 @@
|
|
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_domestic_hot_water(category, target_standard, min_pass, max_pass, name_only = false)
|
41
|
+
# TODO: - could expose meal turnover and people per unit for res and hotel into arguments
|
42
|
+
|
43
|
+
# summary of the check
|
44
|
+
check_elems = OpenStudio::AttributeVector.new
|
45
|
+
check_elems << OpenStudio::Attribute.new('name', 'Domestic Hot Water')
|
46
|
+
check_elems << OpenStudio::Attribute.new('category', category)
|
47
|
+
if target_standard == 'ICC IECC 2015'
|
48
|
+
check_elems << OpenStudio::Attribute.new('description', 'Check service water heating consumption against Table R405.5.2(1) in ICC IECC 2015 Residential Provisions.')
|
49
|
+
else
|
50
|
+
check_elems << OpenStudio::Attribute.new('description', 'Check against the 2011 ASHRAE Handbook - HVAC Applications, Table 7 section 50.14.')
|
51
|
+
end
|
52
|
+
|
53
|
+
# stop here if only name is requested this is used to populate display name for arguments
|
54
|
+
if name_only == true
|
55
|
+
results = []
|
56
|
+
check_elems.each do |elem|
|
57
|
+
results << elem.valueAsString
|
58
|
+
end
|
59
|
+
return results
|
60
|
+
end
|
61
|
+
|
62
|
+
# Versions of OpenStudio greater than 2.4.0 use a modified version of
|
63
|
+
# openstudio-standards with different method calls. These methods
|
64
|
+
# require a "Standard" object instead of the standard being passed into method calls.
|
65
|
+
# This Standard object is used throughout the QAQC check.
|
66
|
+
if OpenStudio::VersionString.new(OpenStudio.openStudioVersion) < OpenStudio::VersionString.new('2.4.3')
|
67
|
+
use_old_gem_code = true
|
68
|
+
else
|
69
|
+
use_old_gem_code = false
|
70
|
+
std = Standard.build(target_standard)
|
71
|
+
end
|
72
|
+
|
73
|
+
begin
|
74
|
+
# loop through water_use_equipment
|
75
|
+
service_water_consumption_daily_avg_gal = 0.0
|
76
|
+
@model.getWaterUseEquipments.each do |water_use_equipment|
|
77
|
+
# get peak flow rate from def
|
78
|
+
peak_flow_rate_si = water_use_equipment.waterUseEquipmentDefinition.peakFlowRate
|
79
|
+
source_units = 'm^3/s'
|
80
|
+
target_units = 'gal/min'
|
81
|
+
peak_flow_rate_ip = OpenStudio.convert(peak_flow_rate_si, source_units, target_units).get
|
82
|
+
|
83
|
+
# get value from flow rate schedule
|
84
|
+
if water_use_equipment.flowRateFractionSchedule.is_initialized
|
85
|
+
# get annual equiv for model schedule
|
86
|
+
schedule_inst = water_use_equipment.flowRateFractionSchedule.get
|
87
|
+
if schedule_inst.to_ScheduleRuleset.is_initialized
|
88
|
+
if use_old_gem_code
|
89
|
+
annual_equiv_flow_rate = schedule_inst.to_ScheduleRuleset.get.annual_equivalent_full_load_hrs
|
90
|
+
else
|
91
|
+
annual_equiv_flow_rate = std.schedule_ruleset_annual_equivalent_full_load_hrs(schedule_inst.to_ScheduleRuleset.get)
|
92
|
+
end
|
93
|
+
elsif schedule_inst.to_ScheduleConstant.is_initialized
|
94
|
+
if use_old_gem_code
|
95
|
+
annual_equiv_flow_rate = schedule_inst.to_ScheduleConstant.get.annual_equivalent_full_load_hrs
|
96
|
+
else
|
97
|
+
annual_equiv_flow_rate = std.schedule_constant_annual_equivalent_full_load_hrs(schedule_inst.to_ScheduleConstant.get)
|
98
|
+
end
|
99
|
+
else
|
100
|
+
check_elems << OpenStudio::Attribute.new('flag', "#{schedule_inst.name} isn't a Ruleset or Constant schedule. Can't calculate annual equivalent full load hours.")
|
101
|
+
next
|
102
|
+
end
|
103
|
+
else
|
104
|
+
# issue flag
|
105
|
+
check_elems << OpenStudio::Attribute.new('flag', "#{water_use_equipment.name} doesn't have a schedule. Can't identify hot water consumption.")
|
106
|
+
next
|
107
|
+
end
|
108
|
+
|
109
|
+
# add to global service water consumpiton value
|
110
|
+
service_water_consumption_daily_avg_gal += 60.0 * peak_flow_rate_ip * annual_equiv_flow_rate / 365.0
|
111
|
+
end
|
112
|
+
|
113
|
+
if target_standard == 'ICC IECC 2015'
|
114
|
+
|
115
|
+
num_people = 0.0
|
116
|
+
@model.getSpaceTypes.each do |space_type|
|
117
|
+
next if !space_type.standardsSpaceType.is_initialized
|
118
|
+
next if space_type.standardsSpaceType.get != 'Apartment' # currently only supports midrise apt space type
|
119
|
+
space_type_floor_area = space_type.floorArea
|
120
|
+
space_type_num_people = space_type.getNumberOfPeople(space_type_floor_area)
|
121
|
+
num_people += space_type_num_people
|
122
|
+
end
|
123
|
+
|
124
|
+
# lookup target gal/day for the building
|
125
|
+
bedrooms_per_unit = 2.0 # assumption
|
126
|
+
num_units = num_people / 2.5 # Avg 2.5 units per person.
|
127
|
+
if use_old_gem_code
|
128
|
+
target_consumption = @model.find_icc_iecc_2015_hot_water_demand(num_units, bedrooms_per_unit)
|
129
|
+
else
|
130
|
+
target_consumption = std.model_find_icc_iecc_2015_hot_water_demand(@model, num_units, bedrooms_per_unit)
|
131
|
+
end
|
132
|
+
|
133
|
+
else # only other path for now is 90.1-2013
|
134
|
+
|
135
|
+
# get building type
|
136
|
+
building_type = ''
|
137
|
+
if @model.getBuilding.standardsBuildingType.is_initialized
|
138
|
+
building_type = @model.getBuilding.standardsBuildingType.get
|
139
|
+
end
|
140
|
+
|
141
|
+
# lookup data from standards
|
142
|
+
if use_old_gem_code
|
143
|
+
ashrae_hot_water_demand = @model.find_ashrae_hot_water_demand
|
144
|
+
else
|
145
|
+
ashrae_hot_water_demand = std.model_find_ashrae_hot_water_demand(@model)
|
146
|
+
end
|
147
|
+
|
148
|
+
# building type specific logic for water consumption
|
149
|
+
# todo - update test to exercise various building types
|
150
|
+
if !ashrae_hot_water_demand.empty?
|
151
|
+
|
152
|
+
if building_type == 'FullServiceRestaurant'
|
153
|
+
num_people_hours = 0.0
|
154
|
+
@model.getSpaceTypes.each do |space_type|
|
155
|
+
next if !space_type.standardsSpaceType.is_initialized
|
156
|
+
next if space_type.standardsSpaceType.get != 'Dining'
|
157
|
+
space_type_floor_area = space_type.floorArea
|
158
|
+
|
159
|
+
space_type_num_people_hours = 0.0
|
160
|
+
# loop through peole instances
|
161
|
+
space_type.peoples.each do |inst|
|
162
|
+
inst_num_people = inst.getNumberOfPeople(space_type_floor_area)
|
163
|
+
inst_schedule = inst.numberofPeopleSchedule.get # sim will fail prior to this if doesn't have it
|
164
|
+
|
165
|
+
if inst_schedule.to_ScheduleRuleset.is_initialized
|
166
|
+
if use_old_gem_code
|
167
|
+
annual_equiv_flow_rate = inst_schedule.to_ScheduleRuleset.get.annual_equivalent_full_load_hrs
|
168
|
+
else
|
169
|
+
annual_equiv_flow_rate = std.schedule_ruleset_annual_equivalent_full_load_hrs(inst_schedule.to_ScheduleRuleset.get)
|
170
|
+
end
|
171
|
+
elsif inst_schedule.to_ScheduleConstant.is_initialized
|
172
|
+
if use_old_gem_code
|
173
|
+
annual_equiv_flow_rate = inst_schedule.to_ScheduleConstant.get.annual_equivalent_full_load_hrs
|
174
|
+
else
|
175
|
+
annual_equiv_flow_rate = std.schedule_constant_annual_equivalent_full_load_hrs(inst_schedule.to_ScheduleConstant.get)
|
176
|
+
end
|
177
|
+
else
|
178
|
+
check_elems << OpenStudio::Attribute.new('flag', "#{inst_schedule.name} isn't a Ruleset or Constant schedule. Can't calculate annual equivalent full load hours.")
|
179
|
+
annual_equiv_flow_rate = 0.0
|
180
|
+
end
|
181
|
+
|
182
|
+
inst_num_people_horus = annual_equiv_flow_rate * inst_num_people
|
183
|
+
space_type_num_people_hours += inst_num_people_horus
|
184
|
+
end
|
185
|
+
|
186
|
+
num_people_hours += space_type_num_people_hours
|
187
|
+
end
|
188
|
+
num_meals = num_people_hours / 365.0 * 1.5 # 90 minute meal
|
189
|
+
target_consumption = num_meals * ashrae_hot_water_demand.first[:avg_day_unit]
|
190
|
+
|
191
|
+
elsif ['LargeHotel', 'SmallHotel'].include? building_type
|
192
|
+
num_people = 0.0
|
193
|
+
@model.getSpaceTypes.each do |space_type|
|
194
|
+
next if !space_type.standardsSpaceType.is_initialized
|
195
|
+
next if space_type.standardsSpaceType.get != 'GuestRoom'
|
196
|
+
space_type_floor_area = space_type.floorArea
|
197
|
+
space_type_num_people = space_type.getNumberOfPeople(space_type_floor_area)
|
198
|
+
num_people += space_type_num_people
|
199
|
+
end
|
200
|
+
|
201
|
+
# find best fit from returned results
|
202
|
+
num_units = num_people / 2.0 # 2 people per room design load, not typical occupancy
|
203
|
+
avg_day_unit = nil
|
204
|
+
fit = nil
|
205
|
+
ashrae_hot_water_demand.each do |block|
|
206
|
+
if fit.nil?
|
207
|
+
avg_day_unit = block[:avg_day_unit]
|
208
|
+
fit = (avg_day_unit - block[:block]).abs
|
209
|
+
elsif (avg_day_unit - block[:block]).abs - fit
|
210
|
+
avg_day_unit = block[:avg_day_unit]
|
211
|
+
fit = (avg_day_unit - block[:block]).abs
|
212
|
+
end
|
213
|
+
end
|
214
|
+
target_consumption = num_units * avg_day_unit
|
215
|
+
|
216
|
+
elsif building_type == 'MidriseApartment'
|
217
|
+
num_people = 0.0
|
218
|
+
@model.getSpaceTypes.each do |space_type|
|
219
|
+
next if !space_type.standardsSpaceType.is_initialized
|
220
|
+
next if space_type.standardsSpaceType.get != 'Apartment'
|
221
|
+
space_type_floor_area = space_type.floorArea
|
222
|
+
space_type_num_people = space_type.getNumberOfPeople(space_type_floor_area)
|
223
|
+
num_people += space_type_num_people
|
224
|
+
end
|
225
|
+
|
226
|
+
# find best fit from returned results
|
227
|
+
num_units = num_people / 2.5 # Avg 2.5 units per person.
|
228
|
+
avg_day_unit = nil
|
229
|
+
fit = nil
|
230
|
+
ashrae_hot_water_demand.each do |block|
|
231
|
+
if fit.nil?
|
232
|
+
avg_day_unit = block[:avg_day_unit]
|
233
|
+
fit = (avg_day_unit - block[:block]).abs
|
234
|
+
elsif (avg_day_unit - block[:block]).abs - fit
|
235
|
+
avg_day_unit = block[:avg_day_unit]
|
236
|
+
fit = (avg_day_unit - block[:block]).abs
|
237
|
+
end
|
238
|
+
end
|
239
|
+
target_consumption = num_units * avg_day_unit
|
240
|
+
|
241
|
+
elsif ['Office', 'LargeOffice', 'MediumOffice', 'SmallOffice'].include? building_type
|
242
|
+
num_people = @model.getBuilding.numberOfPeople
|
243
|
+
target_consumption = num_people * ashrae_hot_water_demand.first[:avg_day_unit]
|
244
|
+
elsif building_type == 'PrimarySchool'
|
245
|
+
num_people = 0.0
|
246
|
+
@model.getSpaceTypes.each do |space_type|
|
247
|
+
next if !space_type.standardsSpaceType.is_initialized
|
248
|
+
next if space_type.standardsSpaceType.get != 'Classroom'
|
249
|
+
space_type_floor_area = space_type.floorArea
|
250
|
+
space_type_num_people = space_type.getNumberOfPeople(space_type_floor_area)
|
251
|
+
num_people += space_type_num_people
|
252
|
+
end
|
253
|
+
target_consumption = num_people * ashrae_hot_water_demand.first[:avg_day_unit]
|
254
|
+
elsif building_type == 'QuickServiceRestaurant'
|
255
|
+
num_people_hours = 0.0
|
256
|
+
@model.getSpaceTypes.each do |space_type|
|
257
|
+
next if !space_type.standardsSpaceType.is_initialized
|
258
|
+
next if space_type.standardsSpaceType.get != 'Dining'
|
259
|
+
space_type_floor_area = space_type.floorArea
|
260
|
+
|
261
|
+
space_type_num_people_hours = 0.0
|
262
|
+
# loop through peole instances
|
263
|
+
space_type.peoples.each do |inst|
|
264
|
+
inst_num_people = inst.getNumberOfPeople(space_type_floor_area)
|
265
|
+
inst_schedule = inst.numberofPeopleSchedule.get # sim will fail prior to this if doesn't have it
|
266
|
+
|
267
|
+
if inst_schedule.to_ScheduleRuleset.is_initialized
|
268
|
+
if use_old_gem_code
|
269
|
+
annual_equiv_flow_rate = inst_schedule.to_ScheduleRuleset.get.annual_equivalent_full_load_hrs
|
270
|
+
else
|
271
|
+
annual_equiv_flow_rate = std.schedule_ruleset_annual_equivalent_full_load_hrs(inst_schedule.to_ScheduleRuleset.get)
|
272
|
+
end
|
273
|
+
elsif inst_schedule.to_ScheduleConstant.is_initialized
|
274
|
+
if use_old_gem_code
|
275
|
+
annual_equiv_flow_rate = inst_schedule.to_ScheduleConstant.get.annual_equivalent_full_load_hrs
|
276
|
+
else
|
277
|
+
annual_equiv_flow_rate = std.schedule_constant_annual_equivalent_full_load_hrs(inst_schedule.to_ScheduleConstant.get)
|
278
|
+
end
|
279
|
+
else
|
280
|
+
check_elems << OpenStudio::Attribute.new('flag', "#{inst_schedule.name} isn't a Ruleset or Constant schedule. Can't calculate annual equivalent full load hours.")
|
281
|
+
annual_equiv_flow_rate = 0.0
|
282
|
+
end
|
283
|
+
|
284
|
+
inst_num_people_horus = annual_equiv_flow_rate * inst_num_people
|
285
|
+
space_type_num_people_hours += inst_num_people_horus
|
286
|
+
end
|
287
|
+
|
288
|
+
num_people_hours += space_type_num_people_hours
|
289
|
+
end
|
290
|
+
num_meals = num_people_hours / 365.0 * 0.5 # 30 minute leal
|
291
|
+
# todo - add logic to address drive through traffic
|
292
|
+
target_consumption = num_meals * ashrae_hot_water_demand.first[:avg_day_unit]
|
293
|
+
|
294
|
+
elsif building_type == 'SecondarySchool'
|
295
|
+
num_people = 0.0
|
296
|
+
@model.getSpaceTypes.each do |space_type|
|
297
|
+
next if !space_type.standardsSpaceType.is_initialized
|
298
|
+
next if space_type.standardsSpaceType.get != 'Classroom'
|
299
|
+
space_type_floor_area = space_type.floorArea
|
300
|
+
space_type_num_people = space_type.getNumberOfPeople(space_type_floor_area)
|
301
|
+
num_people += space_type_num_people
|
302
|
+
end
|
303
|
+
target_consumption = num_people * ashrae_hot_water_demand.first[:avg_day_unit]
|
304
|
+
else
|
305
|
+
check_elems << OpenStudio::Attribute.new('flag', "No rule of thumb values exist for #{building_type}. Hot water consumption was not checked.")
|
306
|
+
end
|
307
|
+
|
308
|
+
else
|
309
|
+
check_elems << OpenStudio::Attribute.new('flag', "No rule of thumb values exist for #{building_type}. Hot water consumption was not checked.")
|
310
|
+
end
|
311
|
+
|
312
|
+
end
|
313
|
+
|
314
|
+
# check actual against target
|
315
|
+
if service_water_consumption_daily_avg_gal < target_consumption * (1.0 - min_pass)
|
316
|
+
check_elems << OpenStudio::Attribute.new('flag', "Annual average of #{service_water_consumption_daily_avg_gal.round} gallons per day of hot water is more than #{min_pass * 100} % below the expected value of #{target_consumption.round} gallons per day.")
|
317
|
+
elsif service_water_consumption_daily_avg_gal > target_consumption * (1.0 + max_pass)
|
318
|
+
check_elems << OpenStudio::Attribute.new('flag', "Annual average of #{service_water_consumption_daily_avg_gal.round} gallons per day of hot water is more than #{max_pass * 100} % above the expected value of #{target_consumption.round} gallons per day.")
|
319
|
+
end
|
320
|
+
rescue StandardError => e
|
321
|
+
# brief description of ruby error
|
322
|
+
check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
|
323
|
+
|
324
|
+
# backtrace of ruby error for diagnostic use
|
325
|
+
if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
|
326
|
+
end
|
327
|
+
|
328
|
+
# add check_elms to new attribute
|
329
|
+
check_elem = OpenStudio::Attribute.new('check', check_elems)
|
330
|
+
|
331
|
+
return check_elem
|
332
|
+
# note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb
|
333
|
+
end
|
334
|
+
end
|