Class: OpenStudio::Workflow::Run

Inherits:
Object
  • Object
show all
Defined in:
lib/openstudio/workflow/run.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(osw_path, user_options = {}) ⇒ Run

Initialize a new run class

Parameters:

  • osw_path (String)

    the path to the OSW file to run. It is highly recommended that this be an absolute path, however if not it will be made absolute relative to the current working directory

  • user_options (Hash) (defaults to: {})

    ({}) A set of user-specified options that are used to override default behaviors.

Options Hash (user_options):

  • :cleanup (Hash)

    Remove unneccessary files during post processing, overrides OSW option if set, defaults to true

  • :debug (Hash)

    Print debugging messages, overrides OSW option if set, defaults to false

  • :energyplus_path (Hash)

    Specifies path to energyplus executable, defaults to empty

  • :fast (Hash)

    Speeds up workflow by skipping steps not needed for running simulations, defaults to false

  • :jobs (Hash)

    Simulation workflow, overrides jobs in OSW if set, defaults to default_jobs

  • :output_adapter (Hash)

    Output adapter to use, overrides output adapter in OSW if set, defaults to local adapter

  • :preserve_run_dir (Hash)

    Prevents run directory from being cleaned prior to run, overrides OSW option if set, defaults to false - DLM, Deprecate

  • :profile (Hash)

    Produce additional output for profiling simulations, defaults to false

  • :skip_energyplus_preprocess (Hash)

    Does not add add default output requests to EnergyPlus input if true, requests from reporting measures are added in either case, defaults to false

  • :skip_expand_objects (Hash)

    Skips the call to the EnergyPlus ExpandObjects program, defaults to false

  • :targets (Hash)

    Log targets to write to, defaults to standard out and run.log

  • :verify_osw (Hash)

    Check OSW for correctness, defaults to true

  • :weather_file (Hash)

    Initial weather file to load, overrides OSW option if set, defaults to empty



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/openstudio/workflow/run.rb', line 84

def initialize(osw_path, user_options = {})
  # DLM - what is final_message?
  @final_message = ''
  @current_state = nil
  @options = {}
  
  # 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
  # - analysis - the current OSA parsed as a Ruby Hash
  # - datapoint - the current OSD parsed as a Ruby Hash
  # - log_targets - IO devices that are being logged to
  # - logger - general logger
  # - model - the current OpenStudio Model object, updated after each step
  # - model_idf - the current EnergyPlus Workspace object, updated after each step
  # - openstudio_2 - true if we are running in OpenStudio 2.X environment
  # - osw_path - the path the OSW was loaded from as a string
  # - osw_dir - the directory the OSW was loaded from as a string
  # - output_attributes - added during simulation time
  # - results - objective function values
  # - root_dir - the root directory in the OSW as a string
  # - run_dir - the run directory for the simulation as a string
  # - runner - the current OSRunner object
  # - sql - the path to the current EnergyPlus SQL file as a string
  # - time_logger - logger for doing profiling - time to run each step will be captured in OSResult, deprecate
  # - wf - the path to the current weather file as a string, updated after each step
  # - workflow - the current OSW parsed as a Ruby Hash
  # - workflow_json - the current WorkflowJSON object        
  @registry = Registry.new

  openstudio_2 = false
  begin
    # OpenStudio 2.X test
    OpenStudio::WorkflowJSON.new
    openstudio_2 = true
  rescue NameError => e
  end
  @registry.register(:openstudio_2) { openstudio_2 }

  # get the input osw
  @input_adapter = OpenStudio::Workflow::InputAdapter::Local.new(osw_path)
  
  # DLM: need to check that we have correct permissions to all these paths
  @registry.register(:osw_path) { Pathname.new(@input_adapter.osw_path).realpath }
  @registry.register(:osw_dir) { Pathname.new(@input_adapter.osw_dir).realpath }
  @registry.register(:run_dir) { Pathname.new(@input_adapter.run_dir).cleanpath } # run dir might not yet exist, calling realpath will throw
  
  # get info to set up logging first in case of failures later
  @options[:debug] = @input_adapter.debug(user_options, false)
  @options[:preserve_run_dir] = @input_adapter.preserve_run_dir(user_options, false)
  @options[:skip_expand_objects] = @input_adapter.skip_expand_objects(user_options, false)
  @options[:skip_energyplus_preprocess] = @input_adapter.skip_energyplus_preprocess(user_options, false)
  @options[:profile] = @input_adapter.profile(user_options, false)
  
  # if running in osw dir, force preserve run dir
  if @registry[:osw_dir] == @registry[:run_dir]
    # force preserving the run directory
    @options[:preserve_run_dir] = true
  end

  # By default blow away the entire run directory every time and recreate it
  unless @options[:preserve_run_dir]
    if File.exist?(@registry[:run_dir])
      # logger is not initialized yet (it needs run dir to exist for log)
      puts "Removing existing run directory #{@registry[:run_dir]}" if @options[:debug]

      # DLM: this is dangerous, we are calling rm_rf on a user entered directory, need to check this first
      # TODO: Echoing Dan's comment
      FileUtils.rm_rf(@registry[:run_dir])
    end
  end
  FileUtils.mkdir_p(@registry[:run_dir])

  # set up logging after cleaning run dir
  if user_options[:targets]
    @options[:targets] = user_options[:targets]
  else
    # don't create these files unless we want to use them
    # DLM: TODO, make sure that run.log will be closed later 
    @options[:targets] = [STDOUT, File.open(File.join(@registry[:run_dir], 'run.log'), 'a')]
  end

  @registry.register(:log_targets) { @options[:targets] }
  @registry.register(:time_logger) { TimeLogger.new } if @options[:profile]

  # Initialize the MultiDelegator logger
  logger_level = @options[:debug] ? ::Logger::DEBUG : ::Logger::WARN
  @logger = ::Logger.new(MultiDelegator.delegate(:write, :close).to(*@options[:targets])) # * is the splat operator
  @logger.level = logger_level 
  @registry.register(:logger) { @logger }
  
  @logger.info "openstudio_2 = #{@registry[:openstudio_2]}"
  
  # get the output adapter
  default_output_adapter = OpenStudio::Workflow::OutputAdapter::Local.new(output_directory: @input_adapter.run_dir)
  @output_adapter = @input_adapter.output_adapter(user_options, default_output_adapter, @logger)

  # get the jobs
  default_jobs = OpenStudio::Workflow::Run.default_jobs
  @jobs = @input_adapter.jobs(user_options, default_jobs, @logger)

  # get other run options out of user_options and into permanent options 
  @options[:cleanup] = @input_adapter.cleanup(user_options, true)
  @options[:energyplus_path] = @input_adapter.energyplus_path(user_options, nil) 
  @options[:verify_osw] = @input_adapter.verify_osw(user_options, true)
  @options[:weather_file] = @input_adapter.weather_file(user_options, nil)
  @options[:fast] = @input_adapter.fast(user_options, false)

  openstudio_dir = "unknown"
  begin
    openstudio_dir = $OpenStudio_Dir
    if openstudio_dir.nil?
      openstudio_dir = OpenStudio::getOpenStudioModuleDirectory.to_s
    end
  rescue
  end
  @logger.info "openstudio_dir = #{openstudio_dir}"

  @logger.info "Initializing directory #{@registry[:run_dir]} for simulation with options #{@options}"

  # Define the state and transitions
  @current_state = :queued
end

Instance Attribute Details

#current_stateObject (readonly)

Returns the value of attribute current_state.



38
39
40
# File 'lib/openstudio/workflow/run.rb', line 38

def current_state
  @current_state
end

#final_messageObject (readonly)

Returns the value of attribute final_message.



36
37
38
# File 'lib/openstudio/workflow/run.rb', line 36

def final_message
  @final_message
end

#input_adapterObject (readonly)

Returns the value of attribute input_adapter.



34
35
36
# File 'lib/openstudio/workflow/run.rb', line 34

def input_adapter
  @input_adapter
end

#job_resultsObject (readonly)

Returns the value of attribute job_results.



37
38
39
# File 'lib/openstudio/workflow/run.rb', line 37

def job_results
  @job_results
end

#optionsObject (readonly)

Returns the value of attribute options.



33
34
35
# File 'lib/openstudio/workflow/run.rb', line 33

def options
  @options
end

#output_adapterObject (readonly)

Returns the value of attribute output_adapter.



35
36
37
# File 'lib/openstudio/workflow/run.rb', line 35

def output_adapter
  @output_adapter
end

#registryObject

Returns the value of attribute registry.



31
32
33
# File 'lib/openstudio/workflow/run.rb', line 31

def registry
  @registry
end

Class Method Details

.default_jobsObject

Define the default set of jobs. Note that the states of :queued of :finished need to exist for all job arrays.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/openstudio/workflow/run.rb', line 42

def self.default_jobs
  [
    { state: :queued, next_state: :initialization, options: { initial: true } },
    { state: :initialization, next_state: :os_measures, job: :RunInitialization,
      file: 'openstudio/workflow/jobs/run_initialization', options: {} },
    { state: :os_measures, next_state: :translator, job: :RunOpenStudioMeasures,
      file: 'openstudio/workflow/jobs/run_os_measures.rb', options: {} },
    { state: :translator, next_state: :ep_measures, job: :RunTranslation,
      file: 'openstudio/workflow/jobs/run_translation.rb', options: {} },
    { state: :ep_measures, next_state: :preprocess, job: :RunEnergyPlusMeasures,
      file: 'openstudio/workflow/jobs/run_ep_measures.rb', options: {} },
    { state: :preprocess, next_state: :simulation, job: :RunPreprocess,
      file: 'openstudio/workflow/jobs/run_preprocess.rb', options: {} },
    { state: :simulation, next_state: :reporting_measures, job: :RunEnergyPlus,
      file: 'openstudio/workflow/jobs/run_energyplus.rb', options: {} },
    { state: :reporting_measures, next_state: :postprocess, job: :RunReportingMeasures,
      file: 'openstudio/workflow/jobs/run_reporting_measures.rb', options: {} },
    { state: :postprocess, next_state: :finished, job: :RunPostprocess,
      file: 'openstudio/workflow/jobs/run_postprocess.rb', options: {} },
    { state: :finished },
    { state: :errored }
  ]
end

Instance Method Details

#runObject

TODO:

add a catch if any job fails

TODO:

make a block method to provide feedback

execute the workflow defined in the state object



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/openstudio/workflow/run.rb', line 210

def run
  @logger.info "Starting workflow in #{@registry[:run_dir]}"
  begin
    next_state
    while @current_state != :finished && @current_state != :errored
      #sleep 2
      step
    end

    if !@options[:fast]
      @logger.info 'Finished workflow - communicating results and zipping files'
      @output_adapter.communicate_results(@registry[:run_dir], @registry[:results])
    end
  rescue => e
    @logger.info "Error occurred during running with #{e.message}"
  ensure
    @logger.info 'Workflow complete'

    if @current_state == :errored
      @registry[:workflow_json].setCompletedStatus('Fail') if @registry[:workflow_json]
    else
      # completed status will already be set if workflow was halted
      if @registry[:workflow_json].completedStatus.empty?
        @registry[:workflow_json].setCompletedStatus('Success')
      else
        @current_state = :errored if @registry[:workflow_json].completedStatus.get == 'Fail'
      end
    end

    # save all files before calling output adapter
    @registry[:log_targets].each(&:flush)

    # save workflow with results
    if @registry[:workflow_json] and !@options[:fast]
      out_path = @registry[:workflow_json].absoluteOutPath
      @registry[:workflow_json].saveAs(out_path)
    end

    # Write out the TimeLogger to the filesystem
    @registry[:time_logger].save(File.join(@registry[:run_dir], 'profile.json')) if @registry[:time_logger]

    if @current_state == :errored
      @output_adapter.communicate_failure
    else
      @output_adapter.communicate_complete
    end

  end

  @current_state
end

#run_finished(_, _, _) ⇒ Object

Return the finished state and exit



294
295
296
297
298
# File 'lib/openstudio/workflow/run.rb', line 294

def run_finished(_, _, _)
  logger.info "Running #{__method__}"

  @current_state
end

#stepObject

Step through the states, if there is an error (e.g. exception) then go to error



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/openstudio/workflow/run.rb', line 264

def step
  step_instance = @jobs.find { |h| h[:state] == @current_state }
  require step_instance[:file]
  klass = OpenStudio::Workflow.new_class(step_instance[:job], @input_adapter, @output_adapter, @registry, @options)
  @output_adapter.communicate_transition("Starting state #{@current_state}", :state)
  state_return = klass.perform
  if state_return
    @output_adapter.communicate_transition("Returned from state #{@current_state} with message #{state_return}", :state)
  else
    @output_adapter.communicate_transition("Returned from state #{@current_state}", :state)
  end
  next_state
rescue => e
  step_error("#{e.message}:#{e.backtrace.join("\n")}")
end

#step_error(*args) ⇒ Object

Error handling for when there is an exception running any of the state transitions



282
283
284
285
286
287
288
289
290
# File 'lib/openstudio/workflow/run.rb', line 282

def step_error(*args)
  # Make sure to set the instance variable @error to true in order to stop the :step
  # event from being fired.
  @final_message = "Found error in state '#{@current_state}' with message #{args}}"
  @logger.error @final_message

  # transition to an error state
  @current_state = :errored
end