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