openstudio-workflow 1.2.1 → 1.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +72 -72
- data/README.md +93 -48
- data/Rakefile +36 -36
- data/lib/openstudio-workflow.rb +49 -49
- data/lib/openstudio/workflow/adapters/input/local.rb +244 -240
- data/lib/openstudio/workflow/adapters/output/local.rb +95 -95
- data/lib/openstudio/workflow/adapters/output/socket.rb +91 -91
- data/lib/openstudio/workflow/adapters/output/web.rb +66 -66
- data/lib/openstudio/workflow/adapters/output_adapter.rb +147 -147
- data/lib/openstudio/workflow/job.rb +22 -22
- data/lib/openstudio/workflow/jobs/resources/monthly_report.idf +222 -222
- data/lib/openstudio/workflow/jobs/run_energyplus.rb +49 -49
- data/lib/openstudio/workflow/jobs/run_ep_measures.rb +55 -55
- data/lib/openstudio/workflow/jobs/run_initialization.rb +169 -167
- data/lib/openstudio/workflow/jobs/run_os_measures.rb +69 -69
- data/lib/openstudio/workflow/jobs/run_postprocess.rb +53 -53
- data/lib/openstudio/workflow/jobs/run_preprocess.rb +69 -69
- data/lib/openstudio/workflow/jobs/run_reporting_measures.rb +98 -98
- data/lib/openstudio/workflow/jobs/run_translation.rb +61 -61
- data/lib/openstudio/workflow/multi_delegator.rb +46 -46
- data/lib/openstudio/workflow/registry.rb +137 -137
- data/lib/openstudio/workflow/run.rb +299 -299
- data/lib/openstudio/workflow/time_logger.rb +53 -53
- data/lib/openstudio/workflow/util.rb +14 -14
- data/lib/openstudio/workflow/util/energyplus.rb +566 -564
- data/lib/openstudio/workflow/util/io.rb +33 -33
- data/lib/openstudio/workflow/util/measure.rb +588 -588
- data/lib/openstudio/workflow/util/model.rb +100 -100
- data/lib/openstudio/workflow/util/post_process.rb +187 -187
- data/lib/openstudio/workflow/util/weather_file.rb +108 -108
- data/lib/openstudio/workflow/version.rb +24 -24
- data/lib/openstudio/workflow_json.rb +426 -426
- data/lib/openstudio/workflow_runner.rb +233 -215
- metadata +3 -3
@@ -1,299 +1,299 @@
|
|
1
|
-
######################################################################
|
2
|
-
# Copyright (c) 2008-2014, Alliance for Sustainable Energy.
|
3
|
-
# All rights reserved.
|
4
|
-
#
|
5
|
-
# This library is free software; you can redistribute it and/or
|
6
|
-
# modify it under the terms of the GNU Lesser General Public
|
7
|
-
# License as published by the Free Software Foundation; either
|
8
|
-
# version 2.1 of the License, or (at your option) any later version.
|
9
|
-
#
|
10
|
-
# This library is distributed in the hope that it will be useful,
|
11
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
13
|
-
# Lesser General Public License for more details.
|
14
|
-
#
|
15
|
-
# You should have received a copy of the GNU Lesser General Public
|
16
|
-
# License along with this library; if not, write to the Free Software
|
17
|
-
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
18
|
-
######################################################################
|
19
|
-
|
20
|
-
require_relative 'registry'
|
21
|
-
require_relative 'adapters/input/local'
|
22
|
-
require_relative 'adapters/output/local'
|
23
|
-
|
24
|
-
require 'logger'
|
25
|
-
require 'pathname'
|
26
|
-
|
27
|
-
# Run Class for OpenStudio workflow. All comments here need some love, as well as the code itself
|
28
|
-
module OpenStudio
|
29
|
-
module Workflow
|
30
|
-
class Run
|
31
|
-
attr_accessor :registry
|
32
|
-
|
33
|
-
attr_reader :options
|
34
|
-
attr_reader :input_adapter
|
35
|
-
attr_reader :output_adapter
|
36
|
-
attr_reader :final_message
|
37
|
-
attr_reader :job_results
|
38
|
-
attr_reader :current_state
|
39
|
-
|
40
|
-
# Define the default set of jobs. Note that the states of :queued of :finished need to exist for all job arrays.
|
41
|
-
#
|
42
|
-
def self.default_jobs
|
43
|
-
[
|
44
|
-
{ state: :queued, next_state: :initialization, options: { initial: true } },
|
45
|
-
{ state: :initialization, next_state: :os_measures, job: :RunInitialization,
|
46
|
-
file: 'openstudio/workflow/jobs/run_initialization', options: {} },
|
47
|
-
{ state: :os_measures, next_state: :translator, job: :RunOpenStudioMeasures,
|
48
|
-
file: 'openstudio/workflow/jobs/run_os_measures.rb', options: {} },
|
49
|
-
{ state: :translator, next_state: :ep_measures, job: :RunTranslation,
|
50
|
-
file: 'openstudio/workflow/jobs/run_translation.rb', options: {} },
|
51
|
-
{ state: :ep_measures, next_state: :preprocess, job: :RunEnergyPlusMeasures,
|
52
|
-
file: 'openstudio/workflow/jobs/run_ep_measures.rb', options: {} },
|
53
|
-
{ state: :preprocess, next_state: :simulation, job: :RunPreprocess,
|
54
|
-
file: 'openstudio/workflow/jobs/run_preprocess.rb', options: {} },
|
55
|
-
{ state: :simulation, next_state: :reporting_measures, job: :RunEnergyPlus,
|
56
|
-
file: 'openstudio/workflow/jobs/run_energyplus.rb', options: {} },
|
57
|
-
{ state: :reporting_measures, next_state: :postprocess, job: :RunReportingMeasures,
|
58
|
-
file: 'openstudio/workflow/jobs/run_reporting_measures.rb', options: {} },
|
59
|
-
{ state: :postprocess, next_state: :finished, job: :RunPostprocess,
|
60
|
-
file: 'openstudio/workflow/jobs/run_postprocess.rb', options: {} },
|
61
|
-
{ state: :finished },
|
62
|
-
{ state: :errored }
|
63
|
-
]
|
64
|
-
end
|
65
|
-
|
66
|
-
# Initialize a new run class
|
67
|
-
#
|
68
|
-
# @param [String] osw_path the path to the OSW file to run. It is highly recommended that this be an absolute
|
69
|
-
# path, however if not it will be made absolute relative to the current working directory
|
70
|
-
# @param [Hash] user_options ({}) A set of user-specified options that are used to override default behaviors.
|
71
|
-
# @option user_options [Hash] :cleanup Remove unneccessary files during post processing, overrides OSW option if set, defaults to true
|
72
|
-
# @option user_options [Hash] :debug Print debugging messages, overrides OSW option if set, defaults to false
|
73
|
-
# @option user_options [Hash] :energyplus_path Specifies path to energyplus executable, defaults to empty
|
74
|
-
# @option user_options [Hash] :jobs Simulation workflow, overrides jobs in OSW if set, defaults to default_jobs
|
75
|
-
# @option user_options [Hash] :output_adapter Output adapter to use, overrides output adapter in OSW if set, defaults to local adapter
|
76
|
-
# @option user_options [Hash] :preserve_run_dir Prevents run directory from being cleaned prior to run, overrides OSW option if set, defaults to false - DLM, Deprecate
|
77
|
-
# @option user_options [Hash] :profile Produce additional output for profiling simulations, defaults to false
|
78
|
-
# @option user_options [Hash] :targets Log targets to write to, defaults to standard out and run.log
|
79
|
-
# @option user_options [Hash] :verify_osw Check OSW for correctness, defaults to true
|
80
|
-
# @option user_options [Hash] :weather_file Initial weather file to load, overrides OSW option if set, defaults to empty
|
81
|
-
def initialize(osw_path, user_options = {})
|
82
|
-
# DLM - what is final_message?
|
83
|
-
@final_message = ''
|
84
|
-
@current_state = nil
|
85
|
-
@options = {}
|
86
|
-
|
87
|
-
# Registry is a large hash of objects that are populated during the run, the number of objects in the registry should be reduced over time, especially if the functionality can be added to the WorkflowJSON class
|
88
|
-
# - analysis - the current OSA parsed as a Ruby Hash
|
89
|
-
# - datapoint - the current OSD parsed as a Ruby Hash
|
90
|
-
# - log_targets - IO devices that are being logged to
|
91
|
-
# - logger - general logger
|
92
|
-
# - model - the current OpenStudio Model object, updated after each step
|
93
|
-
# - model_idf - the current EnergyPlus Workspace object, updated after each step
|
94
|
-
# - openstudio_2 - true if we are running in OpenStudio 2.X environment
|
95
|
-
# - osw_path - the path the OSW was loaded from as a string
|
96
|
-
# - osw_dir - the directory the OSW was loaded from as a string
|
97
|
-
# - output_attributes - added during simulation time
|
98
|
-
# - results - objective function values
|
99
|
-
# - root_dir - the root directory in the OSW as a string
|
100
|
-
# - run_dir - the run directory for the simulation as a string
|
101
|
-
# - runner - the current OSRunner object
|
102
|
-
# - sql - the path to the current EnergyPlus SQL file as a string
|
103
|
-
# - time_logger - logger for doing profiling - time to run each step will be captured in OSResult, deprecate
|
104
|
-
# - wf - the path to the current weather file as a string, updated after each step
|
105
|
-
# - workflow - the current OSW parsed as a Ruby Hash
|
106
|
-
# - workflow_json - the current WorkflowJSON object
|
107
|
-
@registry = Registry.new
|
108
|
-
|
109
|
-
openstudio_2 = false
|
110
|
-
begin
|
111
|
-
# OpenStudio 2.X test
|
112
|
-
OpenStudio::WorkflowJSON.new
|
113
|
-
openstudio_2 = true
|
114
|
-
rescue NameError => e
|
115
|
-
end
|
116
|
-
@registry.register(:openstudio_2) { openstudio_2 }
|
117
|
-
|
118
|
-
# get the input osw
|
119
|
-
@input_adapter = OpenStudio::Workflow::InputAdapter::Local.new(osw_path)
|
120
|
-
|
121
|
-
# DLM: need to check that we have correct permissions to all these paths
|
122
|
-
@registry.register(:osw_path) { Pathname.new(@input_adapter.osw_path).realpath }
|
123
|
-
@registry.register(:osw_dir) { Pathname.new(@input_adapter.osw_dir).realpath }
|
124
|
-
@registry.register(:run_dir) { Pathname.new(@input_adapter.run_dir).cleanpath } # run dir might not yet exist, calling realpath will throw
|
125
|
-
|
126
|
-
# get info to set up logging first in case of failures later
|
127
|
-
@options[:debug] = @input_adapter.debug(user_options, false)
|
128
|
-
@options[:preserve_run_dir] = @input_adapter.preserve_run_dir(user_options, false)
|
129
|
-
@options[:profile] = @input_adapter.profile(user_options, false)
|
130
|
-
|
131
|
-
# if running in osw dir, force preserve run dir
|
132
|
-
if @registry[:osw_dir] == @registry[:run_dir]
|
133
|
-
# force preserving the run directory
|
134
|
-
@options[:preserve_run_dir] = true
|
135
|
-
end
|
136
|
-
|
137
|
-
# By default blow away the entire run directory every time and recreate it
|
138
|
-
unless @options[:preserve_run_dir]
|
139
|
-
if File.exist?(@registry[:run_dir])
|
140
|
-
# logger is not initialized yet (it needs run dir to exist for log)
|
141
|
-
puts "Removing existing run directory #{@registry[:run_dir]}" if @options[:debug]
|
142
|
-
|
143
|
-
# DLM: this is dangerous, we are calling rm_rf on a user entered directory, need to check this first
|
144
|
-
# TODO: Echoing Dan's comment
|
145
|
-
FileUtils.rm_rf(@registry[:run_dir])
|
146
|
-
end
|
147
|
-
end
|
148
|
-
FileUtils.mkdir_p(@registry[:run_dir])
|
149
|
-
|
150
|
-
# set up logging after cleaning run dir
|
151
|
-
if user_options[:targets]
|
152
|
-
@options[:targets] = user_options[:targets]
|
153
|
-
else
|
154
|
-
# don't create these files unless we want to use them
|
155
|
-
# DLM: TODO, make sure that run.log will be closed later
|
156
|
-
@options[:targets] = [STDOUT, File.open(File.join(@registry[:run_dir], 'run.log'), 'a')]
|
157
|
-
end
|
158
|
-
|
159
|
-
@registry.register(:log_targets) { @options[:targets] }
|
160
|
-
@registry.register(:time_logger) { TimeLogger.new } if @options[:profile]
|
161
|
-
|
162
|
-
# Initialize the MultiDelegator logger
|
163
|
-
logger_level = @options[:debug] ? ::Logger::DEBUG : ::Logger::WARN
|
164
|
-
@logger = ::Logger.new(MultiDelegator.delegate(:write, :close).to(*@options[:targets])) # * is the splat operator
|
165
|
-
@logger.level = logger_level
|
166
|
-
@registry.register(:logger) { @logger }
|
167
|
-
|
168
|
-
@logger.info "openstudio_2 = #{@registry[:openstudio_2]}"
|
169
|
-
|
170
|
-
# get the output adapter
|
171
|
-
default_output_adapter = OpenStudio::Workflow::OutputAdapter::Local.new(output_directory: @input_adapter.run_dir)
|
172
|
-
@output_adapter = @input_adapter.output_adapter(user_options, default_output_adapter, @logger)
|
173
|
-
|
174
|
-
# get the jobs
|
175
|
-
default_jobs = OpenStudio::Workflow::Run.default_jobs
|
176
|
-
@jobs = @input_adapter.jobs(user_options, default_jobs, @logger)
|
177
|
-
|
178
|
-
# get other run options out of user_options and into permanent options
|
179
|
-
@options[:cleanup] = @input_adapter.cleanup(user_options, true)
|
180
|
-
@options[:energyplus_path] = @input_adapter.energyplus_path(user_options, nil)
|
181
|
-
@options[:verify_osw] = @input_adapter.verify_osw(user_options, true)
|
182
|
-
@options[:weather_file] = @input_adapter.weather_file(user_options, nil)
|
183
|
-
|
184
|
-
openstudio_dir = "unknown"
|
185
|
-
begin
|
186
|
-
openstudio_dir = $OpenStudio_Dir
|
187
|
-
if openstudio_dir.nil?
|
188
|
-
openstudio_dir = OpenStudio::getOpenStudioModuleDirectory.to_s
|
189
|
-
end
|
190
|
-
rescue
|
191
|
-
end
|
192
|
-
@logger.info "openstudio_dir = #{openstudio_dir}"
|
193
|
-
|
194
|
-
@logger.info "Initializing directory #{@registry[:run_dir]} for simulation with options #{@options}"
|
195
|
-
|
196
|
-
# Define the state and transitions
|
197
|
-
@current_state = :queued
|
198
|
-
end
|
199
|
-
|
200
|
-
# execute the workflow defined in the state object
|
201
|
-
#
|
202
|
-
# @todo add a catch if any job fails
|
203
|
-
# @todo make a block method to provide feedback
|
204
|
-
def run
|
205
|
-
@logger.info "Starting workflow in #{@registry[:run_dir]}"
|
206
|
-
begin
|
207
|
-
next_state
|
208
|
-
while @current_state != :finished && @current_state != :errored
|
209
|
-
sleep 2
|
210
|
-
step
|
211
|
-
end
|
212
|
-
|
213
|
-
@logger.info 'Finished workflow - communicating results and zipping files'
|
214
|
-
@output_adapter.communicate_results(@registry[:run_dir], @registry[:results])
|
215
|
-
rescue => e
|
216
|
-
@logger.info "Error occurred during running with #{e.message}"
|
217
|
-
ensure
|
218
|
-
@logger.info 'Workflow complete'
|
219
|
-
|
220
|
-
if @current_state == :errored
|
221
|
-
@registry[:workflow_json].setCompletedStatus('Fail') if @registry[:workflow_json]
|
222
|
-
else
|
223
|
-
@registry[:workflow_json].setCompletedStatus('Success')
|
224
|
-
end
|
225
|
-
|
226
|
-
# save all files before calling output adapter
|
227
|
-
@registry[:log_targets].each(&:flush)
|
228
|
-
|
229
|
-
# save workflow with results
|
230
|
-
if @registry[:workflow_json]
|
231
|
-
out_path = @registry[:workflow_json].absoluteOutPath
|
232
|
-
@registry[:workflow_json].saveAs(out_path)
|
233
|
-
end
|
234
|
-
|
235
|
-
# Write out the TimeLogger to the filesystem
|
236
|
-
@registry[:time_logger].save(File.join(@registry[:run_dir], 'profile.json')) if @registry[:time_logger]
|
237
|
-
|
238
|
-
if @current_state == :errored
|
239
|
-
@output_adapter.communicate_failure
|
240
|
-
else
|
241
|
-
@output_adapter.communicate_complete
|
242
|
-
end
|
243
|
-
|
244
|
-
end
|
245
|
-
|
246
|
-
@current_state
|
247
|
-
end
|
248
|
-
|
249
|
-
# Step through the states, if there is an error (e.g. exception) then go to error
|
250
|
-
#
|
251
|
-
def step
|
252
|
-
step_instance = @jobs.find { |h| h[:state] == @current_state }
|
253
|
-
require step_instance[:file]
|
254
|
-
klass = OpenStudio::Workflow.new_class(step_instance[:job], @input_adapter, @output_adapter, @registry, @options)
|
255
|
-
@output_adapter.communicate_transition("Starting state #{@current_state}", :state)
|
256
|
-
state_return = klass.perform
|
257
|
-
if state_return
|
258
|
-
@output_adapter.communicate_transition("Returned from state #{@current_state} with message #{state_return}", :state)
|
259
|
-
else
|
260
|
-
@output_adapter.communicate_transition("Returned from state #{@current_state}", :state)
|
261
|
-
end
|
262
|
-
next_state
|
263
|
-
rescue => e
|
264
|
-
step_error("#{e.message}:#{e.backtrace.join("\n")}")
|
265
|
-
end
|
266
|
-
|
267
|
-
# Error handling for when there is an exception running any of the state transitions
|
268
|
-
#
|
269
|
-
def step_error(*args)
|
270
|
-
# Make sure to set the instance variable @error to true in order to stop the :step
|
271
|
-
# event from being fired.
|
272
|
-
@final_message = "Found error in state '#{@current_state}' with message #{args}}"
|
273
|
-
@logger.error @final_message
|
274
|
-
|
275
|
-
# transition to an error state
|
276
|
-
@current_state = :errored
|
277
|
-
end
|
278
|
-
|
279
|
-
# Return the finished state and exit
|
280
|
-
#
|
281
|
-
def run_finished(_, _, _)
|
282
|
-
logger.info "Running #{__method__}"
|
283
|
-
|
284
|
-
@current_state
|
285
|
-
end
|
286
|
-
|
287
|
-
private
|
288
|
-
|
289
|
-
# Advance the @current_state to the next state
|
290
|
-
#
|
291
|
-
def next_state
|
292
|
-
@logger.info "Current state: '#{@current_state}'"
|
293
|
-
ns = @jobs.find { |h| h[:state] == @current_state }[:next_state]
|
294
|
-
@logger.info "Next state will be: '#{ns}'"
|
295
|
-
@current_state = ns
|
296
|
-
end
|
297
|
-
end
|
298
|
-
end
|
299
|
-
end
|
1
|
+
######################################################################
|
2
|
+
# Copyright (c) 2008-2014, Alliance for Sustainable Energy.
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# This library is free software; you can redistribute it and/or
|
6
|
+
# modify it under the terms of the GNU Lesser General Public
|
7
|
+
# License as published by the Free Software Foundation; either
|
8
|
+
# version 2.1 of the License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This library is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
13
|
+
# Lesser General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Lesser General Public
|
16
|
+
# License along with this library; if not, write to the Free Software
|
17
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
18
|
+
######################################################################
|
19
|
+
|
20
|
+
require_relative 'registry'
|
21
|
+
require_relative 'adapters/input/local'
|
22
|
+
require_relative 'adapters/output/local'
|
23
|
+
|
24
|
+
require 'logger'
|
25
|
+
require 'pathname'
|
26
|
+
|
27
|
+
# Run Class for OpenStudio workflow. All comments here need some love, as well as the code itself
|
28
|
+
module OpenStudio
|
29
|
+
module Workflow
|
30
|
+
class Run
|
31
|
+
attr_accessor :registry
|
32
|
+
|
33
|
+
attr_reader :options
|
34
|
+
attr_reader :input_adapter
|
35
|
+
attr_reader :output_adapter
|
36
|
+
attr_reader :final_message
|
37
|
+
attr_reader :job_results
|
38
|
+
attr_reader :current_state
|
39
|
+
|
40
|
+
# Define the default set of jobs. Note that the states of :queued of :finished need to exist for all job arrays.
|
41
|
+
#
|
42
|
+
def self.default_jobs
|
43
|
+
[
|
44
|
+
{ state: :queued, next_state: :initialization, options: { initial: true } },
|
45
|
+
{ state: :initialization, next_state: :os_measures, job: :RunInitialization,
|
46
|
+
file: 'openstudio/workflow/jobs/run_initialization', options: {} },
|
47
|
+
{ state: :os_measures, next_state: :translator, job: :RunOpenStudioMeasures,
|
48
|
+
file: 'openstudio/workflow/jobs/run_os_measures.rb', options: {} },
|
49
|
+
{ state: :translator, next_state: :ep_measures, job: :RunTranslation,
|
50
|
+
file: 'openstudio/workflow/jobs/run_translation.rb', options: {} },
|
51
|
+
{ state: :ep_measures, next_state: :preprocess, job: :RunEnergyPlusMeasures,
|
52
|
+
file: 'openstudio/workflow/jobs/run_ep_measures.rb', options: {} },
|
53
|
+
{ state: :preprocess, next_state: :simulation, job: :RunPreprocess,
|
54
|
+
file: 'openstudio/workflow/jobs/run_preprocess.rb', options: {} },
|
55
|
+
{ state: :simulation, next_state: :reporting_measures, job: :RunEnergyPlus,
|
56
|
+
file: 'openstudio/workflow/jobs/run_energyplus.rb', options: {} },
|
57
|
+
{ state: :reporting_measures, next_state: :postprocess, job: :RunReportingMeasures,
|
58
|
+
file: 'openstudio/workflow/jobs/run_reporting_measures.rb', options: {} },
|
59
|
+
{ state: :postprocess, next_state: :finished, job: :RunPostprocess,
|
60
|
+
file: 'openstudio/workflow/jobs/run_postprocess.rb', options: {} },
|
61
|
+
{ state: :finished },
|
62
|
+
{ state: :errored }
|
63
|
+
]
|
64
|
+
end
|
65
|
+
|
66
|
+
# Initialize a new run class
|
67
|
+
#
|
68
|
+
# @param [String] osw_path the path to the OSW file to run. It is highly recommended that this be an absolute
|
69
|
+
# path, however if not it will be made absolute relative to the current working directory
|
70
|
+
# @param [Hash] user_options ({}) A set of user-specified options that are used to override default behaviors.
|
71
|
+
# @option user_options [Hash] :cleanup Remove unneccessary files during post processing, overrides OSW option if set, defaults to true
|
72
|
+
# @option user_options [Hash] :debug Print debugging messages, overrides OSW option if set, defaults to false
|
73
|
+
# @option user_options [Hash] :energyplus_path Specifies path to energyplus executable, defaults to empty
|
74
|
+
# @option user_options [Hash] :jobs Simulation workflow, overrides jobs in OSW if set, defaults to default_jobs
|
75
|
+
# @option user_options [Hash] :output_adapter Output adapter to use, overrides output adapter in OSW if set, defaults to local adapter
|
76
|
+
# @option user_options [Hash] :preserve_run_dir Prevents run directory from being cleaned prior to run, overrides OSW option if set, defaults to false - DLM, Deprecate
|
77
|
+
# @option user_options [Hash] :profile Produce additional output for profiling simulations, defaults to false
|
78
|
+
# @option user_options [Hash] :targets Log targets to write to, defaults to standard out and run.log
|
79
|
+
# @option user_options [Hash] :verify_osw Check OSW for correctness, defaults to true
|
80
|
+
# @option user_options [Hash] :weather_file Initial weather file to load, overrides OSW option if set, defaults to empty
|
81
|
+
def initialize(osw_path, user_options = {})
|
82
|
+
# DLM - what is final_message?
|
83
|
+
@final_message = ''
|
84
|
+
@current_state = nil
|
85
|
+
@options = {}
|
86
|
+
|
87
|
+
# Registry is a large hash of objects that are populated during the run, the number of objects in the registry should be reduced over time, especially if the functionality can be added to the WorkflowJSON class
|
88
|
+
# - analysis - the current OSA parsed as a Ruby Hash
|
89
|
+
# - datapoint - the current OSD parsed as a Ruby Hash
|
90
|
+
# - log_targets - IO devices that are being logged to
|
91
|
+
# - logger - general logger
|
92
|
+
# - model - the current OpenStudio Model object, updated after each step
|
93
|
+
# - model_idf - the current EnergyPlus Workspace object, updated after each step
|
94
|
+
# - openstudio_2 - true if we are running in OpenStudio 2.X environment
|
95
|
+
# - osw_path - the path the OSW was loaded from as a string
|
96
|
+
# - osw_dir - the directory the OSW was loaded from as a string
|
97
|
+
# - output_attributes - added during simulation time
|
98
|
+
# - results - objective function values
|
99
|
+
# - root_dir - the root directory in the OSW as a string
|
100
|
+
# - run_dir - the run directory for the simulation as a string
|
101
|
+
# - runner - the current OSRunner object
|
102
|
+
# - sql - the path to the current EnergyPlus SQL file as a string
|
103
|
+
# - time_logger - logger for doing profiling - time to run each step will be captured in OSResult, deprecate
|
104
|
+
# - wf - the path to the current weather file as a string, updated after each step
|
105
|
+
# - workflow - the current OSW parsed as a Ruby Hash
|
106
|
+
# - workflow_json - the current WorkflowJSON object
|
107
|
+
@registry = Registry.new
|
108
|
+
|
109
|
+
openstudio_2 = false
|
110
|
+
begin
|
111
|
+
# OpenStudio 2.X test
|
112
|
+
OpenStudio::WorkflowJSON.new
|
113
|
+
openstudio_2 = true
|
114
|
+
rescue NameError => e
|
115
|
+
end
|
116
|
+
@registry.register(:openstudio_2) { openstudio_2 }
|
117
|
+
|
118
|
+
# get the input osw
|
119
|
+
@input_adapter = OpenStudio::Workflow::InputAdapter::Local.new(osw_path)
|
120
|
+
|
121
|
+
# DLM: need to check that we have correct permissions to all these paths
|
122
|
+
@registry.register(:osw_path) { Pathname.new(@input_adapter.osw_path).realpath }
|
123
|
+
@registry.register(:osw_dir) { Pathname.new(@input_adapter.osw_dir).realpath }
|
124
|
+
@registry.register(:run_dir) { Pathname.new(@input_adapter.run_dir).cleanpath } # run dir might not yet exist, calling realpath will throw
|
125
|
+
|
126
|
+
# get info to set up logging first in case of failures later
|
127
|
+
@options[:debug] = @input_adapter.debug(user_options, false)
|
128
|
+
@options[:preserve_run_dir] = @input_adapter.preserve_run_dir(user_options, false)
|
129
|
+
@options[:profile] = @input_adapter.profile(user_options, false)
|
130
|
+
|
131
|
+
# if running in osw dir, force preserve run dir
|
132
|
+
if @registry[:osw_dir] == @registry[:run_dir]
|
133
|
+
# force preserving the run directory
|
134
|
+
@options[:preserve_run_dir] = true
|
135
|
+
end
|
136
|
+
|
137
|
+
# By default blow away the entire run directory every time and recreate it
|
138
|
+
unless @options[:preserve_run_dir]
|
139
|
+
if File.exist?(@registry[:run_dir])
|
140
|
+
# logger is not initialized yet (it needs run dir to exist for log)
|
141
|
+
puts "Removing existing run directory #{@registry[:run_dir]}" if @options[:debug]
|
142
|
+
|
143
|
+
# DLM: this is dangerous, we are calling rm_rf on a user entered directory, need to check this first
|
144
|
+
# TODO: Echoing Dan's comment
|
145
|
+
FileUtils.rm_rf(@registry[:run_dir])
|
146
|
+
end
|
147
|
+
end
|
148
|
+
FileUtils.mkdir_p(@registry[:run_dir])
|
149
|
+
|
150
|
+
# set up logging after cleaning run dir
|
151
|
+
if user_options[:targets]
|
152
|
+
@options[:targets] = user_options[:targets]
|
153
|
+
else
|
154
|
+
# don't create these files unless we want to use them
|
155
|
+
# DLM: TODO, make sure that run.log will be closed later
|
156
|
+
@options[:targets] = [STDOUT, File.open(File.join(@registry[:run_dir], 'run.log'), 'a')]
|
157
|
+
end
|
158
|
+
|
159
|
+
@registry.register(:log_targets) { @options[:targets] }
|
160
|
+
@registry.register(:time_logger) { TimeLogger.new } if @options[:profile]
|
161
|
+
|
162
|
+
# Initialize the MultiDelegator logger
|
163
|
+
logger_level = @options[:debug] ? ::Logger::DEBUG : ::Logger::WARN
|
164
|
+
@logger = ::Logger.new(MultiDelegator.delegate(:write, :close).to(*@options[:targets])) # * is the splat operator
|
165
|
+
@logger.level = logger_level
|
166
|
+
@registry.register(:logger) { @logger }
|
167
|
+
|
168
|
+
@logger.info "openstudio_2 = #{@registry[:openstudio_2]}"
|
169
|
+
|
170
|
+
# get the output adapter
|
171
|
+
default_output_adapter = OpenStudio::Workflow::OutputAdapter::Local.new(output_directory: @input_adapter.run_dir)
|
172
|
+
@output_adapter = @input_adapter.output_adapter(user_options, default_output_adapter, @logger)
|
173
|
+
|
174
|
+
# get the jobs
|
175
|
+
default_jobs = OpenStudio::Workflow::Run.default_jobs
|
176
|
+
@jobs = @input_adapter.jobs(user_options, default_jobs, @logger)
|
177
|
+
|
178
|
+
# get other run options out of user_options and into permanent options
|
179
|
+
@options[:cleanup] = @input_adapter.cleanup(user_options, true)
|
180
|
+
@options[:energyplus_path] = @input_adapter.energyplus_path(user_options, nil)
|
181
|
+
@options[:verify_osw] = @input_adapter.verify_osw(user_options, true)
|
182
|
+
@options[:weather_file] = @input_adapter.weather_file(user_options, nil)
|
183
|
+
|
184
|
+
openstudio_dir = "unknown"
|
185
|
+
begin
|
186
|
+
openstudio_dir = $OpenStudio_Dir
|
187
|
+
if openstudio_dir.nil?
|
188
|
+
openstudio_dir = OpenStudio::getOpenStudioModuleDirectory.to_s
|
189
|
+
end
|
190
|
+
rescue
|
191
|
+
end
|
192
|
+
@logger.info "openstudio_dir = #{openstudio_dir}"
|
193
|
+
|
194
|
+
@logger.info "Initializing directory #{@registry[:run_dir]} for simulation with options #{@options}"
|
195
|
+
|
196
|
+
# Define the state and transitions
|
197
|
+
@current_state = :queued
|
198
|
+
end
|
199
|
+
|
200
|
+
# execute the workflow defined in the state object
|
201
|
+
#
|
202
|
+
# @todo add a catch if any job fails
|
203
|
+
# @todo make a block method to provide feedback
|
204
|
+
def run
|
205
|
+
@logger.info "Starting workflow in #{@registry[:run_dir]}"
|
206
|
+
begin
|
207
|
+
next_state
|
208
|
+
while @current_state != :finished && @current_state != :errored
|
209
|
+
sleep 2
|
210
|
+
step
|
211
|
+
end
|
212
|
+
|
213
|
+
@logger.info 'Finished workflow - communicating results and zipping files'
|
214
|
+
@output_adapter.communicate_results(@registry[:run_dir], @registry[:results])
|
215
|
+
rescue => e
|
216
|
+
@logger.info "Error occurred during running with #{e.message}"
|
217
|
+
ensure
|
218
|
+
@logger.info 'Workflow complete'
|
219
|
+
|
220
|
+
if @current_state == :errored
|
221
|
+
@registry[:workflow_json].setCompletedStatus('Fail') if @registry[:workflow_json]
|
222
|
+
else
|
223
|
+
@registry[:workflow_json].setCompletedStatus('Success')
|
224
|
+
end
|
225
|
+
|
226
|
+
# save all files before calling output adapter
|
227
|
+
@registry[:log_targets].each(&:flush)
|
228
|
+
|
229
|
+
# save workflow with results
|
230
|
+
if @registry[:workflow_json]
|
231
|
+
out_path = @registry[:workflow_json].absoluteOutPath
|
232
|
+
@registry[:workflow_json].saveAs(out_path)
|
233
|
+
end
|
234
|
+
|
235
|
+
# Write out the TimeLogger to the filesystem
|
236
|
+
@registry[:time_logger].save(File.join(@registry[:run_dir], 'profile.json')) if @registry[:time_logger]
|
237
|
+
|
238
|
+
if @current_state == :errored
|
239
|
+
@output_adapter.communicate_failure
|
240
|
+
else
|
241
|
+
@output_adapter.communicate_complete
|
242
|
+
end
|
243
|
+
|
244
|
+
end
|
245
|
+
|
246
|
+
@current_state
|
247
|
+
end
|
248
|
+
|
249
|
+
# Step through the states, if there is an error (e.g. exception) then go to error
|
250
|
+
#
|
251
|
+
def step
|
252
|
+
step_instance = @jobs.find { |h| h[:state] == @current_state }
|
253
|
+
require step_instance[:file]
|
254
|
+
klass = OpenStudio::Workflow.new_class(step_instance[:job], @input_adapter, @output_adapter, @registry, @options)
|
255
|
+
@output_adapter.communicate_transition("Starting state #{@current_state}", :state)
|
256
|
+
state_return = klass.perform
|
257
|
+
if state_return
|
258
|
+
@output_adapter.communicate_transition("Returned from state #{@current_state} with message #{state_return}", :state)
|
259
|
+
else
|
260
|
+
@output_adapter.communicate_transition("Returned from state #{@current_state}", :state)
|
261
|
+
end
|
262
|
+
next_state
|
263
|
+
rescue => e
|
264
|
+
step_error("#{e.message}:#{e.backtrace.join("\n")}")
|
265
|
+
end
|
266
|
+
|
267
|
+
# Error handling for when there is an exception running any of the state transitions
|
268
|
+
#
|
269
|
+
def step_error(*args)
|
270
|
+
# Make sure to set the instance variable @error to true in order to stop the :step
|
271
|
+
# event from being fired.
|
272
|
+
@final_message = "Found error in state '#{@current_state}' with message #{args}}"
|
273
|
+
@logger.error @final_message
|
274
|
+
|
275
|
+
# transition to an error state
|
276
|
+
@current_state = :errored
|
277
|
+
end
|
278
|
+
|
279
|
+
# Return the finished state and exit
|
280
|
+
#
|
281
|
+
def run_finished(_, _, _)
|
282
|
+
logger.info "Running #{__method__}"
|
283
|
+
|
284
|
+
@current_state
|
285
|
+
end
|
286
|
+
|
287
|
+
private
|
288
|
+
|
289
|
+
# Advance the @current_state to the next state
|
290
|
+
#
|
291
|
+
def next_state
|
292
|
+
@logger.info "Current state: '#{@current_state}'"
|
293
|
+
ns = @jobs.find { |h| h[:state] == @current_state }[:next_state]
|
294
|
+
@logger.info "Next state will be: '#{ns}'"
|
295
|
+
@current_state = ns
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|