Module: OpenStudio::Workflow::Util::PostProcess

Included in:
RunPostprocess, RunReportingMeasures
Defined in:
lib/openstudio/workflow/util/post_process.rb

Overview

TODO:

(rhorsey) ummmm. So some of this is pretty ugly. Since @dmacumber had ideas about this maybe he can figure out what to do about it all

TODO:

(nlong) the output adapter restructure will frack up the extraction method royally

This module serves as a wrapper around various post-processing tasks used to manage outputs

Instance Method Summary collapse

Instance Method Details

#cleanup(run_dir, directory, logger) ⇒ Object

A general post-processing step which could be made significantly more modular

Parameters:



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/openstudio/workflow/util/post_process.rb', line 169

def cleanup(run_dir, directory, logger)


  paths_to_rm = []
  # paths_to_rm << Pathname.glob("#{run_dir}/*.osm")
  # paths_to_rm << Pathname.glob("#{run_dir}/*.idf") # keep the idfs
  # paths_to_rm << Pathname.glob("*.audit")
  # paths_to_rm << Pathname.glob("*.bnd")
  # paths_to_rm << Pathname.glob("#{run_dir}/*.eso")
  paths_to_rm << Pathname.glob("#{run_dir}/*.mtr")
  paths_to_rm << Pathname.glob("#{run_dir}/*.epw")
  #paths_to_rm << Pathname.glob("#{run_dir}/*.mtd")
  #paths_to_rm << Pathname.glob("#{run_dir}/*.rdd")
  paths_to_rm.each { |p| FileUtils.rm_rf(p) }
end

#gather_reports(run_dir, directory, workflow_json, logger) ⇒ Object

Save reports to a common directory

Parameters:



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/openstudio/workflow/util/post_process.rb', line 121

def gather_reports(run_dir, directory, workflow_json, logger)
  logger.info run_dir
  logger.info directory

  FileUtils.mkdir_p "#{directory}/reports"

  # try to find the energyplus result file
  eplus_html = "#{run_dir}/eplustbl.htm"
  if File.exist? eplus_html
    # do some encoding on the html if possible
    html = File.read(eplus_html)
    html = html.force_encoding('ISO-8859-1').encode('utf-8', replace: nil)
    logger.info "Saving EnergyPlus HTML report to #{directory}/reports/eplustbl.html"
    File.open("#{directory}/reports/eplustbl.html", 'w') { |f| f << html }
  end

  # Also, find any "report*.*" files
  Dir["#{run_dir}/*/report*.*"].each do |report|
    # HRH: This is a temporary work-around to support PAT 2.1 pretty names AND the CLI while we roll a WorkflowJSON solution
    measure_dir_name = File.dirname(report).split(File::SEPARATOR).last.gsub(/[0-9][0-9][0-9]_/, '')
    measure_xml_path = File.absolute_path(File.join(File.dirname(report), '../../..', 'measures',
                                                    measure_dir_name, 'measure.xml'))
    logger.info "measure_xml_path: #{measure_xml_path}"
    if File.exists? measure_xml_path
      measure_xml = REXML::Document.new File.read(measure_xml_path)
      measure_class_name = OpenStudio.toUnderscoreCase(measure_xml.root.elements['class_name'].text)
    else
      measure_class_name = OpenStudio.toUnderscoreCase(measure_dir_name)
    end
    file_ext = File.extname(report)
    append_str = File.basename(report, '.*')
    new_file_name = "#{directory}/reports/#{measure_class_name}_#{append_str}#{file_ext}"
    logger.info "Saving report #{report} to #{new_file_name}"
    FileUtils.copy report, new_file_name
  end

  # Remove empty directories in run folder
  Dir["#{run_dir}/*"].select { |d| File.directory? d }.select { |d| (Dir.entries(d) - %w(. ..)).empty? }.each do |d|
    logger.info "Removing empty directory #{d}"
    Dir.rmdir d
  end
end

#load_sql_file(sql_file) ⇒ Object?

This method loads a sql file into OpenStudio and returns it

Parameters:

  • sql_file (String)

    Absolute path to the sql file to be loaded

Returns:

  • (Object, nil)

    The OpenStudio::SqlFile object, or nil if it could not be found



19
20
21
22
# File 'lib/openstudio/workflow/util/post_process.rb', line 19

def load_sql_file(sql_file)
  return nil unless File.exist? sql_file
  OpenStudio::SqlFile.new(@sql_filename)
end

#rename_hash_keys(hash, logger) ⇒ Object

Remove any invalid characters in the measure attribute keys. Periods and Pipes are the most problematic

because mongo does not allow hash keys with periods, and the pipes are used in the map/reduce method that
was written to speed up the data write in openstudio-server. Also remove any trailing underscores and spaces

Parameters:

  • hash (Hash)

    Any hash with potentially problematic characters

  • logger (Logger)

    Logger to write to



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/openstudio/workflow/util/post_process.rb', line 94

def rename_hash_keys(hash, logger)
  # @todo should we log the name changes?
  regex = /[|!@#\$%^&\*\(\)\{\}\\\[\];:'",<.>\/?\+=]+/

  rename_keys = lambda do |h|
    if Hash === h
      h.each_key do |key|
        if key.to_s =~ regex
          logger.warn "Renaming result key '#{key}' to remove invalid characters"
        end
      end
      Hash[h.map { |k, v| [k.to_s.gsub(regex, '_').squeeze('_').gsub(/[_\s]+$/, '').chomp.to_sym, rename_keys[v]] }]
    else
      h
    end
  end

  rename_keys[hash]
end

#run_extract_inputs_and_outputs(run_dir, logger) ⇒ Hash

TODO:

(rhorsey) fix the description

This method parses all sorts of stuff which something needs

Parameters:

  • run_dir (String)

    The directory that the simulation was run in

Returns:

  • (Hash, Hash)

    results and objective_function (which may be empty) are returned



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/openstudio/workflow/util/post_process.rb', line 30

def run_extract_inputs_and_outputs(run_dir, logger)
  # For xml, the measure attributes are in the measure_attributes_xml.json file
  # TODO: somehow pass the metadata around on which JSONs to suck into the database
  results = {}
  # Inputs are in the measure_attributes.json file
  if File.exist? "#{run_dir}/measure_attributes.json"
    h = JSON.parse(File.read("#{run_dir}/measure_attributes.json"), symbolize_names: true)
    h = rename_hash_keys(h, logger)
    results.merge! h
  end

  logger.info 'Saving the result hash to file'
  File.open("#{run_dir}/results.json", 'w') { |f| f << JSON.pretty_generate(results) }

  objective_functions = {}
  if @registry[:analysis]
    logger.info 'Iterating over Analysis JSON Output Variables'
    # Save the objective functions to the object for sending back to the simulation executive
    analysis_json = @registry[:analysis]
    if analysis_json[:analysis] && analysis_json[:analysis][:output_variables]
      analysis_json[:analysis][:output_variables].each do |variable|
        # determine which ones are the objective functions (code smell: todo: use enumerator)
        if variable[:objective_function]
          logger.info "Looking for objective function #{variable[:name]}"
          # TODO: move this to cleaner logic. Use ostruct?
          k, v = variable[:name].split('.')

          # look for the objective function key and make sure that it is not nil. False is an okay obj function.
          if results.key?(k.to_sym) && !results[k.to_sym][v.to_sym].nil?
            objective_functions["objective_function_#{variable[:objective_function_index] + 1}"] = results[k.to_sym][v.to_sym]
            if variable[:objective_function_target]
              logger.info "Found objective function target for #{variable[:name]}"
              objective_functions["objective_function_target_#{variable[:objective_function_index] + 1}"] = variable[:objective_function_target].to_f
            end
            if variable[:scaling_factor]
              logger.info "Found scaling factor for #{variable[:name]}"
              objective_functions["scaling_factor_#{variable[:objective_function_index] + 1}"] = variable[:scaling_factor].to_f
            end
            if variable[:objective_function_group]
              logger.info "Found objective function group for #{variable[:name]}"
              objective_functions["objective_function_group_#{variable[:objective_function_index] + 1}"] = variable[:objective_function_group].to_f
            end
          else
            logger.warn "No results for objective function #{variable[:name]}"
            objective_functions["objective_function_#{variable[:objective_function_index] + 1}"] = Float::MAX
            objective_functions["objective_function_target_#{variable[:objective_function_index] + 1}"] = nil
            objective_functions["scaling_factor_#{variable[:objective_function_index] + 1}"] = nil
            objective_functions["objective_function_group_#{variable[:objective_function_index] + 1}"] = nil
          end
        end
      end
    end
  end

  return results, objective_functions
end