openstudio-workflow 0.0.1

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