buildingsync 0.2.0
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 +7 -0
- data/.github/workflows/continuous_integration.yml +146 -0
- data/.gitignore +33 -0
- data/.rspec +3 -0
- data/.rubocop.yml +10 -0
- data/CHANGELOG.md +50 -0
- data/Gemfile +31 -0
- data/Jenkinsfile +10 -0
- data/LICENSE.md +29 -0
- data/README.md +105 -0
- data/Rakefile +77 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/buildingsync.gemspec +37 -0
- data/config.rb.in +26 -0
- data/doc_templates/LICENSE.md +29 -0
- data/doc_templates/README.md.erb +42 -0
- data/doc_templates/copyright_erb.txt +38 -0
- data/doc_templates/copyright_js.txt +5 -0
- data/doc_templates/copyright_ruby.txt +36 -0
- data/lib/buildingsync.rb +43 -0
- data/lib/buildingsync/all_resource_total.rb +54 -0
- data/lib/buildingsync/audit_date.rb +54 -0
- data/lib/buildingsync/constants.rb +49 -0
- data/lib/buildingsync/contact.rb +54 -0
- data/lib/buildingsync/extension.rb +57 -0
- data/lib/buildingsync/generator.rb +584 -0
- data/lib/buildingsync/get_bcl_weather_file.rb +326 -0
- data/lib/buildingsync/helpers/Model.hvac.rb +216 -0
- data/lib/buildingsync/helpers/helper.rb +494 -0
- data/lib/buildingsync/helpers/xml_get_set.rb +215 -0
- data/lib/buildingsync/makers/phase_zero_base.osw +178 -0
- data/lib/buildingsync/makers/workflow_maker.json +811 -0
- data/lib/buildingsync/makers/workflow_maker.rb +581 -0
- data/lib/buildingsync/makers/workflow_maker_base.rb +167 -0
- data/lib/buildingsync/model_articulation/building.rb +1119 -0
- data/lib/buildingsync/model_articulation/building_and_system_types.json +121 -0
- data/lib/buildingsync/model_articulation/building_section.rb +190 -0
- data/lib/buildingsync/model_articulation/building_system.rb +49 -0
- data/lib/buildingsync/model_articulation/envelope_system.rb +102 -0
- data/lib/buildingsync/model_articulation/exterior_floor_system_type.rb +64 -0
- data/lib/buildingsync/model_articulation/facility.rb +439 -0
- data/lib/buildingsync/model_articulation/foundation_system_type.rb +64 -0
- data/lib/buildingsync/model_articulation/hvac_system.rb +395 -0
- data/lib/buildingsync/model_articulation/lighting_system.rb +102 -0
- data/lib/buildingsync/model_articulation/loads_system.rb +287 -0
- data/lib/buildingsync/model_articulation/location_element.rb +129 -0
- data/lib/buildingsync/model_articulation/measure.rb +57 -0
- data/lib/buildingsync/model_articulation/roof_system_type.rb +64 -0
- data/lib/buildingsync/model_articulation/service_hot_water_system.rb +87 -0
- data/lib/buildingsync/model_articulation/site.rb +242 -0
- data/lib/buildingsync/model_articulation/spatial_element.rb +343 -0
- data/lib/buildingsync/model_articulation/wall_system_type.rb +64 -0
- data/lib/buildingsync/report.rb +217 -0
- data/lib/buildingsync/resource_use.rb +55 -0
- data/lib/buildingsync/scenario.rb +622 -0
- data/lib/buildingsync/selection_tool.rb +98 -0
- data/lib/buildingsync/time_series.rb +85 -0
- data/lib/buildingsync/translator.rb +167 -0
- data/lib/buildingsync/utility.rb +67 -0
- data/lib/buildingsync/version.rb +45 -0
- metadata +223 -0
@@ -0,0 +1,581 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# *******************************************************************************
|
4
|
+
# OpenStudio(R), Copyright (c) 2008-2020, Alliance for Sustainable Energy, LLC.
|
5
|
+
# BuildingSync(R), Copyright (c) 2015-2020, Alliance for Sustainable Energy, LLC.
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
# Redistribution and use in source and binary forms, with or without
|
9
|
+
# modification, are permitted provided that the following conditions are met:
|
10
|
+
#
|
11
|
+
# (1) Redistributions of source code must retain the above copyright notice,
|
12
|
+
# this list of conditions and the following disclaimer.
|
13
|
+
#
|
14
|
+
# (2) Redistributions in binary form must reproduce the above copyright notice,
|
15
|
+
# this list of conditions and the following disclaimer in the documentation
|
16
|
+
# and/or other materials provided with the distribution.
|
17
|
+
#
|
18
|
+
# (3) Neither the name of the copyright holder nor the names of any contributors
|
19
|
+
# may be used to endorse or promote products derived from this software without
|
20
|
+
# specific prior written permission from the respective party.
|
21
|
+
#
|
22
|
+
# (4) Other than as required in clauses (1) and (2), distributions in any form
|
23
|
+
# of modifications or other derivative works may not use the "OpenStudio"
|
24
|
+
# trademark, "OS", "os", or any other confusingly similar designation without
|
25
|
+
# specific prior written permission from Alliance for Sustainable Energy, LLC.
|
26
|
+
#
|
27
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
|
28
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
29
|
+
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
30
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
|
31
|
+
# UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
|
32
|
+
# THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
33
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
34
|
+
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
35
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
36
|
+
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
37
|
+
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
38
|
+
# *******************************************************************************
|
39
|
+
require 'rexml/document'
|
40
|
+
|
41
|
+
require 'openstudio/common_measures'
|
42
|
+
require 'openstudio/model_articulation'
|
43
|
+
require 'openstudio/ee_measures'
|
44
|
+
|
45
|
+
require 'buildingsync/extension'
|
46
|
+
require 'buildingsync/constants'
|
47
|
+
require 'buildingsync/scenario'
|
48
|
+
require 'buildingsync/makers/workflow_maker_base'
|
49
|
+
require 'buildingsync/model_articulation/facility'
|
50
|
+
|
51
|
+
module BuildingSync
|
52
|
+
# base class for objects that will configure workflows based on building sync files
|
53
|
+
class WorkflowMaker < WorkflowMakerBase
|
54
|
+
# initialize - load workflow json file and add necessary measure paths
|
55
|
+
# @param doc [REXML::Document]
|
56
|
+
# @param ns [String]
|
57
|
+
def initialize(doc, ns)
|
58
|
+
super(doc, ns)
|
59
|
+
|
60
|
+
@facility_xml = nil
|
61
|
+
@facility = nil
|
62
|
+
|
63
|
+
# TODO: Be consistent in symbolizing names in hashes or not
|
64
|
+
File.open(PHASE_0_BASE_OSW_FILE_PATH, 'r') do |file|
|
65
|
+
@workflow = JSON.parse(file.read)
|
66
|
+
end
|
67
|
+
|
68
|
+
File.open(WORKFLOW_MAKER_JSON_FILE_PATH, 'r') do |file|
|
69
|
+
@workflow_maker_json = JSON.parse(file.read, symbolize_names: true)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Add all of the measure directories from the extension gems
|
73
|
+
# into the @workflow, then check they exist
|
74
|
+
set_measure_paths(get_measure_directories_array)
|
75
|
+
measures_exist?
|
76
|
+
read_xml
|
77
|
+
end
|
78
|
+
|
79
|
+
def read_xml
|
80
|
+
facility_xml_temp = @doc.get_elements("#{get_prefix}BuildingSync/#{get_prefix}Facilities/#{get_prefix}Facility")
|
81
|
+
|
82
|
+
# Raise errors for zero or multiple Facilities. Not supported at this time.
|
83
|
+
if facility_xml_temp.nil? || facility_xml_temp.empty?
|
84
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMaker.populate_facility_report_and_scenarios', 'There are no Facility elements in your BuildingSync file.')
|
85
|
+
raise StandardError, 'There are no Facility elements in your BuildingSync file.'
|
86
|
+
elsif facility_xml_temp.size > 1
|
87
|
+
@facility_xml = facility_xml_temp.first
|
88
|
+
OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.WorkflowMaker.populate_facility_report_and_scenarios', "There are more than one (#{facility_xml_temp.size}) Facility elements in your BuildingSync file. Only the first Facility will be considered (ID: #{@facility_xml.attributes['ID']}")
|
89
|
+
else
|
90
|
+
@facility_xml = facility_xml_temp.first
|
91
|
+
end
|
92
|
+
|
93
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.WorkflowMaker.read_xml', "Setting up workflow for Facility ID: #{@facility_xml.attributes['ID']}")
|
94
|
+
|
95
|
+
# Initialize Facility object
|
96
|
+
@facility = BuildingSync::Facility.new(@facility_xml, @ns)
|
97
|
+
end
|
98
|
+
|
99
|
+
# get the facility object from this workflow
|
100
|
+
# @return [BuildingSync::Facility] facility
|
101
|
+
def get_facility
|
102
|
+
return @facility
|
103
|
+
end
|
104
|
+
|
105
|
+
# get the space types of the facility
|
106
|
+
# @return [Vector<OpenStudio::Model::SpaceType>] vector of space types
|
107
|
+
def get_space_types
|
108
|
+
return @facility.get_space_types
|
109
|
+
end
|
110
|
+
|
111
|
+
# get model
|
112
|
+
# @return [OpenStudio::Model] model
|
113
|
+
def get_model
|
114
|
+
return @facility.get_model
|
115
|
+
end
|
116
|
+
|
117
|
+
# get the current workflow
|
118
|
+
# @return [Hash]
|
119
|
+
def get_workflow
|
120
|
+
return @workflow
|
121
|
+
end
|
122
|
+
|
123
|
+
# get scenario elements
|
124
|
+
# @return [Array<BuildingSync::Scenario>]
|
125
|
+
def get_scenarios
|
126
|
+
return @facility.report.scenarios
|
127
|
+
end
|
128
|
+
|
129
|
+
# generate the baseline model as osm model
|
130
|
+
# @param dir [String]
|
131
|
+
# @param epw_file_path [String]
|
132
|
+
# @param standard_to_be_used [String] 'ASHRAE90.1' or 'CaliforniaTitle24' are supported options
|
133
|
+
# @param ddy_file [String] path to the ddy file
|
134
|
+
# @return @see BuildingSync::Facility.write_osm
|
135
|
+
def setup_and_sizing_run(dir, epw_file_path, standard_to_be_used, ddy_file = nil)
|
136
|
+
@facility.set_all
|
137
|
+
@facility.determine_open_studio_standard(standard_to_be_used)
|
138
|
+
@facility.generate_baseline_osm(epw_file_path, dir, standard_to_be_used, ddy_file)
|
139
|
+
@facility.write_osm(dir)
|
140
|
+
end
|
141
|
+
|
142
|
+
# writes the parameters determined during processing back to the BldgSync XML file
|
143
|
+
def prepare_final_xml
|
144
|
+
@facility.prepare_final_xml
|
145
|
+
end
|
146
|
+
|
147
|
+
# # write osm
|
148
|
+
# # @param dir [String]
|
149
|
+
# def write_osm(dir)
|
150
|
+
# @scenario_types = @facility.write_osm(dir)
|
151
|
+
# end
|
152
|
+
|
153
|
+
# iterate over the current measure list in the workflow and check if they are available at the referenced measure directories
|
154
|
+
# @return [Boolean]
|
155
|
+
def measures_exist?
|
156
|
+
all_measures_found = true
|
157
|
+
number_measures_found = 0
|
158
|
+
@workflow['steps'].each do |step|
|
159
|
+
measure_is_valid = false
|
160
|
+
measure_dir_name = step['measure_dir_name']
|
161
|
+
get_measure_directories_array.each do |potential_measure_path|
|
162
|
+
measure_dir_full_path = "#{potential_measure_path}/#{measure_dir_name}"
|
163
|
+
if Dir.exist?(measure_dir_full_path)
|
164
|
+
measure_is_valid = true
|
165
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.WorkflowMaker.measures_exist?', "Measure: #{measure_dir_name} found at: #{measure_dir_full_path}")
|
166
|
+
number_measures_found += 1
|
167
|
+
break
|
168
|
+
end
|
169
|
+
end
|
170
|
+
if !measure_is_valid
|
171
|
+
all_measures_found = false
|
172
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMaker.measures_exist?', "CANNOT find measure with name (#{measure_dir_name}) in any of the measure paths ")
|
173
|
+
end
|
174
|
+
end
|
175
|
+
if all_measures_found
|
176
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.WorkflowMaker.measures_exist?', "Total measures found: #{number_measures_found}. All measures defined by @workflow found.")
|
177
|
+
puts "Total measures found: #{number_measures_found}. All measures defined by @workflow found."
|
178
|
+
end
|
179
|
+
return all_measures_found
|
180
|
+
end
|
181
|
+
|
182
|
+
# gets all available measures across all measure directories
|
183
|
+
# @return [hash] Looks as follows {path_to_measure_dir: [measure_name1, mn2, etc.], path_to_measure_dir_2: [...]}
|
184
|
+
def get_available_measures_hash
|
185
|
+
measures_hash = {}
|
186
|
+
get_measure_directories_array.each do |potential_measure_path|
|
187
|
+
Dir.chdir(potential_measure_path) do
|
188
|
+
measures_hash[potential_measure_path] = Dir.glob('*').select { |f| File.directory? f }
|
189
|
+
end
|
190
|
+
end
|
191
|
+
return measures_hash
|
192
|
+
end
|
193
|
+
|
194
|
+
# collect all measure directories that contain measures needed for BldgSync
|
195
|
+
# @return [array] of measure dirs
|
196
|
+
def get_measure_directories_array
|
197
|
+
common_measures_instance = OpenStudio::CommonMeasures::Extension.new
|
198
|
+
model_articulation_instance = OpenStudio::ModelArticulation::Extension.new
|
199
|
+
ee_measures_instance = OpenStudio::EeMeasures::Extension.new
|
200
|
+
bldg_sync_instance = BuildingSync::Extension.new
|
201
|
+
return [common_measures_instance.measures_dir, model_articulation_instance.measures_dir, bldg_sync_instance.measures_dir, ee_measures_instance.measures_dir]
|
202
|
+
end
|
203
|
+
|
204
|
+
# inserts any measure. traverses through the measures available in the included extensions
|
205
|
+
# (common measures, model articulation, etc.) to find the lib/measures/[measure_dir] specified.
|
206
|
+
# It is inserted at the relative position according to its type
|
207
|
+
# @param measure_goal_type [String] one of: 'EnergyPlusMeasure', 'ReportingMeasure', or 'ModelMeasure'
|
208
|
+
# @param measure_dir_name [String] the directory name for the measure, as it appears
|
209
|
+
# in any of the gems, i.e. openstudio-common-measures-gem/lib/measures/[measure_dir_name]
|
210
|
+
# @param relative_position [Integer] the position where the measure should be inserted with respect to the measure_goal_type
|
211
|
+
# @param args_hash [hash]
|
212
|
+
def insert_measure_into_workflow(measure_goal_type, measure_dir_name, relative_position = 0, args_hash = {})
|
213
|
+
successfully_added = false
|
214
|
+
count = 0 # count for all of the measures, regardless of the type
|
215
|
+
measure_type_count = 0 # count of measures specific to the measure_goal_type
|
216
|
+
measure_type_found = false
|
217
|
+
new_step = {}
|
218
|
+
new_step['measure_dir_name'] = measure_dir_name
|
219
|
+
new_step['arguments'] = args_hash
|
220
|
+
if @workflow['steps'].empty?
|
221
|
+
@workflow['steps'].insert(count, new_step)
|
222
|
+
successfully_added = true
|
223
|
+
else
|
224
|
+
@workflow['steps'].each do |step|
|
225
|
+
measure_dir_name = step['measure_dir_name']
|
226
|
+
measure_type = get_measure_type(measure_dir_name)
|
227
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.WorkflowMaker.insert_measure_into_workflow', "measure: #{measure_dir_name} with type: #{measure_type} found")
|
228
|
+
if measure_type == measure_goal_type
|
229
|
+
measure_type_found = true
|
230
|
+
if measure_type_count == relative_position
|
231
|
+
# insert measure here
|
232
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.WorkflowMaker.insert_measure_into_workflow', "inserting measure with type (#{measure_goal_type}) at position #{count} and dir: #{measure_dir_name} and type: #{get_measure_type(measure_dir_name)}")
|
233
|
+
puts "inserting measure with type (#{measure_goal_type}) at position #{count} and dir: #{measure_dir_name} and type: #{get_measure_type(measure_dir_name)}"
|
234
|
+
@workflow['steps'].insert(count, new_step)
|
235
|
+
successfully_added = true
|
236
|
+
break
|
237
|
+
end
|
238
|
+
measure_type_count += 1
|
239
|
+
elsif measure_type_found
|
240
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.WorkflowMaker.insert_measure_into_workflow', "inserting measure with type (#{measure_goal_type})at position #{count} and dir: #{measure_dir_name} and type: #{get_measure_type(measure_dir_name)}")
|
241
|
+
puts "inserting measure with type (#{measure_goal_type}) at position #{count} and dir: #{measure_dir_name} and type: #{get_measure_type(measure_dir_name)}"
|
242
|
+
@workflow['steps'].insert(count - 1, new_step)
|
243
|
+
successfully_added = true
|
244
|
+
break
|
245
|
+
end
|
246
|
+
count += 1
|
247
|
+
end
|
248
|
+
end
|
249
|
+
if !successfully_added
|
250
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMakerPhaseZero.insert_measure_into_workflow', "CANNOT insert measure with type (#{measure_goal_type}) at position #{count} and dir: #{measure_dir_name} and type: #{get_measure_type(measure_dir_name)}")
|
251
|
+
end
|
252
|
+
return successfully_added
|
253
|
+
end
|
254
|
+
|
255
|
+
# gets the measure type of a measure given its directory - looking up the measure type in the measure.xml file
|
256
|
+
# @param measure_dir_name [String] the directory name for the measure, as it appears
|
257
|
+
# in any of the gems, i.e. openstudio-common-measures-gem/lib/measures/[measure_dir_name]
|
258
|
+
# @return [String]
|
259
|
+
def get_measure_type(measure_dir_name)
|
260
|
+
measure_type = nil
|
261
|
+
get_measure_directories_array.each do |potential_measure_path|
|
262
|
+
measure_dir_full_path = "#{potential_measure_path}/#{measure_dir_name}"
|
263
|
+
if Dir.exist?(measure_dir_full_path)
|
264
|
+
measure_xml_doc = nil
|
265
|
+
File.open(measure_dir_full_path + '/measure.xml', 'r') do |file|
|
266
|
+
measure_xml_doc = REXML::Document.new(file)
|
267
|
+
end
|
268
|
+
measure_xml_doc.elements.each('/measure/attributes/attribute') do |attribute|
|
269
|
+
attribute_name = attribute.elements['name'].text
|
270
|
+
if attribute_name == 'Measure Type'
|
271
|
+
measure_type = attribute.elements['value'].text
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
return measure_type
|
277
|
+
end
|
278
|
+
|
279
|
+
# Based on the MeasureIDs defined by the Scenario, configure the workflow provided
|
280
|
+
# using the default measure arguments defined by the lib/buildingsync/makers/workflow_maker.json
|
281
|
+
# @param base_workflow [Hash] a Hash map of the @workflow. DO NOT use @workflow directly, should be a deep clone
|
282
|
+
# @param scenario [BuildingSync::Scenario] a Scenario object
|
283
|
+
def configure_workflow_for_scenario(base_workflow, scenario)
|
284
|
+
successful = true
|
285
|
+
|
286
|
+
num_measures = 0
|
287
|
+
scenario.get_measure_ids.each do |measure_id|
|
288
|
+
measure = @facility.measures.find { |m| m.xget_id == measure_id }
|
289
|
+
current_num_measure = num_measures
|
290
|
+
|
291
|
+
sym_to_find = measure.xget_text('SystemCategoryAffected')
|
292
|
+
if sym_to_find.nil? || sym_to_find.empty?
|
293
|
+
OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.WorkflowMaker.configure_workflow_for_scenario', "Measure ID: #{measure.xget_id} does not define a SystemCategoryAffected.")
|
294
|
+
successful = false
|
295
|
+
else
|
296
|
+
sym_to_find = sym_to_find.to_s.to_sym
|
297
|
+
end
|
298
|
+
|
299
|
+
# 'Other HVAC' or 'Cooling System' as examples
|
300
|
+
categories_found = @workflow_maker_json.key?(sym_to_find)
|
301
|
+
if categories_found
|
302
|
+
m_name = measure.xget_name
|
303
|
+
|
304
|
+
if m_name.nil? || m_name.empty?
|
305
|
+
OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.WorkflowMaker.configure_workflow_for_scenario', "Measure ID: #{measure.xget_id} does not have a MeasureName defined.")
|
306
|
+
successful = false
|
307
|
+
else
|
308
|
+
m_name = m_name.to_sym
|
309
|
+
end
|
310
|
+
|
311
|
+
# Where standardized measure names have not been adopted as enumerations
|
312
|
+
# in the BuildingSync Schema, a <MeasureName>Other</MeasureName> is used
|
313
|
+
# and the actual measure name added
|
314
|
+
if m_name == :Other
|
315
|
+
m_name = measure.xget_text('CustomMeasureName')
|
316
|
+
if m_name.nil? || m_name.empty?
|
317
|
+
OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.WorkflowMaker.configure_workflow_for_scenario', "Measure ID: #{measure.xget_id} has a MeasureName of 'Other' but does not have a CustomMeasureName defined.")
|
318
|
+
successful = false
|
319
|
+
else
|
320
|
+
m_name = m_name.to_sym
|
321
|
+
end
|
322
|
+
end
|
323
|
+
measure_found = false
|
324
|
+
@workflow_maker_json[sym_to_find].each do |category|
|
325
|
+
# m_name is, for example: 'Replace HVAC system type to VRF'
|
326
|
+
|
327
|
+
if !category[m_name].nil?
|
328
|
+
measure_found = true
|
329
|
+
measure_dir_name = category[m_name][:measure_dir_name]
|
330
|
+
num_measures += 1
|
331
|
+
category[m_name][:arguments].each do |argument|
|
332
|
+
# Certain arguments are only applied under specific conditions
|
333
|
+
#
|
334
|
+
if !argument[:condition].nil? && !argument[:condition].empty?
|
335
|
+
set_argument_detail(base_workflow, argument, measure_dir_name, m_name.to_s)
|
336
|
+
else
|
337
|
+
set_measure_argument(base_workflow, measure_dir_name, argument[:name], argument[:value])
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
342
|
+
if !measure_found
|
343
|
+
OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.WorkflowMaker.configure_workflow_for_scenario', "Could not find measure '#{m_name}' under category #{sym_to_find} in workflow_maker.json.")
|
344
|
+
end
|
345
|
+
else
|
346
|
+
OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.WorkflowMaker.configure_workflow_for_scenario', "Category: #{measure.xget_text('SystemCategoryAffected')} not found in workflow_maker.json.")
|
347
|
+
end
|
348
|
+
|
349
|
+
if current_num_measure == num_measures
|
350
|
+
OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.WorkflowMaker.configure_workflow_for_scenario', "Measure ID: #{measure.xget_id} could not be processed!")
|
351
|
+
successful = false
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
# ensure that we didn't miss any measures by accident
|
356
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMaker.configure_workflow_for_scenario', "#{scenario.get_measure_ids.size} measures expected, #{num_measures} resolved, expected measure_ids = #{scenario.get_measure_ids}") if num_measures != scenario.get_measure_ids.size
|
357
|
+
return successful
|
358
|
+
end
|
359
|
+
|
360
|
+
# TODO: Update this as I believe no longer will work as expected, keys being searched for
|
361
|
+
# by the @facility_xml['key'] don't make sense.
|
362
|
+
# set argument details, used when the condition
|
363
|
+
# @param workflow [Hash] a hash of the openstudio workflow
|
364
|
+
# @param argument [Hash]
|
365
|
+
# @param measure_dir_name [String] the directory name for the measure, as it appears
|
366
|
+
# in any of the gems, i.e. openstudio-common-measures-gem/lib/measures/[measure_dir_name]
|
367
|
+
# @param measure_name [String]
|
368
|
+
def set_argument_detail(workflow, argument, measure_dir_name, measure_name)
|
369
|
+
argument_name = ''
|
370
|
+
argument_value = ''
|
371
|
+
|
372
|
+
if measure_name == 'Add daylight controls' || measure_name == 'Replace HVAC system type to PZHP'
|
373
|
+
# For these measures, the condition is based on the standards building type determined
|
374
|
+
if argument[:condition] == @facility.site.get_building_type
|
375
|
+
argument_name = argument[:name]
|
376
|
+
|
377
|
+
# This is a really terrible way to do this. It fails
|
378
|
+
# in many scenarios
|
379
|
+
argument_value = "#{argument[:value]} #{@facility.site.get_standard_template}"
|
380
|
+
end
|
381
|
+
elsif measure_name == 'Replace burner'
|
382
|
+
if argument[:condition] == @facility.site.get_system_type
|
383
|
+
argument_name = argument[:name]
|
384
|
+
argument_value = argument[:value]
|
385
|
+
end
|
386
|
+
elsif measure_name == 'Replace boiler'
|
387
|
+
if argument[:condition] == @facility.site.get_system_type
|
388
|
+
argument_name = argument[:name]
|
389
|
+
argument_value = argument[:value]
|
390
|
+
end
|
391
|
+
elsif measure_name == 'Replace package units'
|
392
|
+
if argument[:condition] == @facility.site.get_system_type
|
393
|
+
argument_name = argument[:name]
|
394
|
+
argument_value = argument[:value]
|
395
|
+
end
|
396
|
+
elsif measure_name == 'Replace HVAC system type to VRF' || measure_name == 'Replace HVAC with GSHP and DOAS' || measure_name == 'Replace AC and heating units with ground coupled heat pump systems'
|
397
|
+
if argument[:condition] == @facility.site.get_building_type
|
398
|
+
argument_name = (argument[:name]).to_s
|
399
|
+
argument_value = argument[:value]
|
400
|
+
end
|
401
|
+
else
|
402
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMaker.set_argument_detail', "measure dir name not found #{measure_name}.")
|
403
|
+
puts "BuildingSync.WorkflowMaker.set_argument_detail: Measure dir name not found #{measure_name}."
|
404
|
+
end
|
405
|
+
|
406
|
+
set_measure_argument(workflow, measure_dir_name, argument_name, argument_value) if !argument_name.nil? && !argument_name.empty?
|
407
|
+
end
|
408
|
+
|
409
|
+
# write workflows for scenarios into osw files. This includes:
|
410
|
+
# - Package of Measure Scenarios
|
411
|
+
# - Current Building Modeled (Baseline) Scenario
|
412
|
+
# @param main_output_dir [String] main output path, not scenario specific. i.e. SR should be a subdirectory
|
413
|
+
# @return [Boolean] whether writing of all the new workflows was successful
|
414
|
+
def write_osws(main_output_dir, only_cb_modeled = false)
|
415
|
+
# make sure paths exist
|
416
|
+
FileUtils.mkdir_p(main_output_dir)
|
417
|
+
|
418
|
+
if @facility.report.cb_modeled.nil?
|
419
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMaker.write_osws', 'OSW cannot be written since no current building modeled scenario is defined. One can be added after file import using the add_cb_modeled method')
|
420
|
+
raise StandardError, 'BuildingSync.WorkflowMaker.write_osws: OSW cannot be written since no current building modeled scenario is defined. One can be added after file import using the add_cb_modeled method'
|
421
|
+
end
|
422
|
+
|
423
|
+
# Write a workflow for the current building modeled scenario
|
424
|
+
cb_modeled_success = write_osw(main_output_dir, @facility.report.cb_modeled)
|
425
|
+
|
426
|
+
if !cb_modeled_success
|
427
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMaker.write_osws', 'A workflow was not successfully written for the cb_modeled (Current Building Modeled) Scenario.')
|
428
|
+
raise StandardError, 'BuildingSync.WorkflowMaker.write_osws: A workflow was not successfully written for the cb_modeled (Current Building Modeled) Scenario.'
|
429
|
+
end
|
430
|
+
|
431
|
+
number_successful = cb_modeled_success ? 1 : 0
|
432
|
+
|
433
|
+
if !only_cb_modeled
|
434
|
+
# write an osw for each Package Of Measures scenario
|
435
|
+
@facility.report.poms.each do |scenario|
|
436
|
+
successful = write_osw(main_output_dir, scenario)
|
437
|
+
if successful
|
438
|
+
number_successful += 1
|
439
|
+
else
|
440
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMaker.write_osws', "Scenario ID: #{scenario.xget_id}. Unsuccessful write_osw")
|
441
|
+
end
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
# Compare the total number of potential successes to the number of actual successes
|
446
|
+
if only_cb_modeled
|
447
|
+
# In this case we should have only 1 success
|
448
|
+
expected_successes = 1
|
449
|
+
really_successful = number_successful == expected_successes
|
450
|
+
else
|
451
|
+
# In this case, all pom scenarios should be run + the cb_modeled scenario
|
452
|
+
expected_successes = @facility.report.poms.size + 1
|
453
|
+
really_successful = number_successful == expected_successes
|
454
|
+
end
|
455
|
+
|
456
|
+
if !really_successful
|
457
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMaker.write_osws', "Facility ID: #{@facility.xget_id}. Expected #{expected_successes}, Got #{number_successful} OSWs")
|
458
|
+
end
|
459
|
+
|
460
|
+
return really_successful
|
461
|
+
end
|
462
|
+
|
463
|
+
# Write an OSW for the provided scenario
|
464
|
+
# @param main_output_dir [String] main output path, not scenario specific. i.e. SR should be a subdirectory
|
465
|
+
# @param [BuildingSync::Scenario]
|
466
|
+
# @return [Boolean] whether the writing was successful
|
467
|
+
def write_osw(main_output_dir, scenario)
|
468
|
+
successful = true
|
469
|
+
# deep clone
|
470
|
+
base_workflow = deep_copy_workflow
|
471
|
+
|
472
|
+
# configure the workflow based on measures in this scenario
|
473
|
+
begin
|
474
|
+
# The workflow is updated by configure_workflow, put with pass by reference
|
475
|
+
# we are ok to use it later without returning
|
476
|
+
if !configure_workflow_for_scenario(base_workflow, scenario)
|
477
|
+
successful = false
|
478
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMaker.write_osw', "Could not configure workflow for scenario #{scenario.xget_name}")
|
479
|
+
else
|
480
|
+
purge_skipped_from_workflow(base_workflow)
|
481
|
+
scenario.set_workflow(base_workflow)
|
482
|
+
scenario.write_osw(main_output_dir)
|
483
|
+
end
|
484
|
+
rescue StandardError => e
|
485
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMaker.write_osw', "Could not configure for scenario #{scenario.xget_name}. Error: #{e}")
|
486
|
+
puts "Could not configure for scenario #{scenario.xget_name}"
|
487
|
+
puts e.backtrace.join("\n\t")
|
488
|
+
successful = false
|
489
|
+
end
|
490
|
+
return successful
|
491
|
+
end
|
492
|
+
|
493
|
+
# run osws - running all scenario simulations
|
494
|
+
# @param only_cb_modeled [Boolean] used to only run the simulations for the cb_modeled (baseline) scenario
|
495
|
+
# @param runner_options [hash]
|
496
|
+
def run_osws(output_dir, only_cb_modeled = false, runner_options = { run_simulations: true, verbose: false, num_parallel: 7, max_to_run: Float::INFINITY })
|
497
|
+
osw_files = []
|
498
|
+
osw_sr_files = []
|
499
|
+
if only_cb_modeled
|
500
|
+
osw_files << "#{@facility.report.cb_modeled.get_osw_dir}/in.osw"
|
501
|
+
else
|
502
|
+
Dir.glob("#{output_dir}/**/in.osw") { |osw| osw_files << osw }
|
503
|
+
end
|
504
|
+
Dir.glob("#{output_dir}/SR/in.osw") { |osw| osw_sr_files << osw }
|
505
|
+
|
506
|
+
runner = OpenStudio::Extension::Runner.new(dirname = Dir.pwd, bundle_without = [], options = runner_options)
|
507
|
+
|
508
|
+
# This doesn't run the workflow defined by the Sizing Run
|
509
|
+
return runner.run_osws(osw_files - osw_sr_files)
|
510
|
+
end
|
511
|
+
|
512
|
+
# Creates a deep copy of the @workflow be serializing and reloading with JSON
|
513
|
+
# @return [Hash] a new workflow object
|
514
|
+
def deep_copy_workflow
|
515
|
+
return JSON.load(JSON.generate(@workflow))
|
516
|
+
end
|
517
|
+
|
518
|
+
# Removes unused measures from a workflow, where __SKIP__ == true
|
519
|
+
# @param workflow [Hash] a hash of the openstudio workflow, typically after a deep
|
520
|
+
# copy is made and the measures are configured for the specific scenario
|
521
|
+
def purge_skipped_from_workflow(workflow)
|
522
|
+
non_skipped = []
|
523
|
+
if !workflow.nil? && !workflow['steps'].nil? && workflow.key?('steps')
|
524
|
+
workflow['steps'].each do |step|
|
525
|
+
if !step.nil? && step.key?('arguments') && !step['arguments'].nil?
|
526
|
+
if step['arguments'].key?('__SKIP__') && step['arguments']['__SKIP__'] == false
|
527
|
+
non_skipped << step
|
528
|
+
end
|
529
|
+
end
|
530
|
+
end
|
531
|
+
workflow['steps'] = non_skipped
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
# get failed scenarios
|
536
|
+
# @return [Array<BuildingSync::Scenario>]
|
537
|
+
def get_failed_scenarios
|
538
|
+
failed = []
|
539
|
+
@facility.report.scenarios.each do |scenario|
|
540
|
+
failed << scenario if !scenario.simulation_success?
|
541
|
+
end
|
542
|
+
return failed
|
543
|
+
end
|
544
|
+
|
545
|
+
# cleanup larger files
|
546
|
+
# @param osw_dir [String]
|
547
|
+
def cleanup_larger_files(osw_dir)
|
548
|
+
path = File.join(osw_dir, 'eplusout.sql')
|
549
|
+
FileUtils.rm_f(path) if File.exist?(path)
|
550
|
+
path = File.join(osw_dir, 'data_point.zip')
|
551
|
+
FileUtils.rm_f(path) if File.exist?(path)
|
552
|
+
path = File.join(osw_dir, 'eplusout.eso')
|
553
|
+
FileUtils.rm_f(path) if File.exist?(path)
|
554
|
+
Dir.glob(File.join(osw_dir, '*create_typical_building_from_model*')).each do |path|
|
555
|
+
FileUtils.rm_rf(path) if File.exist?(path)
|
556
|
+
end
|
557
|
+
Dir.glob(File.join(osw_dir, '*create_typical_building_from_model*')).each do |path|
|
558
|
+
FileUtils.rm_rf(path) if File.exist?(path)
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
# gather results for all CB Modeled and POM Scenarios, including both annual and monthly results
|
563
|
+
# - ResourceUse and AllResourceTotal elements are added to the Scenario as part of this process
|
564
|
+
# - ResourceUse - holds consumption information about a specific resource / fuel (Electricity, Natural gas, etc.)
|
565
|
+
# - AllResourceTotal - holds total site and source energy consumption information
|
566
|
+
# @param year_val [Integer]
|
567
|
+
# @param baseline_only [Boolean]
|
568
|
+
# @return [Boolean]
|
569
|
+
def gather_results(year_val = Date.today.year, baseline_only = false)
|
570
|
+
# Gather results for the Current Building Modeled (Baseline) Scenario
|
571
|
+
@facility.report.cb_modeled.os_gather_results(year_val)
|
572
|
+
|
573
|
+
if !baseline_only
|
574
|
+
# Gather results for the Package of Measures scenarios
|
575
|
+
@facility.report.poms.each do |scenario|
|
576
|
+
scenario.os_gather_results(year_val)
|
577
|
+
end
|
578
|
+
end
|
579
|
+
end
|
580
|
+
end
|
581
|
+
end
|