openstudio-workflow 1.0.0.pat1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +16 -68
  4. data/Rakefile +9 -9
  5. data/bin/openstudio_cli +786 -0
  6. data/lib/openstudio/workflow/adapters/input/local.rb +97 -0
  7. data/lib/openstudio/workflow/adapters/output/local.rb +90 -0
  8. data/lib/openstudio/workflow/adapters/output/socket.rb +70 -0
  9. data/lib/openstudio/workflow/{jobs/run_preflight/run_preflight.rb → adapters/output/web.rb} +37 -19
  10. data/lib/openstudio/workflow/{adapter.rb → adapters/output_adapter.rb} +53 -51
  11. data/lib/openstudio/workflow/job.rb +22 -0
  12. data/lib/openstudio/workflow/jobs/{run_energyplus → resources}/monthly_report.idf +0 -0
  13. data/lib/openstudio/workflow/jobs/run_energyplus.rb +49 -0
  14. data/lib/openstudio/workflow/jobs/run_ep_measures.rb +55 -0
  15. data/lib/openstudio/workflow/jobs/run_initialization.rb +136 -0
  16. data/lib/openstudio/workflow/jobs/run_os_measures.rb +59 -0
  17. data/lib/openstudio/workflow/jobs/run_postprocess.rb +53 -0
  18. data/lib/openstudio/workflow/jobs/run_preprocess.rb +81 -0
  19. data/lib/openstudio/workflow/jobs/run_reporting_measures.rb +86 -0
  20. data/lib/openstudio/workflow/jobs/run_translation.rb +49 -0
  21. data/lib/openstudio/workflow/multi_delegator.rb +1 -3
  22. data/lib/openstudio/workflow/registry.rb +137 -0
  23. data/lib/openstudio/workflow/run.rb +182 -221
  24. data/lib/openstudio/workflow/time_logger.rb +1 -1
  25. data/lib/openstudio/workflow/util/energyplus.rb +564 -0
  26. data/lib/openstudio/workflow/util/io.rb +33 -0
  27. data/lib/openstudio/workflow/util/measure.rb +520 -0
  28. data/lib/openstudio/workflow/util/model.rb +100 -0
  29. data/lib/openstudio/workflow/util/post_process.rb +177 -0
  30. data/lib/openstudio/workflow/util/weather_file.rb +108 -0
  31. data/lib/openstudio/workflow/util.rb +14 -0
  32. data/lib/openstudio/workflow/version.rb +1 -1
  33. data/lib/openstudio/workflow_json.rb +399 -0
  34. data/lib/openstudio/workflow_runner.rb +213 -0
  35. data/lib/openstudio-workflow.rb +13 -118
  36. metadata +45 -85
  37. data/lib/openstudio/extended_runner.rb +0 -105
  38. data/lib/openstudio/workflow/adapters/local.rb +0 -101
  39. data/lib/openstudio/workflow/adapters/mongo.rb +0 -227
  40. data/lib/openstudio/workflow/jobs/lib/apply_measures.rb +0 -253
  41. data/lib/openstudio/workflow/jobs/run_energyplus/run_energyplus.rb +0 -314
  42. data/lib/openstudio/workflow/jobs/run_openstudio/run_openstudio.rb +0 -230
  43. data/lib/openstudio/workflow/jobs/run_postprocess/run_postprocess.rb +0 -110
  44. data/lib/openstudio/workflow/jobs/run_reporting_measures/run_reporting_measures.rb +0 -471
  45. data/lib/openstudio/workflow/jobs/run_runmanager/run_runmanager.rb +0 -247
  46. data/lib/openstudio/workflow/jobs/run_xml/run_xml.rb +0 -279
@@ -17,181 +17,236 @@
17
17
  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
18
  ######################################################################
19
19
 
20
- # Run Class for OpenStudio workflow. The data are passed in via the adapter
20
+ require_relative 'registry'
21
+ require_relative 'adapters/input/local'
22
+ require_relative 'adapters/output/local'
23
+
24
+ require 'logger'
25
+
26
+ # Run Class for OpenStudio workflow. All comments here need some love, as well as the code itself
21
27
  module OpenStudio
22
28
  module Workflow
23
29
  class Run
24
- attr_accessor :logger
25
- attr_accessor :workflow_arguments
30
+ attr_accessor :registry
26
31
 
27
32
  attr_reader :options
28
- attr_reader :adapter
29
- attr_reader :directory
30
- attr_reader :run_directory
31
- attr_reader :final_state
33
+ attr_reader :input_adapter
34
+ attr_reader :output_adapter
32
35
  attr_reader :final_message
33
36
  attr_reader :job_results
37
+ attr_reader :current_state
34
38
 
35
- # load the transitions
36
- def self.default_transition
37
- [
38
- { from: :queued, to: :preflight },
39
- { from: :preflight, to: :openstudio },
40
- { from: :openstudio, to: :energyplus },
41
- { from: :energyplus, to: :reporting_measures },
42
- { from: :reporting_measures, to: :postprocess },
43
- { from: :postprocess, to: :finished }
44
- ]
45
- end
46
-
47
- # The default states for the workflow. Note that the states of :queued of :finished need
48
- # to exist for all cases.
49
- def self.default_states
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 }
59
- ]
60
- end
61
-
62
- # transitions for pat job
63
- def self.pat_transition
64
- [
65
- { from: :queued, to: :preflight },
66
- { from: :preflight, to: :runmanager },
67
- { from: :runmanager, to: :postprocess },
68
- { from: :postprocess, to: :finished }
69
- ]
70
- end
71
-
72
- # states for pat job
73
- def self.pat_states
39
+ # Define the default set of jobs. Note that the states of :queued of :finished need to exist for all job arrays.
40
+ #
41
+ def self.default_jobs
74
42
  [
75
- { state: :queued, options: { initial: true } },
76
- { state: :preflight, options: { after_enter: :run_preflight } },
77
- { state: :runmanager, options: { after_enter: :run_runmanager } },
78
- { state: :postprocess, options: { after_enter: :run_postprocess } },
43
+ { state: :queued, next_state: :initialization, options: { initial: true } },
44
+ { state: :initialization, next_state: :os_measures, job: :RunInitialization,
45
+ file: 'openstudio/workflow/jobs/run_initialization', options: {} },
46
+ { state: :os_measures, next_state: :translator, job: :RunOpenStudioMeasures,
47
+ file: 'openstudio/workflow/jobs/run_os_measures.rb', options: {} },
48
+ { state: :translator, next_state: :ep_measures, job: :RunTranslation,
49
+ file: 'openstudio/workflow/jobs/run_translation.rb', options: {} },
50
+ { state: :ep_measures, next_state: :preprocess, job: :RunEnergyPlusMeasures,
51
+ file: 'openstudio/workflow/jobs/run_ep_measures.rb', options: {} },
52
+ { state: :preprocess, next_state: :simulation, job: :RunPreprocess,
53
+ file: 'openstudio/workflow/jobs/run_preprocess.rb', options: {} },
54
+ { state: :simulation, next_state: :reporting_measures, job: :RunEnergyPlus,
55
+ file: 'openstudio/workflow/jobs/run_energyplus.rb', options: {} },
56
+ { state: :reporting_measures, next_state: :postprocess, job: :RunReportingMeasures,
57
+ file: 'openstudio/workflow/jobs/run_reporting_measures.rb', options: {} },
58
+ { state: :postprocess, next_state: :finished, job: :RunPostprocess,
59
+ file: 'openstudio/workflow/jobs/run_postprocess.rb', options: {} },
79
60
  { state: :finished },
80
61
  { state: :errored }
81
62
  ]
82
63
  end
83
64
 
84
- # initialize a new run class
65
+ # Initialize a new run class
85
66
  #
86
- # @param adapter an instance of the adapter class
87
- # @param directory location of the datapoint directory to run. This is needed
88
- # independent of the adapter that is being used. Note that the simulation will actually run in 'run'
89
- # @param options that are sent to the adapters
90
- def initialize(adapter, directory, options = {})
91
- @adapter = adapter
67
+ # @param [String] osw_path the path to the OSW file to run. It is highly recommended that this be an absolute
68
+ # path, however if not it will be made absolute relative to the current working directory
69
+ # @param [Hash] options ({}) A set of user-specified options that are used to override default behaviors. Some
70
+ # sort of definitive documentation is needed for this hash
71
+ # @option options [Hash] :transitions Non-default transitions set (see Run#default_transition)
72
+ # @option options [Hash] :states Non-default states set (see Run#default_states)
73
+ # @option options [Hash] :jobs ???
74
+ # @todo (rhorsey) establish definitive documentation on all option parameters
75
+ #
76
+ def initialize(osw_path, options = {})
77
+ # DLM - what is final_message?
92
78
  @final_message = ''
93
79
  @current_state = nil
94
- @transitions = {}
95
- @directory = directory
96
- @time_logger = TimeLogger.new
97
- @workflow_arguments = {}
98
- @past_results = {}
99
- # TODO: run directory is a convention right now. Move to a configuration item
100
- @run_directory = "#{@directory}/run"
101
-
102
- defaults = nil
103
- if options[:is_pat]
104
- defaults = {
105
- transitions: OpenStudio::Workflow::Run.pat_transition,
106
- states: OpenStudio::Workflow::Run.pat_states,
107
- jobs: {}
108
- }
80
+
81
+ # Registry is a large hash of objects that are populated during the run, the number of objects in the registry should be reduced over time
82
+ # - openstudio_2 - true if we are running in OpenStudio 2.X environment
83
+ # - logger - general logger
84
+ # - log_targets - IO devices that are being logged to
85
+ # - time_logger - logger for doing profiling - time to run each step will be captured in OSResult, deprecate
86
+ # - workflow - the current OSW parsed as a Ruby Hash
87
+ # - workflow_json - the current WorkflowJSON object
88
+ # - osw_dir - the directory the OSW was loaded from as a string
89
+ # - root_dir - the root directory in the OSW as a string
90
+ # - run_dir - the run directory for the simulation as a string
91
+ # - datapoint - the current OSD parsed as a Ruby Hash
92
+ # - analysis - the current OSA parsed as a Ruby Hash
93
+ # - runner - the current OSRunner object
94
+ # - results - the output of the run_extract_inputs_and_outputs method
95
+ # - model - the current OpenStudio Model object, updated after each step
96
+ # - model_idf - the current EnergyPlus Workspace object, updated after each step
97
+ # - wf - the path to the current weather file as a string, updated after each step
98
+ # - output_attributes - ? - deprecate
99
+ # - sql - the path to the current EnergyPlus SQL file as a string
100
+ @registry = Registry.new
101
+
102
+ openstudio_2 = false
103
+ begin
104
+ # OpenStudio 2.X test
105
+ OpenStudio::WorkflowJSON.new
106
+ openstudio_2 = true
107
+ rescue NameError => e
108
+ end
109
+ @registry.register(:openstudio_2) { openstudio_2 }
110
+
111
+ # get the input osw
112
+ @input_adapter = OpenStudio::Workflow::InputAdapter::Local.new(osw_path)
113
+
114
+ # create the output adapter
115
+ @output_adapter = nil
116
+ if options[:output_adapter]
117
+ @output_adapter = options[:output_adapter]
109
118
  else
110
- defaults = {
111
- transitions: OpenStudio::Workflow::Run.default_transition,
112
- states: OpenStudio::Workflow::Run.default_states,
113
- jobs: {}
114
- }
119
+ @output_adapter = OpenStudio::Workflow::OutputAdapter::Local.new(output_directory: @input_adapter.run_dir)
115
120
  end
116
- @options = defaults.merge(options)
117
121
 
118
- @job_results = {}
122
+ @registry.register(:osw_path) { @input_adapter.osw_path }
123
+ @registry.register(:osw_dir) { @input_adapter.osw_dir }
124
+ @registry.register(:run_dir) { @input_adapter.run_dir }
125
+
126
+ # DLM: need to check that we have correct permissions to all these paths
119
127
 
120
128
  # By default blow away the entire run directory every time and recreate it
121
- FileUtils.rm_rf(@run_directory) if File.exist?(@run_directory)
122
- FileUtils.mkdir_p(@run_directory)
129
+ unless options[:preserve_run_dir]
130
+ if File.exist?(@registry[:run_dir])
131
+ # logger is not initialized yet (it needs run dir to exist for log)
132
+ puts "Removing existing run directory #{@registry[:run_dir]}" if options[:debug]
133
+
134
+ # DLM: this is dangerous, we are calling rm_rf on a user entered directory, need to check this first
135
+ # TODO: Echoing Dan's comment
136
+ FileUtils.rm_rf(@registry[:run_dir])
137
+ end
138
+ end
139
+ FileUtils.mkdir_p(@registry[:run_dir])
140
+
141
+ defaults = {
142
+ jobs: OpenStudio::Workflow::Run.default_jobs,
143
+ preserve_run_dir: false,
144
+ debug: false,
145
+ profile: true
146
+ }
147
+ if options[:targets].nil?
148
+ # DLM: need to make sure that run.log will be closed later
149
+ defaults[:targets] = [STDOUT, File.open(File.join(@registry[:run_dir], 'run.log'), 'a')]
150
+ end
151
+ @options = defaults.merge(options)
123
152
 
124
- # There is a namespace conflict when OpenStudio is loaded: be careful!
125
- log_file = File.open("#{@run_directory}/run.log", 'a')
153
+ @registry.register(:log_targets) { @options[:targets] }
154
+ @registry.register(:time_logger) { TimeLogger.new } if @options[:profile]
126
155
 
127
- l = @adapter.get_logger @directory, @options
128
- if l
129
- @logger = ::Logger.new MultiDelegator.delegate(:write, :close).to(STDOUT, log_file, l)
130
- else
131
- @logger = ::Logger.new MultiDelegator.delegate(:write, :close).to(STDOUT, log_file)
156
+ # Initialize the MultiDelegator logger
157
+ logger_level = @options[:debug] ? ::Logger::DEBUG : ::Logger::WARN
158
+ @logger = ::Logger.new(MultiDelegator.delegate(:write, :close).to(*@options[:targets])) # * is the splat operator
159
+ @logger.level = logger_level
160
+ @registry.register(:logger) { @logger }
161
+
162
+ @logger.info "openstudio_2 = #{@registry[:openstudio_2]}"
163
+
164
+ openstudio_dir = "unknown"
165
+ begin
166
+ openstudio_dir = $OpenStudio_Dir
167
+ if openstudio_dir.nil?
168
+ openstudio_dir = OpenStudio::getOpenStudioModuleDirectory.to_s
169
+ end
170
+ rescue
132
171
  end
172
+ @logger.info "openstudio_dir = #{openstudio_dir}"
133
173
 
134
- @logger.info "Initializing directory #{@directory} for simulation with options #{@options}"
174
+ @logger.info "Initializing directory #{@registry[:run_dir]} for simulation with options #{@options}"
135
175
 
136
- # load the state machine
137
- machine
176
+ # Define the state and transitions
177
+ @current_state = :queued
178
+ @jobs = @options[:jobs]
138
179
  end
139
180
 
140
- # run the simulations.
141
- # TODO: add a catch if any job fails; TODO: make a block method to provide feedback
181
+ # execute the workflow defined in the state object
182
+ #
183
+ # @todo add a catch if any job fails
184
+ # @todo make a block method to provide feedback
142
185
  def run
143
- @logger.info "Starting workflow in #{@directory}"
186
+ @logger.info "Starting workflow in #{@registry[:run_dir]}"
144
187
  begin
188
+ next_state
145
189
  while @current_state != :finished && @current_state != :errored
146
190
  sleep 2
147
191
  step
148
192
  end
149
193
 
150
194
  @logger.info 'Finished workflow - communicating results and zipping files'
151
-
152
- # TODO: this should be a job that handles the use case with a :guard on if @job_results[:run_postprocess]
153
- # or @job_results[:run_reporting_measures]
154
- # these are the results that need to be sent back to adapter
155
- if @job_results[:run_runmanager]
156
- @logger.info 'Sending the run_runmananger results back to the adapter'
157
- @adapter.communicate_results @directory, @job_results[:run_runmanager]
158
- elsif @job_results[:run_reporting_measures]
159
- @logger.info 'Sending the reporting measures results back to the adapter'
160
- @time_logger.save(File.join(@directory, 'profile.json'))
161
- @adapter.communicate_results @directory, @job_results[:run_reporting_measures]
162
- end
195
+ @output_adapter.communicate_results(@registry[:run_dir], @registry[:results])
196
+ rescue => e
197
+ @logger.info "Error occurred during running with #{e.message}"
163
198
  ensure
199
+ @logger.info 'Workflow complete'
200
+
164
201
  if @current_state == :errored
165
- @adapter.communicate_failure @directory
202
+ @registry[:workflow_json].setCompletedStatus('Fail') if @registry[:workflow_json]
166
203
  else
167
- @adapter.communicate_complete @directory
204
+ @registry[:workflow_json].setCompletedStatus('Success')
168
205
  end
169
206
 
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'))
207
+ # save all files before calling output adapter
208
+ @registry[:log_targets].each(&:flush)
209
+
210
+ # save workflow with results
211
+ if @registry[:workflow_json]
212
+ out_path = @registry[:workflow_json].absoluteOutPath
213
+ @registry[:workflow_json].saveAs(out_path)
214
+ end
215
+
216
+ # Write out the TimeLogger to the filesystem
217
+ @registry[:time_logger].save(File.join(@registry[:run_dir], 'profile.json')) if @registry[:time_logger]
173
218
 
174
- # TODO: define the outputs and figure out how to show it correctly
175
- obj_function_array ||= ['NA']
219
+ if @current_state == :errored
220
+ @output_adapter.communicate_failure
221
+ else
222
+ @output_adapter.communicate_complete
223
+ end
176
224
 
177
- # Print the objective functions to the screen even though the file is being used right now
178
- # Note as well that we can't guarantee that the csv format will be in the right order
179
- puts obj_function_array.join(',')
180
225
  end
181
226
 
182
227
  @current_state
183
228
  end
184
229
 
185
230
  # Step through the states, if there is an error (e.g. exception) then go to error
186
- def step(*args)
231
+ #
232
+ def step
233
+ step_instance = @jobs.find { |h| h[:state] == @current_state }
234
+ require step_instance[:file]
235
+ klass = OpenStudio::Workflow.new_class(step_instance[:job], @input_adapter, @output_adapter, @registry, options)
236
+ @output_adapter.communicate_transition("Starting state #{@current_state}", :state)
237
+ state_return = klass.perform
238
+ if state_return
239
+ @output_adapter.communicate_transition("Returned from state #{@current_state} with message #{state_return}", :state)
240
+ else
241
+ @output_adapter.communicate_transition("Returned from state #{@current_state}", :state)
242
+ end
187
243
  next_state
188
-
189
- send("run_#{@current_state}")
190
244
  rescue => e
191
245
  step_error("#{e.message}:#{e.backtrace.join("\n")}")
192
246
  end
193
247
 
194
- # call back for when there is an exception running any of the state transitions
248
+ # Error handling for when there is an exception running any of the state transitions
249
+ #
195
250
  def step_error(*args)
196
251
  # Make sure to set the instance variable @error to true in order to stop the :step
197
252
  # event from being fired.
@@ -202,117 +257,23 @@ module OpenStudio
202
257
  @current_state = :errored
203
258
  end
204
259
 
205
- # TODO: these methods needs to be dynamic or inherited
206
- # run energplus
207
- def run_energyplus
208
- @logger.info "Running #{__method__}"
209
- klass = get_run_class(__method__)
210
-
211
- @job_results[__method__.to_sym] = klass.perform
212
- end
213
-
214
- # run openstudio to create the model and apply the measures
215
- def run_openstudio
216
- @logger.info "Running #{__method__}"
217
- klass = get_run_class(__method__)
218
-
219
- # TODO: save the resulting filenames to an array
220
- @job_results[__method__.to_sym] = klass.perform
221
- end
222
-
223
- # run a pat file using runmanager
224
- def run_runmanager
225
- @logger.info "Running #{__method__}"
226
- klass = get_run_class(__method__)
227
-
228
- # TODO: save the resulting filenames to an array
229
- @job_results[__method__.to_sym] = klass.perform
230
- end
231
-
232
- # run reporting measures
233
- def run_reporting_measures
234
- @logger.info "Running #{__method__}"
235
- klass = get_run_class(__method__)
236
-
237
- # TODO: save the resulting filenames to an array
238
- @job_results[__method__.to_sym] = klass.perform
239
- end
240
-
241
- def run_postprocess
242
- @logger.info "Running #{__method__}"
243
- klass = get_run_class(__method__)
244
-
245
- @job_results[__method__.to_sym] = klass.perform
246
- end
247
-
248
- # preconfigured run method for preflight. This configures the input directories and sets everything
249
- # up for running the simulations.
250
- def run_preflight
251
- @logger.info "Running #{__method__}"
252
- klass = get_run_class(__method__)
253
-
254
- @job_results[__method__.to_sym] = klass.perform
255
- end
256
-
257
- def run_xml
258
- @logger.info "Running #{__method__}"
259
- klass = get_run_class(__method__)
260
-
261
- @job_results[__method__.to_sym] = klass.perform
262
- @logger.info @job_results
263
- end
264
-
265
- # last method that is called.
266
- def run_finished
267
- @logger.info "Running #{__method__}"
260
+ # Return the finished state and exit
261
+ #
262
+ def run_finished(_, _, _)
263
+ logger.info "Running #{__method__}"
268
264
 
269
265
  @current_state
270
266
  end
271
- alias_method :final_state, :run_finished
272
267
 
273
268
  private
274
269
 
275
- # Create a state machine from the predefined transitions methods. This will initialize in the :queued state
276
- # and then load in the transitions from the @options hash
277
- def machine
278
- @logger.info 'Initializing state machine'
279
- @current_state = :queued
280
-
281
- @transitions = @options[:transitions]
282
- end
283
-
270
+ # Advance the @current_state to the next state
271
+ #
284
272
  def next_state
285
273
  @logger.info "Current state: '#{@current_state}'"
286
- ns = @transitions.find { |h| h[:from] == @current_state }[:to]
274
+ ns = @jobs.find { |h| h[:state] == @current_state }[:next_state]
287
275
  @logger.info "Next state will be: '#{ns}'"
288
-
289
- # Set the next state before calling the method
290
276
  @current_state = ns
291
-
292
- # do not return anything, the step method uses the @current_state variable to call run_#{next_state}
293
- end
294
-
295
- # Get any options that may have been sent into the class defining the workflow step
296
- def get_job_options
297
- result = {}
298
- # if @options[:jobs].has_key?(@current_state)
299
- # logger.info "Retrieving job options from the @options array for #{state.current_state}"
300
- # result = @options[:jobs][@current_state]
301
- # end
302
-
303
- # result
304
-
305
- # TODO: fix this so that it gets the base config options plus its job options. Need to
306
- # also merge in all the former job results.
307
- @options.merge(@job_results)
308
- end
309
-
310
- def get_run_class(from_method)
311
- require_relative "jobs/#{from_method}/#{from_method}"
312
- klass_name = from_method.to_s.split('_').map(&:capitalize) * ''
313
- @logger.info "Getting method for state transition '#{from_method}'"
314
- klass = Object.const_get(klass_name).new(@directory, @logger, @time_logger, @adapter, @workflow_arguments, @past_results, get_job_options)
315
- klass
316
277
  end
317
278
  end
318
279
  end
@@ -13,7 +13,7 @@ class TimeLogger
13
13
  def start(channel)
14
14
  # warning -- "will reset timer for #{moniker}" if @monikers.key? moniker
15
15
  s = ::Time.now
16
- @channels[channel] = { start_time_str: "#{s}", start_time: s.to_f }
16
+ @channels[channel] = { start_time_str: s.to_s, start_time: s.to_f }
17
17
  end
18
18
 
19
19
  def stop(channel)