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 +4 -4
- data/CHANGELOG.md +7 -2
- data/lib/openstudio-workflow.rb +37 -5
- data/lib/openstudio/workflow/adapters/mongo.rb +1 -1
- data/lib/openstudio/workflow/jobs/lib/apply_measures.rb +0 -1
- data/lib/openstudio/workflow/jobs/run_energyplus/run_energyplus.rb +63 -6
- data/lib/openstudio/workflow/jobs/run_openstudio/run_openstudio.rb +25 -14
- data/lib/openstudio/workflow/jobs/run_postprocess/run_postprocess.rb +0 -6
- data/lib/openstudio/workflow/jobs/run_runmanager/run_runmanager.rb +4 -6
- data/lib/openstudio/workflow/jobs/run_xml/run_xml.rb +1 -1
- data/lib/openstudio/workflow/run.rb +77 -74
- data/lib/openstudio/workflow/version.rb +1 -1
- metadata +24 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8fa9aff18f17a837285bcc9bc72d8beff2b82028
|
4
|
+
data.tar.gz: 871e9aeffdd30c228cf804c758ea9a8918fa87ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
data/lib/openstudio-workflow.rb
CHANGED
@@ -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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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 = {})
|
@@ -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
|
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
|
-
|
58
|
-
|
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
|
-
|
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
|
76
|
-
|
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
|
-
|
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
|
-
|
201
|
-
|
202
|
-
|
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
|
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
|
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
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
49
|
+
warn "[Deprecation Warning] explicitly specifying states will no longer be required in 0.3.0. Method #{__method__}"
|
55
50
|
[
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
74
|
+
warn "[Deprecation Warning] explicitly specifying states will no longer be required in 0.3.0. Method #{__method__}"
|
81
75
|
[
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
106
|
-
|
107
|
-
|
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
|
-
|
112
|
-
|
113
|
-
|
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
|
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 @
|
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
|
-
|
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
|
-
@
|
191
|
-
@logger.error
|
196
|
+
@final_message = "Found error in state '#{@current_state}' with message #{args}}"
|
197
|
+
@logger.error @final_message
|
192
198
|
|
193
|
-
#
|
194
|
-
|
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
|
-
|
258
|
-
|
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
|
264
|
-
#
|
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
|
-
@
|
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
|
-
|
275
|
-
|
276
|
-
event = OpenStudio::Workflow::Run.aasm.events[:step]
|
279
|
+
@transitions = @options[:transitions]
|
280
|
+
end
|
277
281
|
|
278
|
-
|
279
|
-
|
280
|
-
@
|
281
|
-
|
282
|
-
|
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
|
-
#
|
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?(
|
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][
|
298
|
+
# result = @options[:jobs][@current_state]
|
296
299
|
# end
|
297
300
|
|
298
301
|
# result
|
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.
|
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:
|
42
|
+
name: multi_json
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ~>
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
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:
|
54
|
+
version: 1.10.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: colored
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ~>
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 1.
|
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.
|
68
|
+
version: '1.2'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: facter
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ~>
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
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:
|
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
|