buildingsync 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/continuous_integration.yml +146 -0
  3. data/.gitignore +33 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +10 -0
  6. data/CHANGELOG.md +50 -0
  7. data/Gemfile +31 -0
  8. data/Jenkinsfile +10 -0
  9. data/LICENSE.md +29 -0
  10. data/README.md +105 -0
  11. data/Rakefile +77 -0
  12. data/bin/console +15 -0
  13. data/bin/setup +8 -0
  14. data/buildingsync.gemspec +37 -0
  15. data/config.rb.in +26 -0
  16. data/doc_templates/LICENSE.md +29 -0
  17. data/doc_templates/README.md.erb +42 -0
  18. data/doc_templates/copyright_erb.txt +38 -0
  19. data/doc_templates/copyright_js.txt +5 -0
  20. data/doc_templates/copyright_ruby.txt +36 -0
  21. data/lib/buildingsync.rb +43 -0
  22. data/lib/buildingsync/all_resource_total.rb +54 -0
  23. data/lib/buildingsync/audit_date.rb +54 -0
  24. data/lib/buildingsync/constants.rb +49 -0
  25. data/lib/buildingsync/contact.rb +54 -0
  26. data/lib/buildingsync/extension.rb +57 -0
  27. data/lib/buildingsync/generator.rb +584 -0
  28. data/lib/buildingsync/get_bcl_weather_file.rb +326 -0
  29. data/lib/buildingsync/helpers/Model.hvac.rb +216 -0
  30. data/lib/buildingsync/helpers/helper.rb +494 -0
  31. data/lib/buildingsync/helpers/xml_get_set.rb +215 -0
  32. data/lib/buildingsync/makers/phase_zero_base.osw +178 -0
  33. data/lib/buildingsync/makers/workflow_maker.json +811 -0
  34. data/lib/buildingsync/makers/workflow_maker.rb +581 -0
  35. data/lib/buildingsync/makers/workflow_maker_base.rb +167 -0
  36. data/lib/buildingsync/model_articulation/building.rb +1119 -0
  37. data/lib/buildingsync/model_articulation/building_and_system_types.json +121 -0
  38. data/lib/buildingsync/model_articulation/building_section.rb +190 -0
  39. data/lib/buildingsync/model_articulation/building_system.rb +49 -0
  40. data/lib/buildingsync/model_articulation/envelope_system.rb +102 -0
  41. data/lib/buildingsync/model_articulation/exterior_floor_system_type.rb +64 -0
  42. data/lib/buildingsync/model_articulation/facility.rb +439 -0
  43. data/lib/buildingsync/model_articulation/foundation_system_type.rb +64 -0
  44. data/lib/buildingsync/model_articulation/hvac_system.rb +395 -0
  45. data/lib/buildingsync/model_articulation/lighting_system.rb +102 -0
  46. data/lib/buildingsync/model_articulation/loads_system.rb +287 -0
  47. data/lib/buildingsync/model_articulation/location_element.rb +129 -0
  48. data/lib/buildingsync/model_articulation/measure.rb +57 -0
  49. data/lib/buildingsync/model_articulation/roof_system_type.rb +64 -0
  50. data/lib/buildingsync/model_articulation/service_hot_water_system.rb +87 -0
  51. data/lib/buildingsync/model_articulation/site.rb +242 -0
  52. data/lib/buildingsync/model_articulation/spatial_element.rb +343 -0
  53. data/lib/buildingsync/model_articulation/wall_system_type.rb +64 -0
  54. data/lib/buildingsync/report.rb +217 -0
  55. data/lib/buildingsync/resource_use.rb +55 -0
  56. data/lib/buildingsync/scenario.rb +622 -0
  57. data/lib/buildingsync/selection_tool.rb +98 -0
  58. data/lib/buildingsync/time_series.rb +85 -0
  59. data/lib/buildingsync/translator.rb +167 -0
  60. data/lib/buildingsync/utility.rb +67 -0
  61. data/lib/buildingsync/version.rb +45 -0
  62. 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