openstudio-workflow 1.2.1 → 1.2.2
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/CHANGELOG.md +72 -72
- data/README.md +93 -48
- data/Rakefile +36 -36
- data/lib/openstudio-workflow.rb +49 -49
- data/lib/openstudio/workflow/adapters/input/local.rb +244 -240
- data/lib/openstudio/workflow/adapters/output/local.rb +95 -95
- data/lib/openstudio/workflow/adapters/output/socket.rb +91 -91
- data/lib/openstudio/workflow/adapters/output/web.rb +66 -66
- data/lib/openstudio/workflow/adapters/output_adapter.rb +147 -147
- data/lib/openstudio/workflow/job.rb +22 -22
- data/lib/openstudio/workflow/jobs/resources/monthly_report.idf +222 -222
- data/lib/openstudio/workflow/jobs/run_energyplus.rb +49 -49
- data/lib/openstudio/workflow/jobs/run_ep_measures.rb +55 -55
- data/lib/openstudio/workflow/jobs/run_initialization.rb +169 -167
- data/lib/openstudio/workflow/jobs/run_os_measures.rb +69 -69
- data/lib/openstudio/workflow/jobs/run_postprocess.rb +53 -53
- data/lib/openstudio/workflow/jobs/run_preprocess.rb +69 -69
- data/lib/openstudio/workflow/jobs/run_reporting_measures.rb +98 -98
- data/lib/openstudio/workflow/jobs/run_translation.rb +61 -61
- data/lib/openstudio/workflow/multi_delegator.rb +46 -46
- data/lib/openstudio/workflow/registry.rb +137 -137
- data/lib/openstudio/workflow/run.rb +299 -299
- data/lib/openstudio/workflow/time_logger.rb +53 -53
- data/lib/openstudio/workflow/util.rb +14 -14
- data/lib/openstudio/workflow/util/energyplus.rb +566 -564
- data/lib/openstudio/workflow/util/io.rb +33 -33
- data/lib/openstudio/workflow/util/measure.rb +588 -588
- data/lib/openstudio/workflow/util/model.rb +100 -100
- data/lib/openstudio/workflow/util/post_process.rb +187 -187
- data/lib/openstudio/workflow/util/weather_file.rb +108 -108
- data/lib/openstudio/workflow/version.rb +24 -24
- data/lib/openstudio/workflow_json.rb +426 -426
- data/lib/openstudio/workflow_runner.rb +233 -215
- metadata +3 -3
@@ -1,100 +1,100 @@
|
|
1
|
-
module OpenStudio
|
2
|
-
module Workflow
|
3
|
-
module Util
|
4
|
-
# Manages routine tasks involving OpenStudio::Model or OpenStudio::Workflow objects, such as loading, saving, and
|
5
|
-
# translating them.
|
6
|
-
#
|
7
|
-
module Model
|
8
|
-
# Method to create / load an OSM file
|
9
|
-
#
|
10
|
-
# @param [String] osm_path The full path to an OSM file to load
|
11
|
-
# @param [Object] logger An optional logger to use for finding the OSM model
|
12
|
-
# @return [Object] The return from this method is a loaded OSM or a failure.
|
13
|
-
#
|
14
|
-
def load_osm(osm_path, logger)
|
15
|
-
logger.info 'Loading OSM model'
|
16
|
-
|
17
|
-
# Load the model and return it
|
18
|
-
logger.info "Reading in OSM model #{osm_path}"
|
19
|
-
|
20
|
-
loaded_model = nil
|
21
|
-
begin
|
22
|
-
translator = OpenStudio::OSVersion::VersionTranslator.new
|
23
|
-
loaded_model = translator.loadModel(osm_path)
|
24
|
-
rescue
|
25
|
-
# TODO: get translator working in embedded.
|
26
|
-
# Need to embed idd files
|
27
|
-
logger.warn 'OpenStudio VersionTranslator could not be loaded'
|
28
|
-
loaded_model = OpenStudio::Model::Model.load(osm_path)
|
29
|
-
end
|
30
|
-
raise "Failed to load OSM file #{osm_path}" if loaded_model.empty?
|
31
|
-
loaded_model.get
|
32
|
-
end
|
33
|
-
|
34
|
-
# Method to create / load an IDF file
|
35
|
-
#
|
36
|
-
# @param [String] idf_path Full path to the IDF
|
37
|
-
# @param [Object] logger An optional logger to use for finding the idf model
|
38
|
-
# @return [Object] The return from this method is a loaded IDF or a failure.
|
39
|
-
#
|
40
|
-
def load_idf(idf_path, logger)
|
41
|
-
logger.info 'Loading IDF model'
|
42
|
-
|
43
|
-
# Load the IDF into a workspace object and return it
|
44
|
-
logger.info "Reading in IDF model #{idf_path}"
|
45
|
-
|
46
|
-
idf = OpenStudio::Workspace.load(idf_path)
|
47
|
-
raise "Failed to load IDF file #{idf_path}" if idf.empty?
|
48
|
-
idf.get
|
49
|
-
end
|
50
|
-
|
51
|
-
# Translates a OpenStudio model object into an OpenStudio IDF object
|
52
|
-
#
|
53
|
-
# @param [Object] model the OpenStudio::Model instance to translate into an OpenStudio::Workspace object -- see
|
54
|
-
# the OpenStudio SDK for details on the process
|
55
|
-
# @return [Object] Returns and OpenStudio::Workspace object
|
56
|
-
# @todo (rhorsey) rescue errors here
|
57
|
-
#
|
58
|
-
def translate_to_energyplus(model, logger = nil)
|
59
|
-
logger = ::Logger.new(STDOUT) unless logger
|
60
|
-
logger.info 'Translate object to EnergyPlus IDF in preparation for EnergyPlus'
|
61
|
-
a = ::Time.now
|
62
|
-
# ensure objects exist for reporting purposes
|
63
|
-
model.getFacility
|
64
|
-
model.getBuilding
|
65
|
-
forward_translator = OpenStudio::EnergyPlus::ForwardTranslator.new
|
66
|
-
model_idf = forward_translator.translateModel(model)
|
67
|
-
b = ::Time.now
|
68
|
-
logger.info "Translate object to EnergyPlus IDF took #{b.to_f - a.to_f}"
|
69
|
-
model_idf
|
70
|
-
end
|
71
|
-
|
72
|
-
# Saves an OpenStudio model object to file
|
73
|
-
#
|
74
|
-
# @param [Object] model The OpenStudio::Model instance to save to file
|
75
|
-
# @param [String] save_directory Folder to save the model in
|
76
|
-
# @param [String] name ('in.osm') Option to define a non-standard name
|
77
|
-
# @return [String] OSM file name
|
78
|
-
#
|
79
|
-
def save_osm(model, save_directory, name = 'in.osm')
|
80
|
-
osm_filename = File.join(save_directory.to_s, name.to_s)
|
81
|
-
File.open(osm_filename, 'w') { |f| f << model.to_s }
|
82
|
-
osm_filename
|
83
|
-
end
|
84
|
-
|
85
|
-
# Saves an OpenStudio IDF model object to file
|
86
|
-
#
|
87
|
-
# @param [Object] model The OpenStudio::Workspace instance to save to file
|
88
|
-
# @param [String] save_directory Folder to save the model in
|
89
|
-
# @param [String] name ('in.osm') Option to define a non-standard name
|
90
|
-
# @return [String] IDF file name
|
91
|
-
#
|
92
|
-
def save_idf(model_idf, save_directory, name = 'in.idf')
|
93
|
-
idf_filename = File.join(save_directory.to_s, name.to_s)
|
94
|
-
File.open(idf_filename, 'w') { |f| f << model_idf.to_s }
|
95
|
-
idf_filename
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
1
|
+
module OpenStudio
|
2
|
+
module Workflow
|
3
|
+
module Util
|
4
|
+
# Manages routine tasks involving OpenStudio::Model or OpenStudio::Workflow objects, such as loading, saving, and
|
5
|
+
# translating them.
|
6
|
+
#
|
7
|
+
module Model
|
8
|
+
# Method to create / load an OSM file
|
9
|
+
#
|
10
|
+
# @param [String] osm_path The full path to an OSM file to load
|
11
|
+
# @param [Object] logger An optional logger to use for finding the OSM model
|
12
|
+
# @return [Object] The return from this method is a loaded OSM or a failure.
|
13
|
+
#
|
14
|
+
def load_osm(osm_path, logger)
|
15
|
+
logger.info 'Loading OSM model'
|
16
|
+
|
17
|
+
# Load the model and return it
|
18
|
+
logger.info "Reading in OSM model #{osm_path}"
|
19
|
+
|
20
|
+
loaded_model = nil
|
21
|
+
begin
|
22
|
+
translator = OpenStudio::OSVersion::VersionTranslator.new
|
23
|
+
loaded_model = translator.loadModel(osm_path)
|
24
|
+
rescue
|
25
|
+
# TODO: get translator working in embedded.
|
26
|
+
# Need to embed idd files
|
27
|
+
logger.warn 'OpenStudio VersionTranslator could not be loaded'
|
28
|
+
loaded_model = OpenStudio::Model::Model.load(osm_path)
|
29
|
+
end
|
30
|
+
raise "Failed to load OSM file #{osm_path}" if loaded_model.empty?
|
31
|
+
loaded_model.get
|
32
|
+
end
|
33
|
+
|
34
|
+
# Method to create / load an IDF file
|
35
|
+
#
|
36
|
+
# @param [String] idf_path Full path to the IDF
|
37
|
+
# @param [Object] logger An optional logger to use for finding the idf model
|
38
|
+
# @return [Object] The return from this method is a loaded IDF or a failure.
|
39
|
+
#
|
40
|
+
def load_idf(idf_path, logger)
|
41
|
+
logger.info 'Loading IDF model'
|
42
|
+
|
43
|
+
# Load the IDF into a workspace object and return it
|
44
|
+
logger.info "Reading in IDF model #{idf_path}"
|
45
|
+
|
46
|
+
idf = OpenStudio::Workspace.load(idf_path)
|
47
|
+
raise "Failed to load IDF file #{idf_path}" if idf.empty?
|
48
|
+
idf.get
|
49
|
+
end
|
50
|
+
|
51
|
+
# Translates a OpenStudio model object into an OpenStudio IDF object
|
52
|
+
#
|
53
|
+
# @param [Object] model the OpenStudio::Model instance to translate into an OpenStudio::Workspace object -- see
|
54
|
+
# the OpenStudio SDK for details on the process
|
55
|
+
# @return [Object] Returns and OpenStudio::Workspace object
|
56
|
+
# @todo (rhorsey) rescue errors here
|
57
|
+
#
|
58
|
+
def translate_to_energyplus(model, logger = nil)
|
59
|
+
logger = ::Logger.new(STDOUT) unless logger
|
60
|
+
logger.info 'Translate object to EnergyPlus IDF in preparation for EnergyPlus'
|
61
|
+
a = ::Time.now
|
62
|
+
# ensure objects exist for reporting purposes
|
63
|
+
model.getFacility
|
64
|
+
model.getBuilding
|
65
|
+
forward_translator = OpenStudio::EnergyPlus::ForwardTranslator.new
|
66
|
+
model_idf = forward_translator.translateModel(model)
|
67
|
+
b = ::Time.now
|
68
|
+
logger.info "Translate object to EnergyPlus IDF took #{b.to_f - a.to_f}"
|
69
|
+
model_idf
|
70
|
+
end
|
71
|
+
|
72
|
+
# Saves an OpenStudio model object to file
|
73
|
+
#
|
74
|
+
# @param [Object] model The OpenStudio::Model instance to save to file
|
75
|
+
# @param [String] save_directory Folder to save the model in
|
76
|
+
# @param [String] name ('in.osm') Option to define a non-standard name
|
77
|
+
# @return [String] OSM file name
|
78
|
+
#
|
79
|
+
def save_osm(model, save_directory, name = 'in.osm')
|
80
|
+
osm_filename = File.join(save_directory.to_s, name.to_s)
|
81
|
+
File.open(osm_filename, 'w') { |f| f << model.to_s }
|
82
|
+
osm_filename
|
83
|
+
end
|
84
|
+
|
85
|
+
# Saves an OpenStudio IDF model object to file
|
86
|
+
#
|
87
|
+
# @param [Object] model The OpenStudio::Workspace instance to save to file
|
88
|
+
# @param [String] save_directory Folder to save the model in
|
89
|
+
# @param [String] name ('in.osm') Option to define a non-standard name
|
90
|
+
# @return [String] IDF file name
|
91
|
+
#
|
92
|
+
def save_idf(model_idf, save_directory, name = 'in.idf')
|
93
|
+
idf_filename = File.join(save_directory.to_s, name.to_s)
|
94
|
+
File.open(idf_filename, 'w') { |f| f << model_idf.to_s }
|
95
|
+
idf_filename
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -1,187 +1,187 @@
|
|
1
|
-
module OpenStudio
|
2
|
-
module Workflow
|
3
|
-
module Util
|
4
|
-
require 'openstudio/workflow/util/measure'
|
5
|
-
require 'csv'
|
6
|
-
require 'rexml/document'
|
7
|
-
|
8
|
-
# This module serves as a wrapper around various post-processing tasks used to manage outputs
|
9
|
-
# @todo (rhorsey) ummmm. So some of this is pretty ugly. Since @dmacumber had ideas about this maybe he can figure
|
10
|
-
# out what to do about it all
|
11
|
-
# @todo (nlong) the output adapter restructure will frack up the extraction method royally
|
12
|
-
#
|
13
|
-
module PostProcess
|
14
|
-
# This method loads a sql file into OpenStudio and returns it
|
15
|
-
#
|
16
|
-
# @param [String] sql_file Absolute path to the sql file to be loaded
|
17
|
-
# @return [Object, nil] The OpenStudio::SqlFile object, or nil if it could not be found
|
18
|
-
#
|
19
|
-
def load_sql_file(sql_file)
|
20
|
-
return nil unless File.exist? sql_file
|
21
|
-
OpenStudio::SqlFile.new(@sql_filename)
|
22
|
-
end
|
23
|
-
|
24
|
-
# This method parses all sorts of stuff which something needs
|
25
|
-
#
|
26
|
-
# @param [String] run_dir The directory that the simulation was run in
|
27
|
-
# @return [Hash, Hash] results and objective_function (which may be empty) are returned
|
28
|
-
# @todo (rhorsey) fix the description
|
29
|
-
#
|
30
|
-
def run_extract_inputs_and_outputs(run_dir, logger)
|
31
|
-
# For xml, the measure attributes are in the measure_attributes_xml.json file
|
32
|
-
# TODO: somehow pass the metadata around on which JSONs to suck into the database
|
33
|
-
results = {}
|
34
|
-
# Inputs are in the measure_attributes.json file
|
35
|
-
if File.exist? "#{run_dir}/measure_attributes.json"
|
36
|
-
h = JSON.parse(File.read("#{run_dir}/measure_attributes.json"), symbolize_names: true)
|
37
|
-
h = rename_hash_keys(h, logger)
|
38
|
-
results.merge! h
|
39
|
-
end
|
40
|
-
|
41
|
-
logger.info 'Saving the result hash to file'
|
42
|
-
File.open("#{run_dir}/results.json", 'w') { |f| f << JSON.pretty_generate(results) }
|
43
|
-
|
44
|
-
objective_functions = {}
|
45
|
-
if @registry[:analysis]
|
46
|
-
logger.info 'Iterating over Analysis JSON Output Variables'
|
47
|
-
# Save the objective functions to the object for sending back to the simulation executive
|
48
|
-
analysis_json = @registry[:analysis]
|
49
|
-
if analysis_json[:analysis] && analysis_json[:analysis][:output_variables]
|
50
|
-
analysis_json[:analysis][:output_variables].each do |variable|
|
51
|
-
# determine which ones are the objective functions (code smell: todo: use enumerator)
|
52
|
-
if variable[:objective_function]
|
53
|
-
logger.info "Looking for objective function #{variable[:name]}"
|
54
|
-
# TODO: move this to cleaner logic. Use ostruct?
|
55
|
-
k, v = variable[:name].split('.')
|
56
|
-
|
57
|
-
# look for the objective function key and make sure that it is not nil. False is an okay obj function.
|
58
|
-
if results.key?(k.to_sym) && !results[k.to_sym][v.to_sym].nil?
|
59
|
-
objective_functions["objective_function_#{variable[:objective_function_index] + 1}"] = results[k.to_sym][v.to_sym]
|
60
|
-
if variable[:objective_function_target]
|
61
|
-
logger.info "Found objective function target for #{variable[:name]}"
|
62
|
-
objective_functions["objective_function_target_#{variable[:objective_function_index] + 1}"] = variable[:objective_function_target].to_f
|
63
|
-
end
|
64
|
-
if variable[:scaling_factor]
|
65
|
-
logger.info "Found scaling factor for #{variable[:name]}"
|
66
|
-
objective_functions["scaling_factor_#{variable[:objective_function_index] + 1}"] = variable[:scaling_factor].to_f
|
67
|
-
end
|
68
|
-
if variable[:objective_function_group]
|
69
|
-
logger.info "Found objective function group for #{variable[:name]}"
|
70
|
-
objective_functions["objective_function_group_#{variable[:objective_function_index] + 1}"] = variable[:objective_function_group].to_f
|
71
|
-
end
|
72
|
-
else
|
73
|
-
logger.warn "No results for objective function #{variable[:name]}"
|
74
|
-
objective_functions["objective_function_#{variable[:objective_function_index] + 1}"] = Float::MAX
|
75
|
-
objective_functions["objective_function_target_#{variable[:objective_function_index] + 1}"] = nil
|
76
|
-
objective_functions["scaling_factor_#{variable[:objective_function_index] + 1}"] = nil
|
77
|
-
objective_functions["objective_function_group_#{variable[:objective_function_index] + 1}"] = nil
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
return results, objective_functions
|
85
|
-
end
|
86
|
-
|
87
|
-
# Remove any invalid characters in the measure attribute keys. Periods and Pipes are the most problematic
|
88
|
-
# because mongo does not allow hash keys with periods, and the pipes are used in the map/reduce method that
|
89
|
-
# was written to speed up the data write in openstudio-server. Also remove any trailing underscores and spaces
|
90
|
-
#
|
91
|
-
# @param [Hash] hash Any hash with potentially problematic characters
|
92
|
-
# @param [Logger] logger Logger to write to
|
93
|
-
#
|
94
|
-
def rename_hash_keys(hash, logger)
|
95
|
-
# @todo should we log the name changes?
|
96
|
-
regex = /[|!@#\$%^&\*\(\)\{\}\\\[\];:'",<.>\/?\+=]+/
|
97
|
-
|
98
|
-
rename_keys = lambda do |h|
|
99
|
-
if Hash === h
|
100
|
-
h.each_key do |key|
|
101
|
-
if key.to_s =~ regex
|
102
|
-
logger.warn "Renaming result key '#{key}' to remove invalid characters"
|
103
|
-
end
|
104
|
-
end
|
105
|
-
Hash[h.map { |k, v| [k.to_s.gsub(regex, '_').squeeze('_').gsub(/[_\s]+$/, '').chomp.to_sym, rename_keys[v]] }]
|
106
|
-
else
|
107
|
-
h
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
rename_keys[hash]
|
112
|
-
end
|
113
|
-
|
114
|
-
|
115
|
-
# Save reports to a common directory
|
116
|
-
#
|
117
|
-
# @param [String] run_dir
|
118
|
-
# @param [String] directory
|
119
|
-
# @param [String] logger
|
120
|
-
#
|
121
|
-
def gather_reports(run_dir, directory, workflow_json, logger)
|
122
|
-
logger.info run_dir
|
123
|
-
logger.info directory
|
124
|
-
|
125
|
-
FileUtils.mkdir_p "#{directory}/reports"
|
126
|
-
|
127
|
-
# try to find the energyplus result file
|
128
|
-
eplus_html = "#{run_dir}/eplustbl.htm"
|
129
|
-
if File.exist? eplus_html
|
130
|
-
# do some encoding on the html if possible
|
131
|
-
html = File.read(eplus_html)
|
132
|
-
html = html.force_encoding('ISO-8859-1').encode('utf-8', replace: nil)
|
133
|
-
logger.info "Saving EnergyPlus HTML report to #{directory}/reports/eplustbl.html"
|
134
|
-
File.open("#{directory}/reports/eplustbl.html", 'w') { |f| f << html }
|
135
|
-
end
|
136
|
-
|
137
|
-
# Also, find any "report*.*" files
|
138
|
-
Dir["#{run_dir}/*/report*.*"].each do |report|
|
139
|
-
# HRH: This is a temporary work-around to support PAT 2.1 pretty names AND the CLI while we roll a WorkflowJSON solution
|
140
|
-
measure_dir_name = File.dirname(report).split(File::SEPARATOR).last.gsub(/[0-9][0-9][0-9]_/, '')
|
141
|
-
measure_xml_path = File.absolute_path(File.join(File.dirname(report), '../../..', 'measures',
|
142
|
-
measure_dir_name, 'measure.xml'))
|
143
|
-
logger.info "measure_xml_path: #{measure_xml_path}"
|
144
|
-
if File.exists? measure_xml_path
|
145
|
-
measure_xml = REXML::Document.new File.read(measure_xml_path)
|
146
|
-
measure_class_name = OpenStudio.toUnderscoreCase(measure_xml.root.elements['class_name'].text)
|
147
|
-
else
|
148
|
-
measure_class_name = OpenStudio.toUnderscoreCase(measure_dir_name)
|
149
|
-
end
|
150
|
-
file_ext = File.extname(report)
|
151
|
-
append_str = File.basename(report, '.*')
|
152
|
-
new_file_name = "#{directory}/reports/#{measure_class_name}_#{append_str}#{file_ext}"
|
153
|
-
logger.info "Saving report #{report} to #{new_file_name}"
|
154
|
-
FileUtils.copy report, new_file_name
|
155
|
-
end
|
156
|
-
|
157
|
-
# Remove empty directories in run folder
|
158
|
-
Dir["#{run_dir}/*"].select { |d| File.directory? d }.select { |d| (Dir.entries(d) - %w(. ..)).empty? }.each do |d|
|
159
|
-
logger.info "Removing empty directory #{d}"
|
160
|
-
Dir.rmdir d
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
|
165
|
-
# A general post-processing step which could be made significantly more modular
|
166
|
-
#
|
167
|
-
# @param [String] run_dir
|
168
|
-
#
|
169
|
-
def cleanup(run_dir, directory, logger)
|
170
|
-
|
171
|
-
|
172
|
-
paths_to_rm = []
|
173
|
-
# paths_to_rm << Pathname.glob("#{run_dir}/*.osm")
|
174
|
-
# paths_to_rm << Pathname.glob("#{run_dir}/*.idf") # keep the idfs
|
175
|
-
# paths_to_rm << Pathname.glob("*.audit")
|
176
|
-
# paths_to_rm << Pathname.glob("*.bnd")
|
177
|
-
# paths_to_rm << Pathname.glob("#{run_dir}/*.eso")
|
178
|
-
paths_to_rm << Pathname.glob("#{run_dir}/*.mtr")
|
179
|
-
paths_to_rm << Pathname.glob("#{run_dir}/*.epw")
|
180
|
-
#paths_to_rm << Pathname.glob("#{run_dir}/*.mtd")
|
181
|
-
#paths_to_rm << Pathname.glob("#{run_dir}/*.rdd")
|
182
|
-
paths_to_rm.each { |p| FileUtils.rm_rf(p) }
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
186
|
-
end
|
187
|
-
end
|
1
|
+
module OpenStudio
|
2
|
+
module Workflow
|
3
|
+
module Util
|
4
|
+
require 'openstudio/workflow/util/measure'
|
5
|
+
require 'csv'
|
6
|
+
require 'rexml/document'
|
7
|
+
|
8
|
+
# This module serves as a wrapper around various post-processing tasks used to manage outputs
|
9
|
+
# @todo (rhorsey) ummmm. So some of this is pretty ugly. Since @dmacumber had ideas about this maybe he can figure
|
10
|
+
# out what to do about it all
|
11
|
+
# @todo (nlong) the output adapter restructure will frack up the extraction method royally
|
12
|
+
#
|
13
|
+
module PostProcess
|
14
|
+
# This method loads a sql file into OpenStudio and returns it
|
15
|
+
#
|
16
|
+
# @param [String] sql_file Absolute path to the sql file to be loaded
|
17
|
+
# @return [Object, nil] The OpenStudio::SqlFile object, or nil if it could not be found
|
18
|
+
#
|
19
|
+
def load_sql_file(sql_file)
|
20
|
+
return nil unless File.exist? sql_file
|
21
|
+
OpenStudio::SqlFile.new(@sql_filename)
|
22
|
+
end
|
23
|
+
|
24
|
+
# This method parses all sorts of stuff which something needs
|
25
|
+
#
|
26
|
+
# @param [String] run_dir The directory that the simulation was run in
|
27
|
+
# @return [Hash, Hash] results and objective_function (which may be empty) are returned
|
28
|
+
# @todo (rhorsey) fix the description
|
29
|
+
#
|
30
|
+
def run_extract_inputs_and_outputs(run_dir, logger)
|
31
|
+
# For xml, the measure attributes are in the measure_attributes_xml.json file
|
32
|
+
# TODO: somehow pass the metadata around on which JSONs to suck into the database
|
33
|
+
results = {}
|
34
|
+
# Inputs are in the measure_attributes.json file
|
35
|
+
if File.exist? "#{run_dir}/measure_attributes.json"
|
36
|
+
h = JSON.parse(File.read("#{run_dir}/measure_attributes.json"), symbolize_names: true)
|
37
|
+
h = rename_hash_keys(h, logger)
|
38
|
+
results.merge! h
|
39
|
+
end
|
40
|
+
|
41
|
+
logger.info 'Saving the result hash to file'
|
42
|
+
File.open("#{run_dir}/results.json", 'w') { |f| f << JSON.pretty_generate(results) }
|
43
|
+
|
44
|
+
objective_functions = {}
|
45
|
+
if @registry[:analysis]
|
46
|
+
logger.info 'Iterating over Analysis JSON Output Variables'
|
47
|
+
# Save the objective functions to the object for sending back to the simulation executive
|
48
|
+
analysis_json = @registry[:analysis]
|
49
|
+
if analysis_json[:analysis] && analysis_json[:analysis][:output_variables]
|
50
|
+
analysis_json[:analysis][:output_variables].each do |variable|
|
51
|
+
# determine which ones are the objective functions (code smell: todo: use enumerator)
|
52
|
+
if variable[:objective_function]
|
53
|
+
logger.info "Looking for objective function #{variable[:name]}"
|
54
|
+
# TODO: move this to cleaner logic. Use ostruct?
|
55
|
+
k, v = variable[:name].split('.')
|
56
|
+
|
57
|
+
# look for the objective function key and make sure that it is not nil. False is an okay obj function.
|
58
|
+
if results.key?(k.to_sym) && !results[k.to_sym][v.to_sym].nil?
|
59
|
+
objective_functions["objective_function_#{variable[:objective_function_index] + 1}"] = results[k.to_sym][v.to_sym]
|
60
|
+
if variable[:objective_function_target]
|
61
|
+
logger.info "Found objective function target for #{variable[:name]}"
|
62
|
+
objective_functions["objective_function_target_#{variable[:objective_function_index] + 1}"] = variable[:objective_function_target].to_f
|
63
|
+
end
|
64
|
+
if variable[:scaling_factor]
|
65
|
+
logger.info "Found scaling factor for #{variable[:name]}"
|
66
|
+
objective_functions["scaling_factor_#{variable[:objective_function_index] + 1}"] = variable[:scaling_factor].to_f
|
67
|
+
end
|
68
|
+
if variable[:objective_function_group]
|
69
|
+
logger.info "Found objective function group for #{variable[:name]}"
|
70
|
+
objective_functions["objective_function_group_#{variable[:objective_function_index] + 1}"] = variable[:objective_function_group].to_f
|
71
|
+
end
|
72
|
+
else
|
73
|
+
logger.warn "No results for objective function #{variable[:name]}"
|
74
|
+
objective_functions["objective_function_#{variable[:objective_function_index] + 1}"] = Float::MAX
|
75
|
+
objective_functions["objective_function_target_#{variable[:objective_function_index] + 1}"] = nil
|
76
|
+
objective_functions["scaling_factor_#{variable[:objective_function_index] + 1}"] = nil
|
77
|
+
objective_functions["objective_function_group_#{variable[:objective_function_index] + 1}"] = nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
return results, objective_functions
|
85
|
+
end
|
86
|
+
|
87
|
+
# Remove any invalid characters in the measure attribute keys. Periods and Pipes are the most problematic
|
88
|
+
# because mongo does not allow hash keys with periods, and the pipes are used in the map/reduce method that
|
89
|
+
# was written to speed up the data write in openstudio-server. Also remove any trailing underscores and spaces
|
90
|
+
#
|
91
|
+
# @param [Hash] hash Any hash with potentially problematic characters
|
92
|
+
# @param [Logger] logger Logger to write to
|
93
|
+
#
|
94
|
+
def rename_hash_keys(hash, logger)
|
95
|
+
# @todo should we log the name changes?
|
96
|
+
regex = /[|!@#\$%^&\*\(\)\{\}\\\[\];:'",<.>\/?\+=]+/
|
97
|
+
|
98
|
+
rename_keys = lambda do |h|
|
99
|
+
if Hash === h
|
100
|
+
h.each_key do |key|
|
101
|
+
if key.to_s =~ regex
|
102
|
+
logger.warn "Renaming result key '#{key}' to remove invalid characters"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
Hash[h.map { |k, v| [k.to_s.gsub(regex, '_').squeeze('_').gsub(/[_\s]+$/, '').chomp.to_sym, rename_keys[v]] }]
|
106
|
+
else
|
107
|
+
h
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
rename_keys[hash]
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
# Save reports to a common directory
|
116
|
+
#
|
117
|
+
# @param [String] run_dir
|
118
|
+
# @param [String] directory
|
119
|
+
# @param [String] logger
|
120
|
+
#
|
121
|
+
def gather_reports(run_dir, directory, workflow_json, logger)
|
122
|
+
logger.info run_dir
|
123
|
+
logger.info directory
|
124
|
+
|
125
|
+
FileUtils.mkdir_p "#{directory}/reports"
|
126
|
+
|
127
|
+
# try to find the energyplus result file
|
128
|
+
eplus_html = "#{run_dir}/eplustbl.htm"
|
129
|
+
if File.exist? eplus_html
|
130
|
+
# do some encoding on the html if possible
|
131
|
+
html = File.read(eplus_html)
|
132
|
+
html = html.force_encoding('ISO-8859-1').encode('utf-8', replace: nil)
|
133
|
+
logger.info "Saving EnergyPlus HTML report to #{directory}/reports/eplustbl.html"
|
134
|
+
File.open("#{directory}/reports/eplustbl.html", 'w') { |f| f << html }
|
135
|
+
end
|
136
|
+
|
137
|
+
# Also, find any "report*.*" files
|
138
|
+
Dir["#{run_dir}/*/report*.*"].each do |report|
|
139
|
+
# HRH: This is a temporary work-around to support PAT 2.1 pretty names AND the CLI while we roll a WorkflowJSON solution
|
140
|
+
measure_dir_name = File.dirname(report).split(File::SEPARATOR).last.gsub(/[0-9][0-9][0-9]_/, '')
|
141
|
+
measure_xml_path = File.absolute_path(File.join(File.dirname(report), '../../..', 'measures',
|
142
|
+
measure_dir_name, 'measure.xml'))
|
143
|
+
logger.info "measure_xml_path: #{measure_xml_path}"
|
144
|
+
if File.exists? measure_xml_path
|
145
|
+
measure_xml = REXML::Document.new File.read(measure_xml_path)
|
146
|
+
measure_class_name = OpenStudio.toUnderscoreCase(measure_xml.root.elements['class_name'].text)
|
147
|
+
else
|
148
|
+
measure_class_name = OpenStudio.toUnderscoreCase(measure_dir_name)
|
149
|
+
end
|
150
|
+
file_ext = File.extname(report)
|
151
|
+
append_str = File.basename(report, '.*')
|
152
|
+
new_file_name = "#{directory}/reports/#{measure_class_name}_#{append_str}#{file_ext}"
|
153
|
+
logger.info "Saving report #{report} to #{new_file_name}"
|
154
|
+
FileUtils.copy report, new_file_name
|
155
|
+
end
|
156
|
+
|
157
|
+
# Remove empty directories in run folder
|
158
|
+
Dir["#{run_dir}/*"].select { |d| File.directory? d }.select { |d| (Dir.entries(d) - %w(. ..)).empty? }.each do |d|
|
159
|
+
logger.info "Removing empty directory #{d}"
|
160
|
+
Dir.rmdir d
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
# A general post-processing step which could be made significantly more modular
|
166
|
+
#
|
167
|
+
# @param [String] run_dir
|
168
|
+
#
|
169
|
+
def cleanup(run_dir, directory, logger)
|
170
|
+
|
171
|
+
|
172
|
+
paths_to_rm = []
|
173
|
+
# paths_to_rm << Pathname.glob("#{run_dir}/*.osm")
|
174
|
+
# paths_to_rm << Pathname.glob("#{run_dir}/*.idf") # keep the idfs
|
175
|
+
# paths_to_rm << Pathname.glob("*.audit")
|
176
|
+
# paths_to_rm << Pathname.glob("*.bnd")
|
177
|
+
# paths_to_rm << Pathname.glob("#{run_dir}/*.eso")
|
178
|
+
paths_to_rm << Pathname.glob("#{run_dir}/*.mtr")
|
179
|
+
paths_to_rm << Pathname.glob("#{run_dir}/*.epw")
|
180
|
+
#paths_to_rm << Pathname.glob("#{run_dir}/*.mtd")
|
181
|
+
#paths_to_rm << Pathname.glob("#{run_dir}/*.rdd")
|
182
|
+
paths_to_rm.each { |p| FileUtils.rm_rf(p) }
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|