# ******************************************************************************* # OpenStudio(R), Copyright (c) 2008-2020, Alliance for Sustainable Energy, LLC. # All rights reserved. # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # (1) Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # (2) Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # (3) Neither the name of the copyright holder nor the names of any contributors # may be used to endorse or promote products derived from this software without # specific prior written permission from the respective party. # # (4) Other than as required in clauses (1) and (2), distributions in any form # of modifications or other derivative works may not use the "OpenStudio" # trademark, "OS", "os", or any other confusingly similar designation without # specific prior written permission from Alliance for Sustainable Energy, LLC. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER, THE UNITED STATES # GOVERNMENT, OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # ******************************************************************************* # OpenStudio::Analysis::Workflow configured the list of measures to run and in what order module OpenStudio module Analysis class Workflow attr_reader :items # allow users to access the items via the measures attribute accessor alias measures items # Create an instance of the OpenStudio::Analysis::Workflow # # @return [Object] An OpenStudio::Analysis::Workflow object def initialize @items = [] end # Remove all the items in the workflow def clear @items.clear end # Add a measure to the workflow from a path. This will parse the measure.xml which must exist. # # @params instance_name [String] The name of the instance. This allows for multiple measures to be added to the workflow with uni que names # @params instance_display_name [String] The display name of the instance. This allows for multiple measures to be added to the workflow with unique names # @param local_path_to_measure [String] This is the local path to the measure directory, relative or absolute. It is used when zipping up all the measures. # @return [Object] Returns the measure that was added as an OpenStudio::AnalysisWorkflowStep object def add_measure_from_path(instance_name, instance_display_name, local_path_to_measure) measure_filename = 'measure.rb' if File.exist?(local_path_to_measure) && File.file?(local_path_to_measure) measure_filename = File.basename(local_path_to_measure) local_path_to_measure = File.dirname(local_path_to_measure) end if Dir.exist?(local_path_to_measure) && File.directory?(local_path_to_measure) measure_hash = nil if File.exist?(File.join(local_path_to_measure, 'measure.xml')) measure_hash = parse_measure_xml(File.join(local_path_to_measure, 'measure.xml')) else raise 'Could not find measure.xml' end add_measure(instance_name, instance_display_name, local_path_to_measure, measure_hash) else raise "could not find measure to add to workflow #{local_path_to_measure}" end @items.last end # Add a measure from the custom hash format without reading the measure.rb or measure.xml file # # @params instance_name [String] The name of the instance. This allows for multiple measures to be added to the workflow with unique names # @params instance_display_name [String] The display name of the instance. This allows for multiple measures to be added to the workflow with unique names # @param local_path_to_measure [String] This is the local path to the measure directory, relative or absolute. It is used when zipping up all the measures. # @param measure_metadata [Hash] Format of the measure.xml in JSON format # @return [Object] Returns the measure that was added as an OpenStudio::AnalysisWorkflowStep object def add_measure(instance_name, instance_display_name, local_path_to_measure, measure_metadata) @items << OpenStudio::Analysis::WorkflowStep.from_measure_hash(instance_name, instance_display_name, local_path_to_measure, measure_metadata) @items.last end # Add a measure from the analysis hash format # # @params instance_name [String] The name of the instance. This allows for multiple measures to be added to the workflow with unique names # @params instance_display_name [String] The display name of the instance. This allows for multiple measures to be added to the workflow with unique names # @param local_path_to_measure [String] This is the local path to the measure directory, relative or absolute. It is used when zipping up all the measures. # @param measure_metadata [Hash] Format of the measure.xml in JSON format # @return [Object] Returns the measure that was added as an OpenStudio::AnalysisWorkflowStep object def add_measure_from_analysis_hash(instance_name, instance_display_name, local_path_to_measure, measure_metadata) @items << OpenStudio::Analysis::WorkflowStep.from_analysis_hash(instance_name, instance_display_name, local_path_to_measure, measure_metadata) @items.last end # Add a measure from the format that Excel parses into. This is a helper method to map the excel data to the new # programmatic interface format # # @params measure [Hash] The measure in the format of the Excel translator # @return [Object] Returns the measure that was added as an OpenStudio::AnalysisWorkflowStep object def add_measure_from_excel(measure) hash = {} hash[:classname] = measure['measure_file_name'] hash[:name] = measure['name'] hash[:display_name] = measure['display_name'] hash[:measure_type] = measure['measure_type'] hash[:uid] = measure['uid'] ? measure['uid'] : SecureRandom.uuid hash[:version_id] = measure['version_id'] ? measure['version_id'] : SecureRandom.uuid # map the arguments - this can be a variable or argument, add them all as arguments first, # the make_variable will remove from arg and place into variables args = [] measure['variables'].each do |variable| args << { local_variable: variable['name'], variable_type: variable['type'], name: variable['name'], display_name: variable['display_name'], display_name_short: variable['display_name_short'], units: variable['units'], default_value: variable['distribution']['static_value'], value: variable['distribution']['static_value'] } end hash[:arguments] = args m = add_measure(measure['name'], measure['display_name'], measure['local_path_to_measure'], hash) measure['variables'].each do |variable| next unless ['variable', 'pivot'].include? variable['variable_type'] dist = { type: variable['distribution']['type'], minimum: variable['distribution']['min'], maximum: variable['distribution']['max'], mean: variable['distribution']['mean'], standard_deviation: variable['distribution']['stddev'], values: variable['distribution']['discrete_values'], weights: variable['distribution']['discrete_weights'], step_size: variable['distribution']['delta_x'] } opt = { variable_type: variable['variable_type'], variable_display_name_short: variable['display_name_short'], static_value: variable['distribution']['static_value'] } m.make_variable(variable['name'], variable['display_name'], dist, opt) end end # Add a measure from the format that CSV parses into. This is a helper method to map the csv data to the new # programmatic interface format # # @params measure [Hash] The measure in the format of the CSV translator # @return [Object] Returns the measure that was added as an OpenStudio::AnalysisWorkflowStep object def add_measure_from_csv(measure) hash = {} hash[:classname] = measure[:measure_data][:classname] hash[:name] = measure[:measure_data][:name] hash[:display_name] = measure[:measure_data][:display_name] hash[:measure_type] = measure[:measure_data][:measure_type] hash[:uid] = measure[:measure_data][:uid] ? measure[:measure_data][:uid] : SecureRandom.uuid hash[:version_id] = measure[:measure_data][:version_id] ? measure[:measure_data][:version_id] : SecureRandom.uuid # map the arguments - this can be a variable or argument, add them all as arguments first, # the make_variable will remove from arg and place into variables hash[:arguments] = measure[:args] m = add_measure(measure[:measure_data][:name], measure[:measure_data][:display_name], measure[:measure_data][:local_path_to_measure], hash) measure[:vars].each do |variable| next unless ['variable', 'pivot'].include? variable[:variable_type] dist = variable[:distribution] opt = { variable_type: variable[:variable_type], variable_display_name_short: variable[:display_name_short], static_value: variable[:distribution][:mode] } m.make_variable(variable[:name], variable[:display_name], dist, opt) end end # Iterate over all the WorkflowItems def each @items.each { |i| yield i } end # Find the measure by its instance name # # @params instance_name [String] instance name of the measure # @return [Object] The WorkflowStep with the instance_name def find_measure(instance_name) @items.find { |i| i.name == instance_name } end alias find_workflow_step find_measure # Return all the variables in the analysis as an array. The list that is returned is read only. # # @return [Array] All variables in the workflow def all_variables @items.map(&:variables).flatten end # Save the workflow to a hash object def to_hash(version = 1) h = nil if version == 1 arr = [] @items.each_with_index do |item, index| temp_h = item.to_hash(version) temp_h[:workflow_index] = index arr << temp_h end h = arr else raise "Version #{version} not yet implemented for to_hash" end h end # Save the workflow to a JSON string # # @return [String] JSON formatted string def to_json(version = 1) if version == 1 JSON.pretty_generate(to_hash(version)) else raise "Version #{version} not yet implemented for to_json" end end # Load from a from a hash # # @param h [Hash or String] Path to file or hash def self.load(h) # get the version of the file file_format_version = h[:file_format_version] ? h[:file_format_version] : 1 puts "Parsing file version #{file_format_version}" o = OpenStudio::Analysis::Workflow.new if h[:workflow] h[:workflow].each do |wf| puts "Adding measure #{wf[:name]}" # Go though the defined measure paths and try and find the local measure local_measure_dir = nil if wf[:measure_definition_directory_local] && Dir.exist?(wf[:measure_definition_directory_local]) local_measure_dir = wf[:measure_definition_directory_local] else # search in the measure paths for the measure OpenStudio::Analysis.measure_paths.each do |p| test_dir = File.join(p, File.basename(wf[:measure_definition_directory])) if Dir.exist?(test_dir) local_measure_dir = test_dir break end end end raise "Could not find local measure when loading workflow for #{wf[:measure_definition_class_name]} in #{File.basename(wf[:measure_definition_directory])}. You may have to add measure paths to OpenStudio::Analysis.measure_paths" unless local_measure_dir o.add_measure_from_analysis_hash(wf[:name], wf[:display_name], local_measure_dir, wf) end end o end # Read the Workflow description from a persisted file. The format at the moment is the current analysis.json # # @params filename [String] Path to file with the analysis.json to load # @return [Object] Return an instance of the workflow object def self.from_file(filename) o = nil if File.exist? filename j = JSON.parse(File.read(filename), symbolize_names: true) o = OpenStudio::Analysis::Workflow.load(j) else raise "Could not find workflow file #{filename}" end o end end end end