openstudio-workflow 0.0.3 → 0.0.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8fa9aff18f17a837285bcc9bc72d8beff2b82028
4
- data.tar.gz: 871e9aeffdd30c228cf804c758ea9a8918fa87ea
3
+ metadata.gz: 0530685f749da9f5112920053f26b8934ab4861e
4
+ data.tar.gz: 312ebc0a8b2844f94796214054605e414fc5606f
5
5
  SHA512:
6
- metadata.gz: eee7ff6d5a939d168d981b1685d98e6264cabe87c0f8cf1a8693402ac8b635a0b6deab233a9675790c82e51a7bc5ce2335d21934d69a9555d279b3421b10d856
7
- data.tar.gz: 37c6b1264805b11be32b885950d1aa5492dd4daf2c66c2e3a53402bc8749c071b218db914122b8063779fefa683eabd5e76329277238f6bc7ff55cd6b9b52c10
6
+ metadata.gz: eebca1c2eac830150a5c8cace44e77724d12090109bd34b224a228645930f85ca7f3bb068bbc254e0437719e9e8a5a23aaf5850aad18fdf45fe439cfe5b29f23
7
+ data.tar.gz: 6eb722576d2142d9de21aa193703783ee4115ffa203a4d5f99c38438524cfa974e9972dc7485289790fc83736bf1fc97be1aba0bf4c099ac9eb4bf41a036bab8
data/CHANGELOG.md CHANGED
@@ -1,6 +1,13 @@
1
1
  OpenStudio::Workflow Change Log
2
2
  ==================================
3
3
 
4
+ Version 0.0.4 (Unreleased)
5
+ -------------
6
+ * Include rubyXL gem for reading/writing MS Excel files
7
+ * Remove invalid characters from OpenStudio Measure Attributes. /[|!@#\$%^&\*\(\)\{\}\\\[\]|;:'",<.>\/?\+=]+/
8
+ * Fix objective functions to read from any of the results hash, not just the standard report legacy
9
+ * Add time logger and reporting measure
10
+
4
11
  Version 0.0.3
5
12
  --------------
6
13
  * Allow measures to set weather file in a measure and have it update what EnergyPlus uses for the weather file.
@@ -18,6 +18,7 @@
18
18
  ######################################################################
19
19
 
20
20
  require 'pp'
21
+ require 'rubyXL'
21
22
  require 'multi_json'
22
23
  require 'colored'
23
24
  require 'fileutils'
@@ -35,6 +36,7 @@ require 'openstudio/workflow/version'
35
36
  require 'openstudio/workflow/multi_delegator'
36
37
  require 'openstudio/workflow/run'
37
38
  require 'openstudio/workflow/jobs/lib/apply_measures'
39
+ require 'openstudio/workflow/time_logger'
38
40
 
39
41
  begin
40
42
  require 'openstudio'
@@ -48,10 +50,10 @@ end
48
50
  class String
49
51
  def snake_case
50
52
  gsub(/::/, '/')
51
- .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
52
- .gsub(/([a-z\d])([A-Z])/, '\1_\2')
53
- .tr(' -', '__')
54
- .downcase
53
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
54
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
55
+ .tr(' -', '__')
56
+ .downcase
55
57
  end
56
58
  end
57
59
 
@@ -66,11 +68,11 @@ module OpenStudio
66
68
 
67
69
  # Convert various paths to absolute paths
68
70
  if options[:adapter_options] && options[:adapter_options][:mongoid_path] &&
69
- (Pathname.new options[:adapter_options][:mongoid_path]).absolute? == false
71
+ (Pathname.new options[:adapter_options][:mongoid_path]).absolute? == false
70
72
  options[:adapter_options][:mongoid_path] = File.expand_path options[:adapter_options][:mongoid_path]
71
73
  end
72
74
  if options[:analysis_root_path] &&
73
- (Pathname.new options[:analysis_root_path]).absolute? == false
75
+ (Pathname.new options[:analysis_root_path]).absolute? == false
74
76
  options[:analysis_root_path] = File.expand_path options[:analysis_root_path]
75
77
  end
76
78
  unless (Pathname.new run_directory).absolute?
@@ -96,7 +96,8 @@ module OpenStudio
96
96
 
97
97
  def apply_measure(workflow_item)
98
98
  @logger.info "Starting #{__method__} for #{workflow_item[:name]}"
99
- start_time = ::Time.now
99
+ @time_logger.start("Measure:#{workflow_item[:name]}")
100
+ #start_time = ::Time.now
100
101
  current_dir = Dir.pwd
101
102
  begin
102
103
  measure_working_directory = "#{@run_directory}/#{workflow_item[:measure_definition_class_name]}"
@@ -204,7 +205,9 @@ module OpenStudio
204
205
  end
205
206
  ensure
206
207
  Dir.chdir current_dir
207
- @logger.info "Finished #{__method__} for #{workflow_item[:name]} in #{::Time.now - start_time} s"
208
+ @time_logger.stop("Measure:#{workflow_item[:name]}")
209
+
210
+ @logger.info "Finished #{__method__} for #{workflow_item[:name]} in #{@time_logger.delta("Measure:#{workflow_item[:name]}")} s"
208
211
  end
209
212
  end
210
213
 
@@ -21,7 +21,7 @@ class RunEnergyplus
21
21
  # Initialize
22
22
  # param directory: base directory where the simulation files are prepared
23
23
  # param logger: logger object in which to write log messages
24
- def initialize(directory, logger, adapter, options = {})
24
+ def initialize(directory, logger, time_logger, adapter, options = {})
25
25
  energyplus_path = nil
26
26
  if /cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
27
27
  energyplus_path = 'C:/EnergyPlus-8-1-0'
@@ -39,6 +39,7 @@ class RunEnergyplus
39
39
  @run_directory = "#{@directory}/run"
40
40
  @adapter = adapter
41
41
  @logger = logger
42
+ @time_logger = time_logger
42
43
  @results = {}
43
44
 
44
45
  @logger.info "#{self.class} passed the following options #{@options}"
@@ -62,9 +63,9 @@ class RunEnergyplus
62
63
 
63
64
  # verify that the OSM, IDF, and the Weather files are in the run directory as the 'in.*' format
64
65
  if !weather_file_name &&
65
- @options[:run_openstudio] &&
66
- @options[:run_openstudio][:weather_filename] &&
67
- File.exist?(@options[:run_openstudio][:weather_filename])
66
+ @options[:run_openstudio] &&
67
+ @options[:run_openstudio][:weather_filename] &&
68
+ File.exist?(@options[:run_openstudio][:weather_filename])
68
69
  weather_file_name = @options[:run_openstudio][:weather_filename]
69
70
  end
70
71
 
@@ -98,6 +99,7 @@ class RunEnergyplus
98
99
 
99
100
  # can't create symlinks because the /vagrant mount is actually a windows mount
100
101
  @logger.info "Copying EnergyPlus files to run directory: #{@run_directory}"
102
+ @time_logger.start("Copying EnergyPlus files")
101
103
  FileUtils.copy("#{@options[:energyplus_path]}/libbcvtb.so", "#{@run_directory}/libbcvtb.so")
102
104
  FileUtils.copy("#{@options[:energyplus_path]}/libepexpat.so", "#{@run_directory}/libepexpat.so")
103
105
  FileUtils.copy("#{@options[:energyplus_path]}/libepfmiimport.so", "#{@run_directory}/libepfmiimport.so")
@@ -106,8 +108,11 @@ class RunEnergyplus
106
108
  FileUtils.copy("#{@options[:energyplus_path]}/ExpandObjects", "#{@run_directory}/ExpandObjects")
107
109
  FileUtils.copy("#{@options[:energyplus_path]}/EnergyPlus", "#{@run_directory}/EnergyPlus")
108
110
  FileUtils.copy("#{@options[:energyplus_path]}/Energy+.idd", "#{@run_directory}/Energy+.idd")
111
+ @time_logger.stop("Copying EnergyPlus files")
109
112
 
113
+ @time_logger.start("Running EnergyPlus")
110
114
  @results = call_energyplus
115
+ @time_logger.stop("Running EnergyPlus")
111
116
 
112
117
  @results
113
118
  end
@@ -156,21 +161,21 @@ class RunEnergyplus
156
161
  paths_to_rm.each { |p| FileUtils.rm_rf(p) }
157
162
 
158
163
  unless r == 0
159
- fail "EnergyPlus returned a non-zero exit code. Check the stdout-energyplus log."
164
+ fail 'EnergyPlus returned a non-zero exit code. Check the stdout-energyplus log.'
160
165
  end
161
166
 
162
167
  # TODO: check the end or err file
163
- if File.exists? 'eplusout.err'
168
+ if File.exist? 'eplusout.err'
164
169
  eplus_err = File.read('eplusout.err')
165
170
  eplus_err = eplus_err.force_encoding('ISO-8859-1').encode('utf-8', replace: nil)
166
171
  if eplus_err =~ /EnergyPlus Terminated--Fatal Error Detected/
167
- fail "EnergyPlus Terminated with a Fatal Error. Check eplusout.err log."
172
+ fail 'EnergyPlus Terminated with a Fatal Error. Check eplusout.err log.'
168
173
  end
169
174
  end
170
175
  rescue => e
171
176
  log_message = "#{__FILE__} failed with #{e.message}, #{e.backtrace.join("\n")}"
172
177
  @logger.error log_message
173
- fail log_message
178
+ raise log_message
174
179
  ensure
175
180
  Dir.chdir(current_dir)
176
181
  @logger.info 'EnergyPlus Completed'
@@ -25,7 +25,7 @@ class RunOpenstudio
25
25
  # Initialize
26
26
  # param directory: base directory where the simulation files are prepared
27
27
  # param logger: logger object in which to write log messages
28
- def initialize(directory, logger, adapter, options = {})
28
+ def initialize(directory, logger, time_logger, adapter, options = {})
29
29
  defaults = { format: 'hash', use_monthly_reports: false, analysis_root_path: '.' }
30
30
  @options = defaults.merge(options)
31
31
  @directory = directory
@@ -34,6 +34,7 @@ class RunOpenstudio
34
34
  @adapter = adapter
35
35
  @results = {}
36
36
  @logger = logger
37
+ @time_logger = time_logger
37
38
  @logger.info "#{self.class} passed the following options #{@options}"
38
39
 
39
40
  # initialize instance variables that are needed in the perform section
@@ -63,7 +64,9 @@ class RunOpenstudio
63
64
 
64
65
  apply_measures(:openstudio_measure)
65
66
 
67
+ @time_logger.start("Translating to EnergyPlus")
66
68
  translate_to_energyplus
69
+ @time_logger.stop("Translating to EnergyPlus")
67
70
 
68
71
  apply_measures(:energyplus_measure)
69
72
 
@@ -86,7 +89,9 @@ class RunOpenstudio
86
89
  end
87
90
  end
88
91
 
92
+ @time_logger.start("Saving OSM and IDF")
89
93
  save_osm_and_idf
94
+ @time_logger.stop("Saving OSM and IDF")
90
95
 
91
96
  @results
92
97
  end
@@ -95,13 +100,9 @@ class RunOpenstudio
95
100
 
96
101
  def save_osm_and_idf
97
102
  # save the data
98
- a = Time.now
99
103
  osm_filename = "#{@run_directory}/in.osm"
100
104
  File.open(osm_filename, 'w') { |f| f << @model.to_s }
101
- b = Time.now
102
- @logger.info "OpenStudio write took #{b.to_f - a.to_f}"
103
105
 
104
- # Run EnergyPlus using run energyplus script
105
106
  idf_filename = "#{@run_directory}/in.idf"
106
107
  File.open(idf_filename, 'w') { |f| f << @model_idf.to_s }
107
108
 
@@ -27,13 +27,14 @@ class RunPostprocess
27
27
  # Mixin the MeasureApplication module to apply measures
28
28
  include OpenStudio::Workflow::ApplyMeasures
29
29
 
30
- def initialize(directory, logger, adapter, options = {})
30
+ def initialize(directory, logger, time_logger, adapter, options = {})
31
31
  defaults = {}
32
32
  @options = defaults.merge(options)
33
33
  @directory = directory
34
34
  @run_directory = "#{@directory}/run"
35
35
  @adapter = adapter
36
36
  @logger = logger
37
+ @time_logger = time_logger
37
38
  @results = {}
38
39
  @output_attributes = {}
39
40
 
@@ -19,13 +19,14 @@
19
19
 
20
20
  # Run Prelight job to prepare the directory for simulations.
21
21
  class RunPreflight
22
- def initialize(directory, logger, adapter, options = {})
22
+ def initialize(directory, logger, time_logger, adapter, options = {})
23
23
  defaults = {}
24
24
  @options = defaults.merge(options)
25
25
  @directory = directory
26
26
  @adapter = adapter
27
- @results = {}
28
27
  @logger = logger
28
+ @time_logger = time_logger
29
+ @results = {}
29
30
  end
30
31
 
31
32
  def perform
@@ -33,9 +34,9 @@ class RunPreflight
33
34
 
34
35
  @adapter.communicate_started @directory, @options
35
36
 
36
- # Add the moment this does nothing.
37
+ # At the moment this does nothing.
37
38
 
38
- # return the results back to the caller--always
39
+ # return the results back to the caller -- always
39
40
  @results
40
41
  end
41
42
  end
@@ -18,8 +18,6 @@
18
18
  ######################################################################
19
19
 
20
20
  # Run precanned post processing to extract object functions
21
- # TODO: I hear that measures can step on each other if not run in their own directory
22
-
23
21
  require 'csv'
24
22
  require 'ostruct'
25
23
 
@@ -27,13 +25,14 @@ class RunReportingMeasures
27
25
  # Mixin the MeasureApplication module to apply measures
28
26
  include OpenStudio::Workflow::ApplyMeasures
29
27
 
30
- def initialize(directory, logger, adapter, options = {})
28
+ def initialize(directory, logger, time_logger, adapter, options = {})
31
29
  defaults = {}
32
30
  @options = defaults.merge(options)
33
31
  @directory = directory
34
32
  @run_directory = "#{@directory}/run"
35
33
  @adapter = adapter
36
34
  @logger = logger
35
+ @time_logger = time_logger
37
36
  @results = {}
38
37
  @output_attributes = {}
39
38
 
@@ -61,11 +60,13 @@ class RunReportingMeasures
61
60
  @datapoint_json = @adapter.get_datapoint(@directory, @options)
62
61
  @analysis_json = @adapter.get_problem(@directory, @options)
63
62
 
63
+ @time_logger.start("Running standard post process")
64
64
  if @options[:use_monthly_reports]
65
65
  run_monthly_postprocess
66
66
  else
67
67
  run_standard_postprocess
68
68
  end
69
+ @time_logger.stop("Running standard post process")
69
70
 
70
71
  translate_csv_to_json
71
72
 
@@ -77,7 +78,8 @@ class RunReportingMeasures
77
78
 
78
79
  @logger.info 'Saving reporting measures output attributes JSON'
79
80
  File.open("#{@run_directory}/reporting_measure_attributes.json", 'w') do
80
- |f| f << JSON.pretty_generate(@output_attributes)
81
+ |f|
82
+ f << JSON.pretty_generate(@output_attributes)
81
83
  end
82
84
 
83
85
  run_extract_inputs_and_outputs
@@ -99,30 +101,40 @@ class RunReportingMeasures
99
101
  # For xml, the measure attributes are in the measure_attributes_xml.json file
100
102
  # TODO: somehow pass the metadata around on which JSONs to suck into the database
101
103
  if File.exist? "#{@run_directory}/measure_attributes_xml.json"
102
- temp_json = JSON.parse(File.read("#{@run_directory}/measure_attributes_xml.json"), symbolize_names: true)
103
- @results.merge! temp_json
104
+ h = JSON.parse(File.read("#{@run_directory}/measure_attributes_xml.json"), symbolize_names: true)
105
+ h = rename_hash_keys(h)
106
+ @results.merge! h
104
107
  end
105
108
 
106
109
  # Inputs are in the measure_attributes.json file
107
110
  if File.exist? "#{@run_directory}/measure_attributes.json"
108
- temp_json = JSON.parse(File.read("#{@run_directory}/measure_attributes.json"), symbolize_names: true)
109
- @results.merge! temp_json
111
+ h = JSON.parse(File.read("#{@run_directory}/measure_attributes.json"), symbolize_names: true)
112
+ h = rename_hash_keys(h)
113
+ @results.merge! h
110
114
  end
111
115
 
112
116
  # Inputs are in the reporting_measure_attributes.jsonfile
113
117
  if File.exist? "#{@run_directory}/reporting_measure_attributes.json"
114
- temp_json = JSON.parse(File.read("#{@run_directory}/reporting_measure_attributes.json"), symbolize_names: true)
115
- @results.merge! temp_json
118
+ h = JSON.parse(File.read("#{@run_directory}/reporting_measure_attributes.json"), symbolize_names: true)
119
+ h = rename_hash_keys(h)
120
+ @results.merge! h
116
121
  end
117
122
 
118
- # Initialize the objective function variable
123
+ # Initialize the objective function variable.
119
124
  @objective_functions = {}
120
125
  if File.exist? "#{@run_directory}/standard_report_legacy.json"
121
- @results[:standard_report_legacy] = JSON.parse(File.read("#{@run_directory}/standard_report_legacy.json"), symbolize_names: true)
126
+ h = JSON.parse(File.read("#{@run_directory}/standard_report_legacy.json"), symbolize_names: true)
127
+ h = rename_hash_keys(h)
128
+ @results[:standard_report_legacy] = h
129
+ end
130
+
131
+ @logger.info 'Saving the result hash to file'
132
+ File.open("#{@run_directory}/results.json", 'w') { |f| f << JSON.pretty_generate(@results) }
122
133
 
123
- @logger.info 'Iterating over Analysis JSON Output Variables'
124
- # Save the objective functions to the object for sending back to the simulation executive
134
+ @logger.info 'Iterating over Analysis JSON Output Variables'
135
+ # Save the objective functions to the object for sending back to the simulation executive
125
136
 
137
+ if @analysis_json[:analysis] && @analysis_json[:analysis][:output_variables]
126
138
  @analysis_json[:analysis][:output_variables].each do |variable|
127
139
  # determine which ones are the objective functions (code smell: todo: use enumerator)
128
140
  if variable[:objective_function]
@@ -240,7 +252,31 @@ class RunReportingMeasures
240
252
  end
241
253
  end
242
254
 
243
- # TODO: THis is uglier than the one below! sorry.
255
+ # Remove any invalid characters in the measure attribute keys.
256
+ # Periods and Pipes are the most problematic because mongo does not allow hash keys with periods, and the pipes
257
+ # are used in the map/reduce method that was written to speed up the data write in openstudio-server.
258
+ # Also remove any trailing underscores and spaces
259
+ def rename_hash_keys(hash)
260
+ # TODO: log the name changes?
261
+ regex = /[|!@#\$%^&\*\(\)\{\}\\\[\]|;:'",<.>\/?\+=]+/
262
+
263
+ rename_keys = lambda do |h|
264
+ if Hash === h
265
+ h.each_key do |key|
266
+ if key.to_s =~ regex
267
+ @logger.warn "Renaming result key '#{key}' to remove invalid characters"
268
+ end
269
+ end
270
+ Hash[h.map { |k, v| [k.to_s.gsub(regex, '_').squeeze('_').gsub(/[_\s]+$/, '').chomp.to_sym, rename_keys[v]] }]
271
+ else
272
+ h
273
+ end
274
+ end
275
+
276
+ rename_keys[hash]
277
+ end
278
+
279
+ # TODO: This needs to be cleaned up and tested. This is just ugly. Sorry.
244
280
  def run_monthly_postprocess
245
281
  def sql_query(sql, report_name, query)
246
282
  val = nil
@@ -27,7 +27,7 @@ class RunRunmanager
27
27
  # Initialize
28
28
  # param directory: base directory where the simulation files are prepared
29
29
  # param logger: logger object in which to write log messages
30
- def initialize(directory, logger, adapter, options = {})
30
+ def initialize(directory, logger, time_logger, adapter, options = {})
31
31
  energyplus_path = nil
32
32
  if /cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
33
33
  energyplus_path = 'C:/EnergyPlus-8-1-0'
@@ -49,6 +49,7 @@ class RunRunmanager
49
49
  @results = {}
50
50
  @logger = logger
51
51
  @logger.info "#{self.class} passed the following options #{@options}"
52
+ @time_logger = time_logger
52
53
 
53
54
  # initialize instance variables that are needed in the perform section
54
55
  @model = nil
@@ -22,7 +22,7 @@ require 'libxml'
22
22
  # This actually belongs as another class that gets added as a state dynamically
23
23
  class RunXml
24
24
  # RunXml
25
- def initialize(directory, logger, adapter, options = {})
25
+ def initialize(directory, logger, time_logger, adapter, options = {})
26
26
  defaults = { use_monthly_reports: false, analysis_root_path: '.', xml_library_file: 'xml_runner.rb' }
27
27
  @options = defaults.merge(options)
28
28
  @directory = directory
@@ -31,6 +31,7 @@ class RunXml
31
31
  @adapter = adapter
32
32
  @results = {}
33
33
  @logger = logger
34
+ @time_logger = time_logger
34
35
  @logger.info "#{self.class} passed the following options #{@options}"
35
36
 
36
37
  # initialize instance variables that are needed in the perform section
@@ -34,12 +34,12 @@ module OpenStudio
34
34
  # load the transitions
35
35
  def self.default_transition
36
36
  [
37
- {from: :queued, to: :preflight},
38
- {from: :preflight, to: :openstudio},
39
- {from: :openstudio, to: :energyplus},
40
- {from: :energyplus, to: :reporting_measures},
41
- {from: :reporting_measures, to: :postprocess},
42
- {from: :postprocess, to: :finished}
37
+ { from: :queued, to: :preflight },
38
+ { from: :preflight, to: :openstudio },
39
+ { from: :openstudio, to: :energyplus },
40
+ { from: :energyplus, to: :reporting_measures },
41
+ { from: :reporting_measures, to: :postprocess },
42
+ { from: :postprocess, to: :finished }
43
43
  ]
44
44
  end
45
45
 
@@ -48,24 +48,24 @@ module OpenStudio
48
48
  def self.default_states
49
49
  warn "[Deprecation Warning] explicitly specifying states will no longer be required in 0.3.0. Method #{__method__}"
50
50
  [
51
- {state: :queued, options: {initial: true}},
52
- {state: :preflight, options: {after_enter: :run_preflight}},
53
- {state: :openstudio, options: {after_enter: :run_openstudio}}, # TODO: this should be run_openstudio_measures and run_energyplus_measures
54
- {state: :energyplus, options: {after_enter: :run_energyplus}},
55
- {state: :reporting_measures, options: {after_enter: :run_reporting_measures}},
56
- {state: :postprocess, options: {after_enter: :run_postprocess}},
57
- {state: :finished},
58
- {state: :errored}
51
+ { state: :queued, options: { initial: true } },
52
+ { state: :preflight, options: { after_enter: :run_preflight } },
53
+ { state: :openstudio, options: { after_enter: :run_openstudio } }, # TODO: this should be run_openstudio_measures and run_energyplus_measures
54
+ { state: :energyplus, options: { after_enter: :run_energyplus } },
55
+ { state: :reporting_measures, options: { after_enter: :run_reporting_measures } },
56
+ { state: :postprocess, options: { after_enter: :run_postprocess } },
57
+ { state: :finished },
58
+ { state: :errored }
59
59
  ]
60
60
  end
61
61
 
62
62
  # transitions for pat job
63
63
  def self.pat_transition
64
64
  [
65
- {from: :queued, to: :preflight},
66
- {from: :preflight, to: :runmanager},
67
- {from: :runmanager, to: :postprocess},
68
- {from: :postprocess, to: :finished}
65
+ { from: :queued, to: :preflight },
66
+ { from: :preflight, to: :runmanager },
67
+ { from: :runmanager, to: :postprocess },
68
+ { from: :postprocess, to: :finished }
69
69
  ]
70
70
  end
71
71
 
@@ -73,12 +73,12 @@ module OpenStudio
73
73
  def self.pat_states
74
74
  warn "[Deprecation Warning] explicitly specifying states will no longer be required in 0.3.0. Method #{__method__}"
75
75
  [
76
- {state: :queued, options: {initial: true}},
77
- {state: :preflight, options: {after_enter: :run_preflight}},
78
- {state: :runmanager, options: {after_enter: :run_runmanager}},
79
- {state: :postprocess, options: {after_enter: :run_postprocess}},
80
- {state: :finished},
81
- {state: :errored}
76
+ { state: :queued, options: { initial: true } },
77
+ { state: :preflight, options: { after_enter: :run_preflight } },
78
+ { state: :runmanager, options: { after_enter: :run_runmanager } },
79
+ { state: :postprocess, options: { after_enter: :run_postprocess } },
80
+ { state: :finished },
81
+ { state: :errored }
82
82
  ]
83
83
  end
84
84
 
@@ -94,21 +94,22 @@ module OpenStudio
94
94
  @current_state = nil
95
95
  @transitions = {}
96
96
  @directory = directory
97
+ @time_logger = TimeLogger.new
97
98
  # TODO: run directory is a convention right now. Move to a configuration item
98
99
  @run_directory = "#{@directory}/run"
99
100
 
100
101
  defaults = nil
101
102
  if options[:is_pat]
102
103
  defaults = {
103
- transitions: OpenStudio::Workflow::Run.pat_transition,
104
- states: OpenStudio::Workflow::Run.pat_states,
105
- jobs: {}
104
+ transitions: OpenStudio::Workflow::Run.pat_transition,
105
+ states: OpenStudio::Workflow::Run.pat_states,
106
+ jobs: {}
106
107
  }
107
108
  else
108
109
  defaults = {
109
- transitions: OpenStudio::Workflow::Run.default_transition,
110
- states: OpenStudio::Workflow::Run.default_states,
111
- jobs: {}
110
+ transitions: OpenStudio::Workflow::Run.default_transition,
111
+ states: OpenStudio::Workflow::Run.default_states,
112
+ jobs: {}
112
113
  }
113
114
  end
114
115
  @options = defaults.merge(options)
@@ -156,6 +157,7 @@ module OpenStudio
156
157
  @adapter.communicate_results @directory, @job_results[:run_runmanager]
157
158
  elsif @job_results[:run_reporting_measures]
158
159
  @logger.info 'Sending the reporting measuers results back to the adapter'
160
+ @time_logger.save(File.join(@directory,'profile.json'))
159
161
  @adapter.communicate_results @directory, @job_results[:run_reporting_measures]
160
162
  end
161
163
  ensure
@@ -166,8 +168,12 @@ module OpenStudio
166
168
  end
167
169
 
168
170
  @logger.info 'Workflow complete'
171
+ # Write out the TimeLogger once again in case the run_reporting_measures didn't exist
172
+ @time_logger.save(File.join(@directory,'profile.json'))
169
173
 
170
- # TODO: define the outputs and figure out how to show it correctory
174
+
175
+
176
+ # TODO: define the outputs and figure out how to show it correctly
171
177
  obj_function_array ||= ['NA']
172
178
 
173
179
  # Print the objective functions to the screen even though the file is being used right now
@@ -180,13 +186,11 @@ module OpenStudio
180
186
 
181
187
  # Step through the states, if there is an error (e.g. exception) then go to error
182
188
  def step(*args)
183
- begin
184
- next_state
189
+ next_state
185
190
 
186
- send("run_#{@current_state}")
187
- rescue => e
188
- step_error("#{e.message}:#{e.backtrace.join("\n")}")
189
- end
191
+ send("run_#{@current_state}")
192
+ rescue => e
193
+ step_error("#{e.message}:#{e.backtrace.join("\n")}")
190
194
  end
191
195
 
192
196
  # call back for when there is an exception running any of the state transitions
@@ -281,7 +285,7 @@ module OpenStudio
281
285
 
282
286
  def next_state
283
287
  @logger.info "Current state: '#{@current_state}'"
284
- ns = @transitions.select{ |h| h[:from] == @current_state}.first[:to]
288
+ ns = @transitions.select { |h| h[:from] == @current_state }.first[:to]
285
289
  @logger.info "Next state will be: '#{ns}'"
286
290
 
287
291
  # Set the next state before calling the method
@@ -309,7 +313,7 @@ module OpenStudio
309
313
  require_relative "jobs/#{from_method}/#{from_method}"
310
314
  klass_name = from_method.to_s.split('_').map(&:capitalize) * ''
311
315
  @logger.info "Getting method for state transition '#{from_method}'"
312
- klass = Object.const_get(klass_name).new(@directory, @logger, @adapter, get_job_options)
316
+ klass = Object.const_get(klass_name).new(@directory, @logger, @time_logger, @adapter, get_job_options)
313
317
  klass
314
318
  end
315
319
  end
@@ -0,0 +1,53 @@
1
+ # Class to store run times in a useful structure. Data are stored in a hash based on a the channel name
2
+ # There is no concept of multi-levels. The onus is on the user to make sure that they don't add a value to the
3
+ # logger that may be a level.
4
+ class TimeLogger
5
+ attr_reader :channels
6
+
7
+ def initialize
8
+ @logger = []
9
+ @channels = {}
10
+ end
11
+
12
+ # name of the moniker that you are tracking. If the name is already in use, then it restarts the timer.
13
+ def start(channel)
14
+ # warning -- "will reset timer for #{moniker}" if @monikers.key? moniker
15
+ s = ::Time.now
16
+ @channels[channel] = {start_time_str: "#{s}", start_time: s.to_f}
17
+ end
18
+
19
+ def stop(channel)
20
+ end_time = ::Time.now.to_f
21
+ @logger << {
22
+ channel: channel,
23
+ start_time: @channels[channel][:start_time],
24
+ start_time_str: @channels[channel][:start_time_str],
25
+ end_time: end_time,
26
+ delta: end_time - @channels[channel][:start_time]
27
+ }
28
+
29
+ # remove the channel
30
+ @channels.delete(channel) if @channels.key? channel
31
+ end
32
+
33
+ def stop_all
34
+ @channels.each_key do |channel|
35
+ stop(channel)
36
+ end
37
+ end
38
+
39
+ # return the entire report
40
+ def report
41
+ @logger
42
+ end
43
+
44
+ # this will report all the values for all the channels with this name.
45
+ def delta(channel)
46
+ @logger.map { |k| {channel.to_s => k[:delta]} if k[:channel] == channel }
47
+ end
48
+
49
+ # save the data to a file. This will overwrite the file if it already exists
50
+ def save(filename)
51
+ File.open(filename, 'w') { |f| f << JSON.pretty_generate(@logger) }
52
+ end
53
+ end
@@ -19,6 +19,6 @@
19
19
 
20
20
  module OpenStudio
21
21
  module Workflow
22
- VERSION = '0.0.3'
22
+ VERSION = '0.0.4'
23
23
  end
24
24
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openstudio-workflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nicholas Long
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-21 00:00:00.000000000 Z
11
+ date: 2014-12-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -81,19 +81,33 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: 2.0.2
83
83
  - !ruby/object:Gem::Dependency
84
- name: zip
84
+ name: rubyXL
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - ~>
88
88
  - !ruby/object:Gem::Version
89
- version: 2.0.2
89
+ version: 3.3.0
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - ~>
95
95
  - !ruby/object:Gem::Version
96
- version: 2.0.2
96
+ version: 3.3.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubyzip
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: 1.1.6
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: 1.1.6
97
111
  description: Run OpenStudio based simulations using EnergyPlus
98
112
  email:
99
113
  - nicholas.long@nrel.gov
@@ -119,6 +133,7 @@ files:
119
133
  - lib/openstudio/workflow/jobs/run_xml/run_xml.rb
120
134
  - lib/openstudio/workflow/multi_delegator.rb
121
135
  - lib/openstudio/workflow/run.rb
136
+ - lib/openstudio/workflow/time_logger.rb
122
137
  - lib/openstudio/workflow/version.rb
123
138
  - lib/openstudio-workflow.rb
124
139
  - README.md