openstudio-workflow 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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