openstudio-workflow 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8246584121ca33fc5d5b10e34ff852d0884d6de6
4
- data.tar.gz: 979bcb2d5e8445a6cf69cf1e948d091ae1a0cf61
3
+ metadata.gz: 8fa9aff18f17a837285bcc9bc72d8beff2b82028
4
+ data.tar.gz: 871e9aeffdd30c228cf804c758ea9a8918fa87ea
5
5
  SHA512:
6
- metadata.gz: 843a1e9af4d17e54b28d30d83fe9020502c29ac71e350f3e1625694feea3120a97826dd8ae8c383e3f4bf75ab91ada7b2c67de3d737ed339bdbd099f4ba1517f
7
- data.tar.gz: a9463ff16b20e54881097c2fae31ad82f9ed9a1d3398a7fc0987640abaa824fba58923bfd4783c52572d91ca673c967081c0d735e281e7657d30bd193b373773
6
+ metadata.gz: eee7ff6d5a939d168d981b1685d98e6264cabe87c0f8cf1a8693402ac8b635a0b6deab233a9675790c82e51a7bc5ce2335d21934d69a9555d279b3421b10d856
7
+ data.tar.gz: 37c6b1264805b11be32b885950d1aa5492dd4daf2c66c2e3a53402bc8749c071b218db914122b8063779fefa683eabd5e76329277238f6bc7ff55cd6b9b52c10
data/CHANGELOG.md CHANGED
@@ -1,8 +1,13 @@
1
1
  OpenStudio::Workflow Change Log
2
2
  ==================================
3
3
 
4
- Unreleased
4
+ Version 0.0.3
5
5
  --------------
6
+ * Allow measures to set weather file in a measure and have it update what EnergyPlus uses for the weather file.
7
+ * OpenStudio::Workflow.run_energyplus method added to just run energyplus.
8
+ * Remove AASM (act as state machine) and replace with simple tracking of the state. Interface is the same except there is now no need to pass in the States.
9
+ * Catch EnergyPlus errors (non-zero exits), Fatal Errors in eplusout.err, and invalid weather files.
10
+ * Force UTF-8 version upon reading the eplusout.err file
6
11
 
7
12
  Version 0.0.2
8
13
  --------------
@@ -16,4 +21,4 @@ Version 0.0.2
16
21
  Version 0.0.1
17
22
  --------------
18
23
 
19
- * Initial release with basic workflow implemented to support running OpenStudio measure-based workflows
24
+ * Initial release with basic workflow implemented to support running OpenStudio measure-based workflows
@@ -17,11 +17,11 @@
17
17
  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
18
  ######################################################################
19
19
 
20
- require 'aasm'
21
20
  require 'pp'
22
21
  require 'multi_json'
23
22
  require 'colored'
24
23
  require 'fileutils'
24
+ require 'securerandom'
25
25
  require 'json' # needed for a single pretty generate call
26
26
  require 'pathname'
27
27
 
@@ -48,10 +48,10 @@ end
48
48
  class String
49
49
  def snake_case
50
50
  gsub(/::/, '/')
51
- .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
52
- .gsub(/([a-z\d])([A-Z])/, '\1_\2')
53
- .tr(' -', '__')
54
- .downcase
51
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
52
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
53
+ .tr(' -', '__')
54
+ .downcase
55
55
  end
56
56
  end
57
57
 
@@ -83,6 +83,38 @@ module OpenStudio
83
83
  run_klass
84
84
  end
85
85
 
86
+ # predefined method that simply runs EnergyPlus in the specified directory. It does not apply any workflow steps
87
+ # such as preprocessing / postprocessing.
88
+ # The directory must have the IDF and EPW file in the folder. The simulations will run in the directory/run path
89
+ def run_energyplus(adapter_name, run_directory, options = {})
90
+ unless (Pathname.new run_directory).absolute?
91
+ # relative to wherever you are running the script
92
+ run_directory = File.expand_path run_directory
93
+ end
94
+
95
+ transitions = [
96
+ { from: :queued, to: :preflight },
97
+ { from: :preflight, to: :energyplus },
98
+ { from: :energyplus, to: :finished }
99
+ ]
100
+
101
+ states = [
102
+ { state: :queued, options: { initial: true } },
103
+ { state: :preflight, options: { after_enter: :run_preflight } },
104
+ { state: :energyplus, options: { after_enter: :run_energyplus } },
105
+ { state: :finished },
106
+ { state: :errored }
107
+ ]
108
+ options = {
109
+ transitions: transitions,
110
+ states: states
111
+ }
112
+
113
+ adapter = load_adapter adapter_name, options[:adapter_options]
114
+ run_klass = OpenStudio::Workflow::Run.new(adapter, run_directory, options)
115
+ run_klass
116
+ end
117
+
86
118
  private
87
119
 
88
120
  def load_adapter(name, adapter_options = {})
@@ -29,7 +29,7 @@ module OpenStudio
29
29
  end
30
30
 
31
31
  def write(msg)
32
- @dp.sdp_log_file << msg.gsub('\n', '')
32
+ @dp.sdp_log_file << msg.gsub("\n", '')
33
33
  @dp.save!
34
34
  end
35
35
  end
@@ -99,7 +99,6 @@ module OpenStudio
99
99
  start_time = ::Time.now
100
100
  current_dir = Dir.pwd
101
101
  begin
102
-
103
102
  measure_working_directory = "#{@run_directory}/#{workflow_item[:measure_definition_class_name]}"
104
103
 
105
104
  @logger.info "Creating run directory to #{measure_working_directory}"
@@ -51,19 +51,50 @@ class RunEnergyplus
51
51
  # Ensure that the directory is created (but it should already be at this point)
52
52
  FileUtils.mkdir_p(@run_directory)
53
53
 
54
+ # if the weather file is already in the directory, then just use that weather file
55
+ weather_file_name = nil
56
+ weather_files = Dir["#{@directory}/*.epw"]
57
+ if weather_files.size > 1
58
+ @logger.info 'Multiple weather files in the directory. Will rely on the weather file name in the openstudio model'
59
+ elsif weather_files.size == 1
60
+ weather_file_name = weather_files.first
61
+ end
62
+
54
63
  # verify that the OSM, IDF, and the Weather files are in the run directory as the 'in.*' format
55
- if @options[:run_openstudio][:weather_filename] && File.exist?(@options[:run_openstudio][:weather_filename])
64
+ if !weather_file_name &&
65
+ @options[:run_openstudio] &&
66
+ @options[:run_openstudio][:weather_filename] &&
67
+ File.exist?(@options[:run_openstudio][:weather_filename])
68
+ weather_file_name = @options[:run_openstudio][:weather_filename]
69
+ end
70
+
71
+ if weather_file_name
56
72
  # verify that it is named in.epw
57
- unless File.basename(@options[:run_openstudio][:weather_filename]).downcase == 'in.idf'
58
- FileUtils.copy(@options[:run_openstudio][:weather_filename], "#{@run_directory}/in.epw")
59
- end
73
+ @logger.info "Weather file for EnergyPlus simulation is #{weather_file_name}"
74
+ FileUtils.copy(weather_file_name, "#{@run_directory}/in.epw")
60
75
  else
61
76
  fail "EPW file not found or not sent to #{self.class}"
62
77
  end
63
78
 
79
+ # check if the run folder has an IDF. If not then check if the parent folder does.
80
+ idf_file_name = nil
81
+ if File.exist?("#{@run_directory}/in.idf")
82
+ @logger.info 'IDF (in.idf) already exists in the run directory'
83
+ else
84
+ # glob for idf at the directory level
85
+ idfs = Dir["#{@directory}/*.idf"]
86
+ if idfs.size > 1
87
+ @logger.info 'Multiple IDF files in the directory. Cannot continue'
88
+ elsif idfs.size == 1
89
+ idf_file_name = idfs.first
90
+ end
91
+ end
92
+
64
93
  # Need to check the in.idf and in.osm
65
94
  # FileUtils.copy(options[:osm], "#{@run_directory}/in.osm")
66
- # FileUtils.copy(options[:idf], "#{@run_directory}/in.idf")
95
+ if idf_file_name
96
+ FileUtils.copy(idf_file_name, "#{@run_directory}/in.idf")
97
+ end
67
98
 
68
99
  # can't create symlinks because the /vagrant mount is actually a windows mount
69
100
  @logger.info "Copying EnergyPlus files to run directory: #{@run_directory}"
@@ -105,15 +136,41 @@ class RunEnergyplus
105
136
 
106
137
  # create stdout
107
138
  File.open('stdout-energyplus', 'w') do |file|
108
- IO.popen('./EnergyPlus') do |io|
139
+ IO.popen('./EnergyPlus + 2>&1') do |io|
109
140
  while (line = io.gets)
110
141
  file << line
111
142
  end
112
143
  end
113
144
  end
145
+ r = $?
146
+
147
+ @logger.info "System call to EnergyPlus returned #{r}"
148
+
149
+ paths_to_rm = []
150
+ paths_to_rm << Pathname.glob("#{@run_directory}/*.ini")
151
+ paths_to_rm << Pathname.glob("#{@run_directory}/*.so")
152
+ paths_to_rm << Pathname.glob("#{@run_directory}/*.idd")
153
+ paths_to_rm << Pathname.glob("#{@run_directory}/ExpandObjects")
154
+ paths_to_rm << Pathname.glob("#{@run_directory}/EnergyPlus")
155
+ paths_to_rm << Pathname.glob("#{@run_directory}/packaged_measures")
156
+ paths_to_rm.each { |p| FileUtils.rm_rf(p) }
157
+
158
+ unless r == 0
159
+ fail "EnergyPlus returned a non-zero exit code. Check the stdout-energyplus log."
160
+ end
161
+
162
+ # TODO: check the end or err file
163
+ if File.exists? 'eplusout.err'
164
+ eplus_err = File.read('eplusout.err')
165
+ eplus_err = eplus_err.force_encoding('ISO-8859-1').encode('utf-8', replace: nil)
166
+ if eplus_err =~ /EnergyPlus Terminated--Fatal Error Detected/
167
+ fail "EnergyPlus Terminated with a Fatal Error. Check eplusout.err log."
168
+ end
169
+ end
114
170
  rescue => e
115
171
  log_message = "#{__FILE__} failed with #{e.message}, #{e.backtrace.join("\n")}"
116
172
  @logger.error log_message
173
+ fail log_message
117
174
  ensure
118
175
  Dir.chdir(current_dir)
119
176
  @logger.info 'EnergyPlus Completed'
@@ -72,8 +72,12 @@ class RunOpenstudio
72
72
  updated_weather_file = get_weather_file_from_model
73
73
  unless updated_weather_file == @initial_weather_file
74
74
  # reset the result hash so the future processes know which weather file to run
75
- @logger.info "Updating the weather file result to be #{updated_weather_file }"
76
- @results[:weather_filename] = "#{@weather_file_path}/#{updated_weather_file}"
75
+ @logger.info "Updating the weather file to be '#{updated_weather_file}'"
76
+ if (Pathname.new updated_weather_file).absolute? && (Pathname.new updated_weather_file).exist?
77
+ @results[:weather_filename] = updated_weather_file
78
+ else
79
+ @results[:weather_filename] = "#{@weather_file_path}/#{updated_weather_file}"
80
+ end
77
81
  end
78
82
 
79
83
  @logger.info 'Saving measure output attributes JSON'
@@ -91,15 +95,9 @@ class RunOpenstudio
91
95
 
92
96
  def save_osm_and_idf
93
97
  # save the data
94
- a = Time.now
95
- osm_filename = "#{@run_directory}/out_raw.osm"
96
- File.open(osm_filename, 'w') { |f| f << @model.to_s }
97
- b = Time.now
98
- @logger.info "Ruby write took #{b.to_f - a.to_f}"
99
-
100
98
  a = Time.now
101
99
  osm_filename = "#{@run_directory}/in.osm"
102
- @model.save(OpenStudio::Path.new(osm_filename), true)
100
+ File.open(osm_filename, 'w') { |f| f << @model.to_s }
103
101
  b = Time.now
104
102
  @logger.info "OpenStudio write took #{b.to_f - a.to_f}"
105
103
 
@@ -141,6 +139,7 @@ class RunOpenstudio
141
139
  fail 'No seed model path in JSON defined'
142
140
  end
143
141
  else
142
+ # TODO: create a blank model and return
144
143
  fail 'No seed model block'
145
144
  end
146
145
 
@@ -173,7 +172,8 @@ class RunOpenstudio
173
172
  elsif @analysis_json[:analysis][:weather_file]
174
173
  if @analysis_json[:analysis][:weather_file][:path]
175
174
  weather_filename = File.expand_path(
176
- File.join(@options[:analysis_root_path], @analysis_json[:analysis][:weather_file][:path]))
175
+ File.join(@options[:analysis_root_path], @analysis_json[:analysis][:weather_file][:path])
176
+ )
177
177
  @weather_file_path = File.dirname(weather_filename)
178
178
  else
179
179
  fail 'No weather file path defined'
@@ -191,15 +191,26 @@ class RunOpenstudio
191
191
  weather_filename
192
192
  end
193
193
 
194
+ # return the weather file from the model. If the weather file is defined in the model, then
195
+ # it checks the file paths to check if the model exists. This allows for a user to upload a
196
+ # weather file in a measure and then have the measure's path be used for the weather file.
194
197
  def get_weather_file_from_model
195
198
  wf = nil
196
199
  # grab the weather file out of the OSM if it exists
197
200
  if @model.weatherFile.empty?
198
201
  @logger.info 'No weather file in model'
199
202
  else
200
- # this is the weather file from the OSM model
201
- wf = File.basename(@model.weatherFile.get.path.get.to_s)
202
- @logger.info "Model weather file is #{wf}" # unless model.weatherFile.empty?
203
+ p = @model.weatherFile.get.path.get.to_s.gsub('file://', '')
204
+ if File.exist? p
205
+ # use this entire path
206
+ @logger.info "Full path to weather file exists #{p}"
207
+ wf = p
208
+ else
209
+ # this is the weather file from the OSM model
210
+ wf = File.basename(@model.weatherFile.get.path.get.to_s)
211
+ end
212
+
213
+ # @logger.info "Initial model weather file is #{wf}" # unless model.weatherFile.empty?
203
214
  end
204
215
 
205
216
  wf
@@ -216,7 +227,7 @@ class RunOpenstudio
216
227
  forward_translator = OpenStudio::EnergyPlus::ForwardTranslator.new
217
228
  @model_idf = forward_translator.translateModel(@model)
218
229
  b = Time.now
219
- @logger.info "Translate object to energyplus IDF took #{b.to_f - a.to_f}"
230
+ @logger.info "Translate object to EnergyPlus IDF took #{b.to_f - a.to_f}"
220
231
  end
221
232
  end
222
233
  end
@@ -94,17 +94,11 @@ class RunPostprocess
94
94
  # paths_to_rm << Pathname.glob("#{@run_directory}/*.idf") # keep the idfs
95
95
  # paths_to_rm << Pathname.glob("*.audit")
96
96
  # paths_to_rm << Pathname.glob("*.bnd")
97
- paths_to_rm << Pathname.glob("#{@run_directory}/*.ini")
98
97
  paths_to_rm << Pathname.glob("#{@run_directory}/*.eso")
99
98
  paths_to_rm << Pathname.glob("#{@run_directory}/*.mtr")
100
- paths_to_rm << Pathname.glob("#{@run_directory}/*.so")
101
99
  paths_to_rm << Pathname.glob("#{@run_directory}/*.epw")
102
- paths_to_rm << Pathname.glob("#{@run_directory}/*.idd")
103
100
  paths_to_rm << Pathname.glob("#{@run_directory}/*.mtd")
104
101
  paths_to_rm << Pathname.glob("#{@run_directory}/*.rdd")
105
- paths_to_rm << Pathname.glob("#{@run_directory}/ExpandObjects")
106
- paths_to_rm << Pathname.glob("#{@run_directory}/EnergyPlus")
107
- paths_to_rm << Pathname.glob("#{@run_directory}/packaged_measures")
108
102
  paths_to_rm.each { |p| FileUtils.rm_rf(p) }
109
103
  end
110
104
  end
@@ -65,9 +65,7 @@ class RunRunmanager
65
65
  def perform
66
66
  @logger.info "Calling #{__method__} in the #{self.class} class"
67
67
  @logger.info "Current directory is #{@directory}"
68
-
69
68
  begin
70
-
71
69
  @logger.info 'Retrieving datapoint and problem'
72
70
  @datapoint_json = @adapter.get_datapoint(@directory.to_s, @options)
73
71
  @analysis_json = @adapter.get_problem(@directory.to_s, @options)
@@ -108,9 +106,9 @@ class RunRunmanager
108
106
  # load problem formulation
109
107
  loadResult = OpenStudio::Analysis.loadJSON(JSON.pretty_generate(@analysis_json))
110
108
  if loadResult.analysisObject.empty?
111
- loadResult.errors.each { |error|
109
+ loadResult.errors.each do |error|
112
110
  @logger.warn error.logMessage # DLM: is this right?
113
- }
111
+ end
114
112
  fail 'Unable to load analysis json.'
115
113
  end
116
114
 
@@ -130,9 +128,9 @@ class RunRunmanager
130
128
  # load data point to run
131
129
  loadResult = OpenStudio::Analysis.loadJSON(JSON.pretty_generate(@datapoint_json))
132
130
  if loadResult.analysisObject.empty?
133
- loadResult.errors.each { |error|
131
+ loadResult.errors.each do |error|
134
132
  @logger.warn error.logMessage
135
- }
133
+ end
136
134
  fail 'Unable to load data point json.'
137
135
  end
138
136
  data_point = loadResult.analysisObject.get.to_DataPoint.get
@@ -116,7 +116,7 @@ class RunXml
116
116
  model
117
117
  end
118
118
 
119
- # Save the weather file to the instance variable
119
+ # Save the weather file to the instance variable. This can change later after measures run.
120
120
  def load_weather_file
121
121
  weather_filename = nil
122
122
  if @analysis_json[:analysis][:weather_file]
@@ -21,8 +21,6 @@
21
21
  module OpenStudio
22
22
  module Workflow
23
23
  class Run
24
- include AASM
25
-
26
24
  attr_accessor :logger
27
25
 
28
26
  attr_reader :options
@@ -30,61 +28,57 @@ module OpenStudio
30
28
  attr_reader :directory
31
29
  attr_reader :run_directory
32
30
  attr_reader :final_state
31
+ attr_reader :final_message
33
32
  attr_reader :job_results
34
33
 
35
- # Create a nice name for the state object instead of aasm
36
- alias_method :state, :aasm
37
-
38
34
  # load the transitions
39
35
  def self.default_transition
40
- # TODO: replace these with dynamic states from a config file of some sort
41
36
  [
42
- { from: :queued, to: :preflight },
43
- { from: :preflight, to: :openstudio },
44
- { from: :openstudio, to: :energyplus },
45
- { from: :energyplus, to: :reporting_measures },
46
- { from: :reporting_measures, to: :postprocess },
47
- { from: :postprocess, to: :finished }
37
+ {from: :queued, to: :preflight},
38
+ {from: :preflight, to: :openstudio},
39
+ {from: :openstudio, to: :energyplus},
40
+ {from: :energyplus, to: :reporting_measures},
41
+ {from: :reporting_measures, to: :postprocess},
42
+ {from: :postprocess, to: :finished}
48
43
  ]
49
44
  end
50
45
 
51
46
  # The default states for the workflow. Note that the states of :queued of :finished need
52
47
  # to exist for all cases.
53
48
  def self.default_states
54
- # TODO: replace this with some sort of dynamic store
49
+ warn "[Deprecation Warning] explicitly specifying states will no longer be required in 0.3.0. Method #{__method__}"
55
50
  [
56
- { state: :queued, options: { initial: true } },
57
- { state: :preflight, options: { after_enter: :run_preflight } },
58
- { state: :openstudio, options: { after_enter: :run_openstudio } }, # TODO: this should be run_openstudio_measures and run_energyplus_measures
59
- { state: :energyplus, options: { after_enter: :run_energyplus } },
60
- { state: :reporting_measures, options: { after_enter: :run_reporting_measures } },
61
- { state: :postprocess, options: { after_enter: :run_postprocess } },
62
- { state: :finished },
63
- { state: :errored }
51
+ {state: :queued, options: {initial: true}},
52
+ {state: :preflight, options: {after_enter: :run_preflight}},
53
+ {state: :openstudio, options: {after_enter: :run_openstudio}}, # TODO: this should be run_openstudio_measures and run_energyplus_measures
54
+ {state: :energyplus, options: {after_enter: :run_energyplus}},
55
+ {state: :reporting_measures, options: {after_enter: :run_reporting_measures}},
56
+ {state: :postprocess, options: {after_enter: :run_postprocess}},
57
+ {state: :finished},
58
+ {state: :errored}
64
59
  ]
65
60
  end
66
61
 
67
62
  # transitions for pat job
68
63
  def self.pat_transition
69
- # TODO: replace these with dynamic states from a config file of some sort
70
64
  [
71
- { from: :queued, to: :preflight },
72
- { from: :preflight, to: :runmanager },
73
- { from: :runmanager, to: :postprocess },
74
- { from: :postprocess, to: :finished }
65
+ {from: :queued, to: :preflight},
66
+ {from: :preflight, to: :runmanager},
67
+ {from: :runmanager, to: :postprocess},
68
+ {from: :postprocess, to: :finished}
75
69
  ]
76
70
  end
77
71
 
78
72
  # states for pat job
79
73
  def self.pat_states
80
- # TODO: replace this with some sort of dynamic store
74
+ warn "[Deprecation Warning] explicitly specifying states will no longer be required in 0.3.0. Method #{__method__}"
81
75
  [
82
- { state: :queued, options: { initial: true } },
83
- { state: :preflight, options: { after_enter: :run_preflight } },
84
- { state: :runmanager, options: { after_enter: :run_runmanager } },
85
- { state: :postprocess, options: { after_enter: :run_postprocess } },
86
- { state: :finished },
87
- { state: :errored }
76
+ {state: :queued, options: {initial: true}},
77
+ {state: :preflight, options: {after_enter: :run_preflight}},
78
+ {state: :runmanager, options: {after_enter: :run_runmanager}},
79
+ {state: :postprocess, options: {after_enter: :run_postprocess}},
80
+ {state: :finished},
81
+ {state: :errored}
88
82
  ]
89
83
  end
90
84
 
@@ -93,8 +87,12 @@ module OpenStudio
93
87
  # @param adapter an instance of the adapter class
94
88
  # @param directory location of the datapoint directory to run. This is needed
95
89
  # independent of the adapter that is being used. Note that the simulation will actually run in 'run'
90
+ # @param options that are sent to the adapters
96
91
  def initialize(adapter, directory, options = {})
97
92
  @adapter = adapter
93
+ @final_message = ''
94
+ @current_state = nil
95
+ @transitions = {}
98
96
  @directory = directory
99
97
  # TODO: run directory is a convention right now. Move to a configuration item
100
98
  @run_directory = "#{@directory}/run"
@@ -102,21 +100,19 @@ module OpenStudio
102
100
  defaults = nil
103
101
  if options[:is_pat]
104
102
  defaults = {
105
- transitions: OpenStudio::Workflow::Run.pat_transition,
106
- states: OpenStudio::Workflow::Run.pat_states,
107
- jobs: {}
103
+ transitions: OpenStudio::Workflow::Run.pat_transition,
104
+ states: OpenStudio::Workflow::Run.pat_states,
105
+ jobs: {}
108
106
  }
109
107
  else
110
108
  defaults = {
111
- transitions: OpenStudio::Workflow::Run.default_transition,
112
- states: OpenStudio::Workflow::Run.default_states,
113
- jobs: {}
109
+ transitions: OpenStudio::Workflow::Run.default_transition,
110
+ states: OpenStudio::Workflow::Run.default_states,
111
+ jobs: {}
114
112
  }
115
113
  end
116
114
  @options = defaults.merge(options)
117
115
 
118
- @error = false
119
-
120
116
  @job_results = {}
121
117
 
122
118
  # By default blow away the entire run directory every time and recreate it
@@ -136,8 +132,6 @@ module OpenStudio
136
132
  @logger.info "Initializing directory #{@directory} for simulation with options #{@options}"
137
133
  @logger.info "OpenStudio loaded: '#{$openstudio_gem}'"
138
134
 
139
- super()
140
-
141
135
  # load the state machine
142
136
  machine
143
137
  end
@@ -147,7 +141,8 @@ module OpenStudio
147
141
  def run
148
142
  @logger.info "Starting workflow in #{@directory}"
149
143
  begin
150
- while state.current_state != :finished && !@error
144
+ while @current_state != :finished && @current_state != :errored
145
+ sleep 2
151
146
  step
152
147
  end
153
148
 
@@ -164,7 +159,7 @@ module OpenStudio
164
159
  @adapter.communicate_results @directory, @job_results[:run_reporting_measures]
165
160
  end
166
161
  ensure
167
- if @error
162
+ if @current_state == :errored
168
163
  @adapter.communicate_failure @directory
169
164
  else
170
165
  @adapter.communicate_complete @directory
@@ -180,18 +175,29 @@ module OpenStudio
180
175
  puts obj_function_array.join(',')
181
176
  end
182
177
 
183
- state.current_state
178
+ @current_state
179
+ end
180
+
181
+ # Step through the states, if there is an error (e.g. exception) then go to error
182
+ def step(*args)
183
+ begin
184
+ next_state
185
+
186
+ send("run_#{@current_state}")
187
+ rescue => e
188
+ step_error("#{e.message}:#{e.backtrace.join("\n")}")
189
+ end
184
190
  end
185
191
 
186
192
  # call back for when there is an exception running any of the state transitions
187
193
  def step_error(*args)
188
194
  # Make sure to set the instance variable @error to true in order to stop the :step
189
195
  # event from being fired.
190
- @error = true
191
- @logger.error "Found error in state '#{aasm.current_state}' with message #{args}}"
196
+ @final_message = "Found error in state '#{@current_state}' with message #{args}}"
197
+ @logger.error @final_message
192
198
 
193
- # Call the error_out event to transition to the :errored state
194
- error_out
199
+ # transition to an error state
200
+ @current_state = :errored
195
201
  end
196
202
 
197
203
  # TODO: these methods needs to be dynamic or inherited
@@ -254,45 +260,42 @@ module OpenStudio
254
260
  @logger.info @job_results
255
261
  end
256
262
 
257
- def final_state
258
- state.current_state
263
+ # last method that is called.
264
+ def run_finished
265
+ @logger.info "Running #{__method__}"
266
+
267
+ @current_state
259
268
  end
269
+ alias_method :final_state, :run_finished
260
270
 
261
271
  private
262
272
 
263
- # Create a state machine from the predefined transitions methods. This loads in
264
- # a single event of :step which steps through the transitions defined in the Hash in default_transitions
265
- # and calls the actions defined in the states in the Hash of default_states
273
+ # Create a state machine from the predefined transitions methods. This will initialize in the :queued state
274
+ # and then load in the transitions from the @options hash
266
275
  def machine
267
276
  @logger.info 'Initializing state machine'
268
- @options[:states].each do |s|
269
- s[:options] ? o = s[:options] : o = {}
270
- OpenStudio::Workflow::Run.aasm.states << AASM::State.new(s[:state], self.class, o)
271
- end
272
- OpenStudio::Workflow::Run.aasm.initial_state(:queued)
277
+ @current_state = :queued
273
278
 
274
- # Create a new event and add in the transitions
275
- new_event = OpenStudio::Workflow::Run.aasm.event(:step)
276
- event = OpenStudio::Workflow::Run.aasm.events[:step]
279
+ @transitions = @options[:transitions]
280
+ end
277
281
 
278
- # TODO: make a config option to not go to the error state. Useful to not error-state when testing
279
- event.options[:error] = 'step_error'
280
- @options[:transitions].each do |t|
281
- event.transitions(t)
282
- end
282
+ def next_state
283
+ @logger.info "Current state: '#{@current_state}'"
284
+ ns = @transitions.select{ |h| h[:from] == @current_state}.first[:to]
285
+ @logger.info "Next state will be: '#{ns}'"
286
+
287
+ # Set the next state before calling the method
288
+ @current_state = ns
283
289
 
284
- # Add in special event to error_out the state machine
285
- new_event = OpenStudio::Workflow::Run.aasm.event(:error_out)
286
- event = OpenStudio::Workflow::Run.aasm.events[:error_out]
287
- event.transitions(to: :errored)
290
+ # do not return anything, the step method uses the @current_state variable to call run_#{next_state}
288
291
  end
289
292
 
290
293
  # Get any options that may have been sent into the class defining the workflow step
291
294
  def get_job_options
292
295
  result = {}
293
- # if @options[:jobs].has_key?(state.current_state)
296
+ # if @options[:jobs].has_key?(@current_state)
294
297
  # logger.info "Retrieving job options from the @options array for #{state.current_state}"
295
- # result = @options[:jobs][state.current_state]
298
+ # result = @options[:jobs][@current_state]
296
299
  # end
297
300
 
298
301
  # result
@@ -19,6 +19,6 @@
19
19
 
20
20
  module OpenStudio
21
21
  module Workflow
22
- VERSION = '0.0.2'
22
+ VERSION = '0.0.3'
23
23
  end
24
24
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openstudio-workflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nicholas Long
@@ -39,47 +39,61 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: aasm
42
+ name: multi_json
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ~>
46
46
  - !ruby/object:Gem::Version
47
- version: 3.1.1
47
+ version: 1.10.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ~>
53
53
  - !ruby/object:Gem::Version
54
- version: 3.1.1
54
+ version: 1.10.0
55
55
  - !ruby/object:Gem::Dependency
56
- name: multi_json
56
+ name: colored
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ~>
60
60
  - !ruby/object:Gem::Version
61
- version: 1.10.0
61
+ version: '1.2'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ~>
67
67
  - !ruby/object:Gem::Version
68
- version: 1.10.0
68
+ version: '1.2'
69
69
  - !ruby/object:Gem::Dependency
70
- name: colored
70
+ name: facter
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ~>
74
74
  - !ruby/object:Gem::Version
75
- version: '1.2'
75
+ version: 2.0.2
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - ~>
81
81
  - !ruby/object:Gem::Version
82
- version: '1.2'
82
+ version: 2.0.2
83
+ - !ruby/object:Gem::Dependency
84
+ name: zip
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: 2.0.2
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: 2.0.2
83
97
  description: Run OpenStudio based simulations using EnergyPlus
84
98
  email:
85
99
  - nicholas.long@nrel.gov