openstudio-workflow 1.0.0.pat1 → 1.0.0

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.
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)