openstudio-workflow 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,45 @@
1
+ ######################################################################
2
+ # Copyright (c) 2008-2014, Alliance for Sustainable Energy.
3
+ # All rights reserved.
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+ ######################################################################
19
+
20
+
21
+ # Run Prelight job to prepare the directory for simulations.
22
+ class RunPreflight
23
+
24
+ def initialize(directory, logger, adapter, options = {})
25
+ defaults = {}
26
+ @options = defaults.merge(options)
27
+ @directory = directory
28
+ @adapter = adapter
29
+ @results = {}
30
+ @logger = logger
31
+
32
+ end
33
+
34
+ def perform
35
+ @logger.info "Calling #{__method__} in the #{self.class} class"
36
+
37
+
38
+ @adapter.communicate_started @directory, @options
39
+
40
+ # Add the moment this does nothing.
41
+
42
+ # return the results back to the caller--always
43
+ @results
44
+ end
45
+ end
@@ -0,0 +1,279 @@
1
+ ######################################################################
2
+ # Copyright (c) 2008-2014, Alliance for Sustainable Energy.
3
+ # All rights reserved.
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+ ######################################################################
19
+
20
+ require 'libxml'
21
+
22
+ # This actually belongs as another class that gets added as a state dynamically
23
+ class RunXml
24
+
25
+ CRASH_ON_NO_WORKFLOW_VARIABLE = FALSE
26
+ # RunXml
27
+ def initialize(directory, logger, adapter, options = {})
28
+ defaults = {use_monthly_reports: false, analysis_root_path: '.', xml_library_file: 'xml_runner.rb'}
29
+ @options = defaults.merge(options)
30
+ @directory = directory
31
+ # TODO: there is a base number of arguments that each job will need including @run_directory. abstract it out.
32
+ @run_directory = "#{@directory}/run"
33
+ @adapter = adapter
34
+ @results = {}
35
+ @logger = logger
36
+ @logger.info "#{self.class} passed the following options #{@options}"
37
+
38
+ # initialize instance variables that are needed in the perform section
39
+ @weather_filename = nil
40
+ @weather_directory = File.expand_path(File.join(@options[:analysis_root_path], "weather"))
41
+ @logger.info "Weather directory is: #{@weather_directory}"
42
+ @model_xml = nil
43
+ @model = nil
44
+ @model_idf = nil
45
+ @analysis_json = nil
46
+ # TODO: rename datapoint_json to just datapoint
47
+ @datapoint_json = nil
48
+ @output_attributes = {}
49
+ @report_measures = []
50
+ @measure_type_lookup = {
51
+ :openstudio_measure => 'RubyMeasure',
52
+ :energyplus_measure => 'EnergyPlusMeasure',
53
+ :reporting_measure => 'ReportingMeasure'
54
+ }
55
+ end
56
+
57
+
58
+ def perform
59
+ @logger.info "Calling #{__method__} in the #{self.class} class"
60
+ @logger.info "Current directory is #{@directory}"
61
+
62
+ @logger.info "Retrieving datapoint and problem"
63
+ @datapoint_json = @adapter.get_datapoint(@directory, @options)
64
+ @analysis_json = @adapter.get_problem(@directory, @options)
65
+
66
+ if @analysis_json && @analysis_json[:analysis]
67
+ @model_xml = load_xml_model
68
+ @weather_filename = load_weather_file
69
+
70
+ apply_xml_measures
71
+
72
+ @logger.info "XML measure output attributes JSON is #{@output_attributes}"
73
+ File.open("#{@run_directory}/measure_attributes_xml.json", 'w') {
74
+ |f| f << JSON.pretty_generate(@output_attributes)
75
+ }
76
+ end
77
+
78
+ create_osm_from_xml
79
+
80
+ @results
81
+ end
82
+
83
+ private
84
+
85
+ def load_xml_model
86
+ model = nil
87
+ @logger.info 'Loading seed model'
88
+
89
+ if @analysis_json[:analysis][:seed]
90
+ @logger.info "Seed model is #{@analysis_json[:analysis][:seed]}"
91
+ if @analysis_json[:analysis][:seed][:path]
92
+
93
+ # assume that the seed model has been placed in the directory
94
+ baseline_model_path = File.expand_path(
95
+ File.join(@options[:analysis_root_path], @analysis_json[:analysis][:seed][:path]))
96
+
97
+ if File.exist? baseline_model_path
98
+ @logger.info "Reading in baseline model #{baseline_model_path}"
99
+ model = LibXML::XML::Document.file(baseline_model_path)
100
+ fail 'XML model is nil' if model.nil?
101
+
102
+ model.save("#{@run_directory}/original.xml")
103
+ else
104
+ fail "Seed model '#{baseline_model_path}' did not exist"
105
+ end
106
+ else
107
+ fail 'No seed model path in JSON defined'
108
+ end
109
+ else
110
+ fail 'No seed model block'
111
+ end
112
+
113
+ model
114
+ end
115
+
116
+ # Save the weather file to the instance variable
117
+ def load_weather_file
118
+ weather_filename = nil
119
+ if @analysis_json[:analysis][:weather_file]
120
+ if @analysis_json[:analysis][:weather_file][:path]
121
+ # This last(4) needs to be cleaned up. Why don't we know the path of the file?
122
+ # assume that the seed model has been placed in the directory
123
+ weather_filename = File.expand_path(
124
+ File.join(@options[:analysis_root_path], @analysis_json[:analysis][:weather_file][:path]))
125
+ unless File.exist?(weather_filename)
126
+ @logger.warn "Could not find weather file for simulation #{weather_filename}. Will continue because may change"
127
+ end
128
+
129
+
130
+ else
131
+ fail 'No weather file path defined'
132
+ end
133
+ else
134
+ fail 'No weather file block defined'
135
+ end
136
+
137
+ weather_filename
138
+ end
139
+
140
+ def create_osm_from_xml
141
+ # Save the final state of the XML file
142
+ xml_filename = "#{@run_directory}/final.xml"
143
+ @model_xml.save(xml_filename)
144
+
145
+ @logger.info 'Starting XML to OSM translation'
146
+
147
+ # set the lib path first -- very specific for this applciation right now
148
+ @space_lib_path = File.expand_path("#{File.dirname(@options[:xml_library_file])}/space_types")
149
+ require @options[:xml_library_file]
150
+
151
+ @logger.info "The weather file is #{@weather_filename}"
152
+ begin
153
+ osxt = Main.new(@weather_directory, @space_lib_path)
154
+ osm, idf, new_xml, building_name, weather_file = osxt.process(@model_xml.to_s, false, true)
155
+ rescue Exception => e
156
+ log_message = "Runner error #{__FILE__} failed with #{e.message}, #{e.backtrace.join("\n")}"
157
+ fail log_message
158
+ end
159
+
160
+ if osm
161
+ osm_filename = "#{@run_directory}/xml_out.osm"
162
+ File.open(osm_filename, 'w') { |f| f << osm }
163
+
164
+ @logger.info 'Finished XML to OSM translation'
165
+ else
166
+ fail 'No OSM model output from XML translation'
167
+ end
168
+
169
+ @results[:osm_filename] = File.expand_path(osm_filename)
170
+ @results[:xml_filename] = File.expand_path(xml_filename)
171
+ @results[:weather_filename] = File.expand_path(File.join(@weather_directory,@weather_filename))
172
+ end
173
+
174
+ def apply_xml_measures
175
+ # iterate over the workflow and grab the measures
176
+ if @analysis_json[:analysis][:problem] && @analysis_json[:analysis][:problem][:workflow]
177
+ @analysis_json[:analysis][:problem][:workflow].each do |wf|
178
+ if wf[:measure_type] == 'XmlMeasure'
179
+ # need to map the variables to the XML classes
180
+ measure_path = wf[:measure_definition_directory]
181
+ measure_name = wf[:measure_definition_class_name]
182
+
183
+ @logger.info "XML Measure path is #{measure_path}"
184
+ @logger.info "XML Measure name is #{measure_name}"
185
+
186
+ @logger.info "Loading measure in relative path #{measure_path}"
187
+ measure_file_path = File.expand_path(
188
+ File.join(@options[:analysis_root_path], measure_path, 'measure.rb'))
189
+ fail "Measure file does not exist #{measure_name} in #{measure_file_path}" unless File.exist? measure_file_path
190
+
191
+ require measure_file_path
192
+ measure = Object.const_get(measure_name).new
193
+
194
+ @logger.info "iterate over arguments for workflow item #{wf[:name]}"
195
+
196
+ # The Argument hash in the workflow json file looks like the following
197
+ # {
198
+ # "display_name": "Set XPath",
199
+ # "machine_name": "set_xpath",
200
+ # "name": "xpath",
201
+ # "value": "/building/blocks/block/envelope/surfaces/window/layout/wwr",
202
+ # "uuid": "440dcce0-7663-0131-41f1-14109fdf0b37",
203
+ # "version_uuid": "440e4bd0-7663-0131-41f2-14109fdf0b37"
204
+ # }
205
+ args = {}
206
+ if wf[:arguments]
207
+ wf[:arguments].each do |wf_arg|
208
+ if wf_arg[:value]
209
+ @logger.info "Setting argument value #{wf_arg[:name]} to #{wf_arg[:value]}"
210
+ # Note that these measures have symbolized hash keys and not strings. I really want indifferential access here!
211
+ args[wf_arg[:name].to_sym] = wf_arg[:value]
212
+ end
213
+ end
214
+ end
215
+
216
+ @logger.info "iterate over variables for workflow item #{wf[:name]}"
217
+ if wf[:variables]
218
+ wf[:variables].each do |wf_var|
219
+ # Argument hash in workflow looks like the following
220
+ # "argument": {
221
+ # "display_name": "Window-To-Wall Ratio",
222
+ # "machine_name": "window_to_wall_ratio",
223
+ # "name": "value",
224
+ # "uuid": "a0618d15-bb0b-4494-a72f-8ad628693a7e",
225
+ # "version_uuid": "b33cf6b0-f1aa-4706-afab-9470e6bd1912"
226
+ # },
227
+ variable_uuid = wf_var[:uuid].to_sym # this is what the variable value is set to
228
+ if wf_var[:argument]
229
+ variable_name = wf_var[:argument][:name]
230
+
231
+ # Get the value from the data point json that was set via R / Problem Formulation
232
+ if @datapoint_json[:data_point]
233
+ if @datapoint_json[:data_point][:set_variable_values]
234
+ if @datapoint_json[:data_point][:set_variable_values][variable_uuid]
235
+ @logger.info "Setting variable #{variable_name} to #{@datapoint_json[:data_point][:set_variable_values][variable_uuid]}"
236
+
237
+ args[wf_var[:argument][:name].to_sym] = @datapoint_json[:data_point][:set_variable_values][variable_uuid]
238
+ args["#{wf_var[:argument][:name]}_machine_name".to_sym] = wf_var[:argument][:machine_name]
239
+ args["#{wf_var[:argument][:name]}_type".to_sym] = wf_var[:value_type] if wf_var[:value_type]
240
+ @logger.info "Setting the machine name for argument '#{wf_var[:argument][:name]}' to '#{args["#{wf_var[:argument][:name]}_machine_name".to_sym]}'"
241
+
242
+ # Catch a very specific case where the weather file has to be changed
243
+ if wf[:name] == 'location'
244
+ @logger.warn "VERY SPECIFIC case to change the location to #{@datapoint_json[:data_point][:set_variable_values][variable_uuid]}"
245
+ @weather_filename = @datapoint_json[:data_point][:set_variable_values][variable_uuid]
246
+ end
247
+ else
248
+ @logger.info "Value for variable '#{variable_name}:#{variable_uuid}' not set in datapoint object"
249
+ fail "Value for variable '#{variable_name}:#{variable_uuid}' not set in datapoint object" if CRASH_ON_NO_WORKFLOW_VARIABLE
250
+ break
251
+ end
252
+ else
253
+ fail 'No block for set_variable_values in data point record'
254
+ end
255
+ else
256
+ fail 'No block for data_point in data_point record'
257
+ end
258
+ end
259
+ end
260
+ end
261
+
262
+ # Run the XML Measure
263
+ xml_changed = measure.run(@model_xml, nil, args)
264
+
265
+ # save the JSON with the changed values
266
+ # the measure has to implement the "results_to_json" method
267
+ @output_attributes[wf[:name].to_sym] = measure.variable_values
268
+ measure.results_to_json("#{@run_directory}/#{wf[:name]}_results.json")
269
+
270
+ # TODO: do we want to do this?
271
+ #ros.communicate_intermediate_result(measure.variable_values)
272
+
273
+ @logger.info "Finished applying measure workflow #{wf[:name]} with change flag set to '#{xml_changed}'"
274
+ end
275
+ end
276
+ end
277
+
278
+ end
279
+ end
@@ -0,0 +1,48 @@
1
+ ######################################################################
2
+ # Copyright (c) 2008-2014, Alliance for Sustainable Energy.
3
+ # All rights reserved.
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+ ######################################################################
19
+
20
+ require 'logger'
21
+
22
+ class Logger
23
+ def format_message(severity, datetime, progname, msg)
24
+ #"#{datetime} (#{$$}) #{msg}\n"
25
+ #"#{datetime}: #{msg}\n"
26
+ "[%s %s] %s\n" % [ datetime.strftime("%H:%M:%S.%6N"), severity, msg ]
27
+ end
28
+ end
29
+
30
+ # Class to allow multiple logging paths
31
+ class MultiDelegator
32
+ def initialize(*targets)
33
+ @targets = targets
34
+ end
35
+
36
+ def self.delegate(*methods)
37
+ methods.each do |m|
38
+ define_method(m) do |*args|
39
+ @targets.map { |t| t.send(m, *args) }
40
+ end
41
+ end
42
+ self
43
+ end
44
+
45
+ class <<self
46
+ alias to new
47
+ end
48
+ end
@@ -0,0 +1,258 @@
1
+ ######################################################################
2
+ # Copyright (c) 2008-2014, Alliance for Sustainable Energy.
3
+ # All rights reserved.
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+ ######################################################################
19
+
20
+ # Run Class for OpenStudio workflow. The data are passed in via the adapter
21
+ module OpenStudio
22
+ module Workflow
23
+ class Run
24
+ include AASM
25
+
26
+ attr_accessor :logger
27
+
28
+ attr_reader :options
29
+ attr_reader :adapter
30
+ attr_reader :directory
31
+ attr_reader :run_directory
32
+ attr_reader :final_state
33
+ attr_reader :job_results
34
+
35
+
36
+ # Create a nice name for the state object instead of aasm
37
+ alias state aasm
38
+
39
+ # load the transitions
40
+ def self.default_transition
41
+ # TODO: replace these with dynamic states from a config file of some sort
42
+ [
43
+ {from: :queued, to: :preflight},
44
+ {from: :preflight, to: :openstudio},
45
+ {from: :openstudio, to: :energyplus},
46
+ {from: :energyplus, to: :postprocess},
47
+ {from: :postprocess, to: :finished},
48
+ ]
49
+ end
50
+
51
+ # The default states for the workflow. Note that the states of :queued of :finished need
52
+ # to exist for all cases.
53
+ def self.default_states
54
+ # TODO: replace this with some sort of dynamic store
55
+ [
56
+ {state: :queued, :options => {initial: true}},
57
+ {state: :preflight, :options => {after_enter: :run_preflight}},
58
+ {state: :openstudio, :options => {after_enter: :run_openstudio}},
59
+ {state: :energyplus, :options => {after_enter: :run_energyplus}},
60
+ {state: :postprocess, :options => {after_enter: :run_postprocess}},
61
+ {state: :finished},
62
+ {state: :errored}
63
+ ]
64
+ end
65
+
66
+ # initialize a new run class
67
+ #
68
+ # @param adapter an instance of the adapter class
69
+ # @param directory location of the datapoint directory to run. This is needed
70
+ # independent of the adapter that is being used. Note that the simulation will actually run in 'run'
71
+ def initialize(adapter, directory, options = {})
72
+ @adapter = adapter
73
+ @directory = directory
74
+ # TODO: run directory is a convention right now. Move to a configuration item
75
+ @run_directory = "#{@directory}/run"
76
+
77
+ defaults = {
78
+ transitions: OpenStudio::Workflow::Run.default_transition,
79
+ states: OpenStudio::Workflow::Run.default_states,
80
+ jobs: {}
81
+ }
82
+ @options = defaults.merge(options)
83
+
84
+ @error = false
85
+
86
+ @job_results = {}
87
+
88
+ # By default blow away the entire run directory every time and recreate it
89
+ FileUtils.rm_rf(@run_directory) if File.exist?(@run_directory)
90
+ FileUtils.mkdir_p(@run_directory)
91
+
92
+ # There is a namespace conflict when OpenStudio is loaded: be careful!
93
+ log_file = File.open("#{@run_directory}/run.log", "a")
94
+
95
+ l = @adapter.get_logger @directory, @options
96
+ if l
97
+ @logger = ::Logger.new MultiDelegator.delegate(:write, :close).to(STDOUT, log_file, l)
98
+ else
99
+ @logger = ::Logger.new MultiDelegator.delegate(:write, :close).to(STDOUT, log_file)
100
+ end
101
+
102
+ @logger.info "Initializing directory #{@directory} for simulation with options #{@options}"
103
+
104
+ super()
105
+
106
+ # load the state machine
107
+ machine
108
+ end
109
+
110
+ # run the simulations.
111
+ # TODO: add a catch if any job fails; TODO: make a block method to provide feedback
112
+ def run
113
+ @logger.info "Starting workflow"
114
+ begin
115
+ while self.state.current_state != :finished && !@error
116
+ self.step
117
+ end
118
+
119
+ @logger.info "Finished workflow"
120
+
121
+ if @error
122
+ # need to tell the system that this failed
123
+ @adapter.communicate_failure @directory
124
+ end
125
+
126
+ # TODO: this should be a job that handles the use case with a :guard on if @job_results[:run_postprocess]
127
+ if @job_results[:run_postprocess]
128
+ # these are the results that need to be sent back to adapter
129
+ @logger.info "Sending the results back to the adapter"
130
+ #@logger.info "Sending communicate_results the following options #{@job_results}"
131
+ @adapter.communicate_results @directory, @job_results[:run_postprocess]
132
+ end
133
+ ensure
134
+ @adapter.communicate_complete @directory
135
+ @logger.info "Running workflow from #{__FILE__} complete"
136
+
137
+ # TODO: define the outputs and figure out how to show it correctory
138
+ obj_function_array ||= ['NA']
139
+
140
+ # Print the objective functions to the screen even though the file is being used right now
141
+ # Note as well that we can't guarantee that the csv format will be in the right order
142
+ puts obj_function_array.join(',')
143
+ end
144
+
145
+ state.current_state
146
+ end
147
+
148
+ # call back for when there is an exception running any of the state transitions
149
+ def step_error(*args)
150
+ # Make sure to set the instance variable @error to true in order to stop the :step
151
+ # event from being fired.
152
+ @error = true
153
+ @logger.error "Found error in state '#{aasm.current_state}' with message #{args}}"
154
+
155
+ # Call the error_out event to transition to the :errored state
156
+ error_out
157
+ end
158
+
159
+ # TODO: these methods needs to be dynamic or inherited
160
+ # run energplus
161
+ def run_energyplus
162
+ @logger.info "Running #{__method__}"
163
+ klass = get_run_class(__method__)
164
+
165
+ @job_results[__method__.to_sym] = klass.perform
166
+ end
167
+
168
+ # run openstudio to create the model and apply the measures
169
+ def run_openstudio
170
+ @logger.info "Running #{__method__}"
171
+ klass = get_run_class(__method__)
172
+
173
+ # TODO: save the resulting filenames to an array
174
+ @job_results[__method__.to_sym] = klass.perform
175
+ end
176
+
177
+ def run_postprocess
178
+ @logger.info "Running #{__method__}"
179
+ klass = get_run_class(__method__)
180
+
181
+ @job_results[__method__.to_sym] = klass.perform
182
+ end
183
+
184
+ # preconfigured run method for preflight. This configures the input directories and sets everything
185
+ # up for running the simulations.
186
+ def run_preflight
187
+ @logger.info "Running #{__method__}"
188
+ klass = get_run_class(__method__)
189
+
190
+ @job_results[__method__.to_sym] = klass.perform
191
+ end
192
+
193
+ def run_xml
194
+ @logger.info "Running #{__method__}"
195
+ klass = get_run_class(__method__)
196
+
197
+ @job_results[__method__.to_sym] = klass.perform
198
+ @logger.info @job_results
199
+ end
200
+
201
+ def final_state
202
+ state.current_state
203
+ end
204
+
205
+ private
206
+
207
+ # Create a state machine from the predefined transitions methods. This loads in
208
+ # a single event of :step which steps through the transitions defined in the Hash in default_transitions
209
+ # and calls the actions defined in the states in the Hash of default_states
210
+ def machine
211
+ @logger.info "Initializing state machine"
212
+ @options[:states].each do |s|
213
+ s[:options] ? o = s[:options] : o = {}
214
+ OpenStudio::Workflow::Run.aasm.states << AASM::State.new(s[:state], self.class, o)
215
+ end
216
+ OpenStudio::Workflow::Run.aasm.initial_state(:queued)
217
+
218
+ # Create a new event and add in the transitions
219
+ new_event = OpenStudio::Workflow::Run.aasm.event(:step)
220
+ event = OpenStudio::Workflow::Run.aasm.events[:step]
221
+
222
+ # TODO: make a config option to not go to the error state. Useful to not error-state when testing
223
+ event.options[:error] = 'step_error'
224
+ @options[:transitions].each do |t|
225
+ event.transitions(t)
226
+ end
227
+
228
+ # Add in special event to error_out the state machine
229
+ new_event = OpenStudio::Workflow::Run.aasm.event(:error_out)
230
+ event = OpenStudio::Workflow::Run.aasm.events[:error_out]
231
+ event.transitions(:to => :errored)
232
+ end
233
+
234
+ # Get any options that may have been sent into the class defining the workflow step
235
+ def get_job_options
236
+ result = {}
237
+ #if @options[:jobs].has_key?(state.current_state)
238
+ #logger.info "Retrieving job options from the @options array for #{state.current_state}"
239
+ # result = @options[:jobs][state.current_state]
240
+ #end
241
+
242
+ #result
243
+
244
+ # TODO fix this so that it gets the base config options plus its job options. Need to
245
+ # also merge in all the former job results.
246
+ @options.merge(@job_results)
247
+ end
248
+
249
+ def get_run_class(from_method)
250
+ require_relative "jobs/#{from_method}/#{from_method}"
251
+ klass_name = from_method.to_s.split('_').map(&:capitalize) * ''
252
+ @logger.info "Getting method for state transition '#{from_method}'"
253
+ klass = Object.const_get(klass_name).new(@directory, @logger, @adapter, get_job_options)
254
+ klass
255
+ end
256
+ end
257
+ end
258
+ end