openstudio-workflow 0.0.2 → 0.0.3

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