openstudio-workflow 1.3.4 → 1.3.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +89 -77
  3. data/README.md +67 -93
  4. data/Rakefile +36 -36
  5. data/lib/openstudio-workflow.rb +65 -65
  6. data/lib/openstudio/workflow/adapters/input/local.rb +311 -324
  7. data/lib/openstudio/workflow/adapters/output/local.rb +158 -161
  8. data/lib/openstudio/workflow/adapters/output/socket.rb +106 -107
  9. data/lib/openstudio/workflow/adapters/output/web.rb +82 -82
  10. data/lib/openstudio/workflow/adapters/output_adapter.rb +163 -163
  11. data/lib/openstudio/workflow/job.rb +57 -57
  12. data/lib/openstudio/workflow/jobs/resources/monthly_report.idf +222 -222
  13. data/lib/openstudio/workflow/jobs/run_energyplus.rb +70 -70
  14. data/lib/openstudio/workflow/jobs/run_ep_measures.rb +73 -73
  15. data/lib/openstudio/workflow/jobs/run_initialization.rb +203 -203
  16. data/lib/openstudio/workflow/jobs/run_os_measures.rb +89 -89
  17. data/lib/openstudio/workflow/jobs/run_postprocess.rb +73 -73
  18. data/lib/openstudio/workflow/jobs/run_preprocess.rb +104 -104
  19. data/lib/openstudio/workflow/jobs/run_reporting_measures.rb +118 -118
  20. data/lib/openstudio/workflow/jobs/run_translation.rb +84 -84
  21. data/lib/openstudio/workflow/multi_delegator.rb +62 -62
  22. data/lib/openstudio/workflow/registry.rb +172 -172
  23. data/lib/openstudio/workflow/run.rb +342 -328
  24. data/lib/openstudio/workflow/time_logger.rb +96 -96
  25. data/lib/openstudio/workflow/util.rb +49 -49
  26. data/lib/openstudio/workflow/util/energyplus.rb +575 -605
  27. data/lib/openstudio/workflow/util/io.rb +68 -68
  28. data/lib/openstudio/workflow/util/measure.rb +658 -650
  29. data/lib/openstudio/workflow/util/model.rb +151 -151
  30. data/lib/openstudio/workflow/util/post_process.rb +235 -238
  31. data/lib/openstudio/workflow/util/weather_file.rb +143 -143
  32. data/lib/openstudio/workflow/version.rb +40 -40
  33. data/lib/openstudio/workflow_json.rb +475 -476
  34. data/lib/openstudio/workflow_runner.rb +263 -268
  35. metadata +24 -24
@@ -1,172 +1,172 @@
1
- # *******************************************************************************
2
- # OpenStudio(R), Copyright (c) 2008-2018, Alliance for Sustainable Energy, LLC.
3
- # All rights reserved.
4
- # Redistribution and use in source and binary forms, with or without
5
- # modification, are permitted provided that the following conditions are met:
6
- #
7
- # (1) Redistributions of source code must retain the above copyright notice,
8
- # this list of conditions and the following disclaimer.
9
- #
10
- # (2) Redistributions in binary form must reproduce the above copyright notice,
11
- # this list of conditions and the following disclaimer in the documentation
12
- # and/or other materials provided with the distribution.
13
- #
14
- # (3) Neither the name of the copyright holder nor the names of any contributors
15
- # may be used to endorse or promote products derived from this software without
16
- # specific prior written permission from the respective party.
17
- #
18
- # (4) Other than as required in clauses (1) and (2), distributions in any form
19
- # of modifications or other derivative works may not use the "OpenStudio"
20
- # trademark, "OS", "os", or any other confusingly similar designation without
21
- # specific prior written permission from Alliance for Sustainable Energy, LLC.
22
- #
23
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26
- # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER, THE UNITED STATES
27
- # GOVERNMENT, OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28
- # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29
- # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30
- # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31
- # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32
- # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
33
- # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
- # *******************************************************************************
35
-
36
- module OpenStudio
37
- module Workflow
38
- # Registers objects in a single place. Based on Hashicorps's Vagrant Registry class
39
- #
40
- # This allows certain components (such as models, weather files, proxy settings, etc.) to be registered as blocks
41
- # and lazily updated. This allows for the state of various objects to be updated through evaluation of or
42
- # overwriting the key. An instance of this class is passed between jobs to allow for highly flexible workflow
43
- # definitions. Note that hashes can be passed into the registry as follows: hash = {...};
44
- # Registry.new.register(:hash) { hash } or Registry.new.register(:hash) { {...} }. This class will likely absorb
45
- # un-abstracted elements of the adapter class, see Workflow#Adapter
46
-
47
- # @todo (rhorsey) registry should be a member of WorkflowRunner - DLM
48
- # @todo (rhorsey) how is this different than a regular hash? why is it important to be able to register keys with blocks that return values instead of values, looks like the block is called on insert anyway? let's not go crazy on performance optimizations until we have to - DLM
49
- class Registry
50
- def initialize
51
- @items = {}
52
- @results_cache = {}
53
- end
54
-
55
- # Register a key and cache it's value. Note that if a key with the given name already exists it is overwritten
56
- #
57
- # @param [] key The key for the passed in block. Symbols are highly recommended
58
- # @param [Proc] block The block (Proc) which contains the registered information
59
- # @return [] Returns block.call from the registries cache
60
- #
61
- def register(key, &block)
62
- raise ArgumentError, 'block required' unless block_given?
63
- @items[key] = block
64
- @results_cache[key] = @items[key].call
65
- end
66
-
67
- # Get the cached value of the given key
68
- #
69
- # @param [] key The key defining the block
70
- # @return [] Returns the registries cached value for the key or nil if the key was not found
71
- #
72
- def get(key)
73
- return nil unless @items.key?(key)
74
- @results_cache[key]
75
- end
76
- alias [] get
77
-
78
- # Re-evaluate the proc of a key and update the cache
79
- #
80
- # @param [Sym or String] key This will evaluate the item assigned to the key and update the cache if possible
81
- # @return [] If successful the method returns the new value, and if it cannot find or cannot update the key it
82
- # returns nil
83
- #
84
- def eval(key)
85
- return nil unless @items.key?(key)
86
- begin
87
- @items[key].call
88
- rescue
89
- return nil
90
- end
91
- @results_cache[key] = @items[key].call
92
- end
93
-
94
- # Checks if the given key is registered with the registry
95
- #
96
- # @return [Boolean]
97
- #
98
- def key?(key)
99
- @items.key?(key)
100
- end
101
- alias has_key? key?
102
-
103
- # Returns an array populated with the keys of this object
104
- #
105
- # @return [Array]
106
- #
107
- def keys
108
- @items.keys
109
- end
110
-
111
- # Return the number of elements in this registry
112
- #
113
- # @return [Fixnum]
114
- #
115
- def length
116
- @items.keys.length
117
- end
118
- alias size length
119
-
120
- # Checks if this registry has any items
121
- #
122
- # @return [Boolean]
123
- #
124
- def empty?
125
- @items.keys.empty?
126
- end
127
-
128
- # Merge one registry with another and return a completely new registry. Note that the result cache is completely
129
- # busted, so any gets on the new registry will result in a cache miss
130
- #
131
- # @param [Registry] other The other #Registry to merge onto of self
132
- # @return [Registry] A merged #Registry
133
- #
134
- def merge(other)
135
- self.class.new.tap do |result|
136
- result.merge!(self)
137
- result.merge!(other)
138
- end
139
- end
140
-
141
- # Like #merge but updates self
142
- #
143
- # @param [Registry] other The other #Registry to merge onto of self
144
- # @return [Void]
145
- #
146
- def merge!(other)
147
- @items.merge!(other.__internal_state[:items])
148
- self
149
- end
150
-
151
- # Converts the registry to a hash
152
- #
153
- # @return [Hash] The registry as a hash
154
- #
155
- def to_hash
156
- result = {}
157
- @results_cache.each_pair do |key, value|
158
- result[key] = value
159
- end
160
-
161
- result
162
- end
163
-
164
- def __internal_state
165
- {
166
- items: @items,
167
- results_cache: @results_cache
168
- }
169
- end
170
- end
171
- end
172
- end
1
+ # *******************************************************************************
2
+ # OpenStudio(R), Copyright (c) 2008-2020, Alliance for Sustainable Energy, LLC.
3
+ # All rights reserved.
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # (1) Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # (2) Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # (3) Neither the name of the copyright holder nor the names of any contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission from the respective party.
17
+ #
18
+ # (4) Other than as required in clauses (1) and (2), distributions in any form
19
+ # of modifications or other derivative works may not use the "OpenStudio"
20
+ # trademark, "OS", "os", or any other confusingly similar designation without
21
+ # specific prior written permission from Alliance for Sustainable Energy, LLC.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER, THE UNITED STATES
27
+ # GOVERNMENT, OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28
+ # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
33
+ # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+ # *******************************************************************************
35
+
36
+ module OpenStudio
37
+ module Workflow
38
+ # Registers objects in a single place. Based on Hashicorps's Vagrant Registry class
39
+ #
40
+ # This allows certain components (such as models, weather files, proxy settings, etc.) to be registered as blocks
41
+ # and lazily updated. This allows for the state of various objects to be updated through evaluation of or
42
+ # overwriting the key. An instance of this class is passed between jobs to allow for highly flexible workflow
43
+ # definitions. Note that hashes can be passed into the registry as follows: hash = {...};
44
+ # Registry.new.register(:hash) { hash } or Registry.new.register(:hash) { {...} }. This class will likely absorb
45
+ # un-abstracted elements of the adapter class, see Workflow#Adapter
46
+
47
+ # @todo (rhorsey) registry should be a member of WorkflowRunner - DLM
48
+ # @todo (rhorsey) how is this different than a regular hash? why is it important to be able to register keys with blocks that return values instead of values, looks like the block is called on insert anyway? let's not go crazy on performance optimizations until we have to - DLM
49
+ class Registry
50
+ def initialize
51
+ @items = {}
52
+ @results_cache = {}
53
+ end
54
+
55
+ # Register a key and cache it's value. Note that if a key with the given name already exists it is overwritten
56
+ #
57
+ # @param [] key The key for the passed in block. Symbols are highly recommended
58
+ # @param [Proc] block The block (Proc) which contains the registered information
59
+ # @return [] Returns block.call from the registries cache
60
+ #
61
+ def register(key, &block)
62
+ raise ArgumentError, 'block required' unless block_given?
63
+ @items[key] = block
64
+ @results_cache[key] = @items[key].call
65
+ end
66
+
67
+ # Get the cached value of the given key
68
+ #
69
+ # @param [] key The key defining the block
70
+ # @return [] Returns the registries cached value for the key or nil if the key was not found
71
+ #
72
+ def get(key)
73
+ return nil unless @items.key?(key)
74
+ @results_cache[key]
75
+ end
76
+ alias [] get
77
+
78
+ # Re-evaluate the proc of a key and update the cache
79
+ #
80
+ # @param [Sym or String] key This will evaluate the item assigned to the key and update the cache if possible
81
+ # @return [] If successful the method returns the new value, and if it cannot find or cannot update the key it
82
+ # returns nil
83
+ #
84
+ def eval(key)
85
+ return nil unless @items.key?(key)
86
+ begin
87
+ @items[key].call
88
+ rescue StandardError
89
+ return nil
90
+ end
91
+ @results_cache[key] = @items[key].call
92
+ end
93
+
94
+ # Checks if the given key is registered with the registry
95
+ #
96
+ # @return [Boolean]
97
+ #
98
+ def key?(key)
99
+ @items.key?(key)
100
+ end
101
+ alias has_key? key?
102
+
103
+ # Returns an array populated with the keys of this object
104
+ #
105
+ # @return [Array]
106
+ #
107
+ def keys
108
+ @items.keys
109
+ end
110
+
111
+ # Return the number of elements in this registry
112
+ #
113
+ # @return [Fixnum]
114
+ #
115
+ def length
116
+ @items.keys.length
117
+ end
118
+ alias size length
119
+
120
+ # Checks if this registry has any items
121
+ #
122
+ # @return [Boolean]
123
+ #
124
+ def empty?
125
+ @items.keys.empty?
126
+ end
127
+
128
+ # Merge one registry with another and return a completely new registry. Note that the result cache is completely
129
+ # busted, so any gets on the new registry will result in a cache miss
130
+ #
131
+ # @param [Registry] other The other #Registry to merge onto of self
132
+ # @return [Registry] A merged #Registry
133
+ #
134
+ def merge(other)
135
+ self.class.new.tap do |result|
136
+ result.merge!(self)
137
+ result.merge!(other)
138
+ end
139
+ end
140
+
141
+ # Like #merge but updates self
142
+ #
143
+ # @param [Registry] other The other #Registry to merge onto of self
144
+ # @return [Void]
145
+ #
146
+ def merge!(other)
147
+ @items.merge!(other.__internal_state[:items])
148
+ self
149
+ end
150
+
151
+ # Converts the registry to a hash
152
+ #
153
+ # @return [Hash] The registry as a hash
154
+ #
155
+ def to_hash
156
+ result = {}
157
+ @results_cache.each_pair do |key, value|
158
+ result[key] = value
159
+ end
160
+
161
+ result
162
+ end
163
+
164
+ def __internal_state
165
+ {
166
+ items: @items,
167
+ results_cache: @results_cache
168
+ }
169
+ end
170
+ end
171
+ end
172
+ end
@@ -1,328 +1,342 @@
1
- # *******************************************************************************
2
- # OpenStudio(R), Copyright (c) 2008-2018, Alliance for Sustainable Energy, LLC.
3
- # All rights reserved.
4
- # Redistribution and use in source and binary forms, with or without
5
- # modification, are permitted provided that the following conditions are met:
6
- #
7
- # (1) Redistributions of source code must retain the above copyright notice,
8
- # this list of conditions and the following disclaimer.
9
- #
10
- # (2) Redistributions in binary form must reproduce the above copyright notice,
11
- # this list of conditions and the following disclaimer in the documentation
12
- # and/or other materials provided with the distribution.
13
- #
14
- # (3) Neither the name of the copyright holder nor the names of any contributors
15
- # may be used to endorse or promote products derived from this software without
16
- # specific prior written permission from the respective party.
17
- #
18
- # (4) Other than as required in clauses (1) and (2), distributions in any form
19
- # of modifications or other derivative works may not use the "OpenStudio"
20
- # trademark, "OS", "os", or any other confusingly similar designation without
21
- # specific prior written permission from Alliance for Sustainable Energy, LLC.
22
- #
23
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26
- # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER, THE UNITED STATES
27
- # GOVERNMENT, OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28
- # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29
- # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30
- # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31
- # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32
- # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
33
- # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
- # *******************************************************************************
35
-
36
- require_relative 'registry'
37
- require_relative 'adapters/input/local'
38
- require_relative 'adapters/output/local'
39
-
40
- require 'logger'
41
- require 'pathname'
42
-
43
- # Run Class for OpenStudio workflow. All comments here need some love, as well as the code itself
44
- module OpenStudio
45
- module Workflow
46
- class Run
47
- attr_accessor :registry
48
-
49
- attr_reader :options
50
- attr_reader :input_adapter
51
- attr_reader :output_adapter
52
- attr_reader :final_message
53
- attr_reader :job_results
54
- attr_reader :current_state
55
-
56
- # Define the default set of jobs. Note that the states of :queued of :finished need to exist for all job arrays.
57
- #
58
- def self.default_jobs
59
- [
60
- { state: :queued, next_state: :initialization, options: { initial: true } },
61
- { state: :initialization, next_state: :os_measures, job: :RunInitialization,
62
- file: 'openstudio/workflow/jobs/run_initialization', options: {} },
63
- { state: :os_measures, next_state: :translator, job: :RunOpenStudioMeasures,
64
- file: 'openstudio/workflow/jobs/run_os_measures.rb', options: {} },
65
- { state: :translator, next_state: :ep_measures, job: :RunTranslation,
66
- file: 'openstudio/workflow/jobs/run_translation.rb', options: {} },
67
- { state: :ep_measures, next_state: :preprocess, job: :RunEnergyPlusMeasures,
68
- file: 'openstudio/workflow/jobs/run_ep_measures.rb', options: {} },
69
- { state: :preprocess, next_state: :simulation, job: :RunPreprocess,
70
- file: 'openstudio/workflow/jobs/run_preprocess.rb', options: {} },
71
- { state: :simulation, next_state: :reporting_measures, job: :RunEnergyPlus,
72
- file: 'openstudio/workflow/jobs/run_energyplus.rb', options: {} },
73
- { state: :reporting_measures, next_state: :postprocess, job: :RunReportingMeasures,
74
- file: 'openstudio/workflow/jobs/run_reporting_measures.rb', options: {} },
75
- { state: :postprocess, next_state: :finished, job: :RunPostprocess,
76
- file: 'openstudio/workflow/jobs/run_postprocess.rb', options: {} },
77
- { state: :finished },
78
- { state: :errored }
79
- ]
80
- end
81
-
82
- # Initialize a new run class
83
- #
84
- # @param [String] osw_path the path to the OSW file to run. It is highly recommended that this be an absolute
85
- # path, however if not it will be made absolute relative to the current working directory
86
- # @param [Hash] user_options ({}) A set of user-specified options that are used to override default behaviors.
87
- # @option user_options [Hash] :cleanup Remove unneccessary files during post processing, overrides OSW option if set, defaults to true
88
- # @option user_options [Hash] :debug Print debugging messages, overrides OSW option if set, defaults to false
89
- # @option user_options [Hash] :energyplus_path Specifies path to energyplus executable, defaults to empty
90
- # @option user_options [Hash] :fast Speeds up workflow by skipping steps not needed for running simulations, defaults to false
91
- # @option user_options [Hash] :jobs Simulation workflow, overrides jobs in OSW if set, defaults to default_jobs
92
- # @option user_options [Hash] :output_adapter Output adapter to use, overrides output adapter in OSW if set, defaults to local adapter
93
- # @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
94
- # @option user_options [Hash] :profile Produce additional output for profiling simulations, defaults to false
95
- # @option user_options [Hash] :skip_energyplus_preprocess Does not add add default output requests to EnergyPlus input if true, requests from reporting measures are added in either case, defaults to false
96
- # @option user_options [Hash] :skip_expand_objects Skips the call to the EnergyPlus ExpandObjects program, defaults to false
97
- # @option user_options [Hash] :targets Log targets to write to, defaults to standard out and run.log
98
- # @option user_options [Hash] :verify_osw Check OSW for correctness, defaults to true
99
- # @option user_options [Hash] :weather_file Initial weather file to load, overrides OSW option if set, defaults to empty
100
- def initialize(osw_path, user_options = {})
101
- # DLM - what is final_message?
102
- @final_message = ''
103
- @current_state = nil
104
- @options = {}
105
-
106
- # 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
107
- # - analysis - the current OSA parsed as a Ruby Hash
108
- # - datapoint - the current OSD parsed as a Ruby Hash
109
- # - log_targets - IO devices that are being logged to
110
- # - logger - general logger
111
- # - model - the current OpenStudio Model object, updated after each step
112
- # - model_idf - the current EnergyPlus Workspace object, updated after each step
113
- # - openstudio_2 - true if we are running in OpenStudio 2.X environment
114
- # - osw_path - the path the OSW was loaded from as a string
115
- # - osw_dir - the directory the OSW was loaded from as a string
116
- # - output_attributes - added during simulation time
117
- # - results - objective function values
118
- # - root_dir - the root directory in the OSW as a string
119
- # - run_dir - the run directory for the simulation as a string
120
- # - runner - the current OSRunner object
121
- # - sql - the path to the current EnergyPlus SQL file as a string
122
- # - time_logger - logger for doing profiling - time to run each step will be captured in OSResult, deprecate
123
- # - wf - the path to the current weather file as a string, updated after each step
124
- # - workflow - the current OSW parsed as a Ruby Hash
125
- # - workflow_json - the current WorkflowJSON object
126
- @registry = Registry.new
127
-
128
- openstudio_2 = false
129
- begin
130
- # OpenStudio 2.X test
131
- OpenStudio::WorkflowJSON.new
132
- openstudio_2 = true
133
- rescue NameError => e
134
- end
135
- @registry.register(:openstudio_2) { openstudio_2 }
136
-
137
- # get the input osw
138
- @input_adapter = OpenStudio::Workflow::InputAdapter::Local.new(osw_path)
139
-
140
- # DLM: need to check that we have correct permissions to all these paths
141
- @registry.register(:osw_path) { Pathname.new(@input_adapter.osw_path).realpath }
142
- @registry.register(:osw_dir) { Pathname.new(@input_adapter.osw_dir).realpath }
143
- @registry.register(:run_dir) { Pathname.new(@input_adapter.run_dir).cleanpath } # run dir might not yet exist, calling realpath will throw
144
-
145
- # get info to set up logging first in case of failures later
146
- @options[:debug] = @input_adapter.debug(user_options, false)
147
- @options[:preserve_run_dir] = @input_adapter.preserve_run_dir(user_options, false)
148
- @options[:skip_expand_objects] = @input_adapter.skip_expand_objects(user_options, false)
149
- @options[:skip_energyplus_preprocess] = @input_adapter.skip_energyplus_preprocess(user_options, false)
150
- @options[:profile] = @input_adapter.profile(user_options, false)
151
-
152
- # if running in osw dir, force preserve run dir
153
- if @registry[:osw_dir] == @registry[:run_dir]
154
- # force preserving the run directory
155
- @options[:preserve_run_dir] = true
156
- end
157
-
158
- # By default blow away the entire run directory every time and recreate it
159
- unless @options[:preserve_run_dir]
160
- if File.exist?(@registry[:run_dir])
161
- # logger is not initialized yet (it needs run dir to exist for log)
162
- puts "Removing existing run directory #{@registry[:run_dir]}" if @options[:debug]
163
-
164
- # DLM: this is dangerous, we are calling rm_rf on a user entered directory, need to check this first
165
- # TODO: Echoing Dan's comment
166
- FileUtils.rm_rf(@registry[:run_dir])
167
- end
168
- end
169
- FileUtils.mkdir_p(@registry[:run_dir])
170
-
171
- # set up logging after cleaning run dir
172
- if user_options[:targets]
173
- @options[:targets] = user_options[:targets]
174
- else
175
- # don't create these files unless we want to use them
176
- # DLM: TODO, make sure that run.log will be closed later
177
- @options[:targets] = [STDOUT, File.open(File.join(@registry[:run_dir], 'run.log'), 'a')]
178
- end
179
-
180
- @registry.register(:log_targets) { @options[:targets] }
181
- @registry.register(:time_logger) { TimeLogger.new } if @options[:profile]
182
-
183
- # Initialize the MultiDelegator logger
184
- logger_level = @options[:debug] ? ::Logger::DEBUG : ::Logger::WARN
185
- @logger = ::Logger.new(MultiDelegator.delegate(:write, :close).to(*@options[:targets])) # * is the splat operator
186
- @logger.level = logger_level
187
- @registry.register(:logger) { @logger }
188
-
189
- @logger.info "openstudio_2 = #{@registry[:openstudio_2]}"
190
-
191
- # get the output adapter
192
- default_output_adapter = OpenStudio::Workflow::OutputAdapter::Local.new(output_directory: @input_adapter.run_dir)
193
- @output_adapter = @input_adapter.output_adapter(user_options, default_output_adapter, @logger)
194
-
195
- # get the jobs
196
- default_jobs = OpenStudio::Workflow::Run.default_jobs
197
- @jobs = @input_adapter.jobs(user_options, default_jobs, @logger)
198
-
199
- # get other run options out of user_options and into permanent options
200
- @options[:cleanup] = @input_adapter.cleanup(user_options, true)
201
- @options[:energyplus_path] = @input_adapter.energyplus_path(user_options, nil)
202
- @options[:verify_osw] = @input_adapter.verify_osw(user_options, true)
203
- @options[:weather_file] = @input_adapter.weather_file(user_options, nil)
204
- @options[:fast] = @input_adapter.fast(user_options, false)
205
-
206
- openstudio_dir = "unknown"
207
- begin
208
- openstudio_dir = $OpenStudio_Dir
209
- if openstudio_dir.nil?
210
- openstudio_dir = OpenStudio::getOpenStudioModuleDirectory.to_s
211
- end
212
- rescue
213
- end
214
- @logger.info "openstudio_dir = #{openstudio_dir}"
215
-
216
- @logger.info "Initializing directory #{@registry[:run_dir]} for simulation with options #{@options}"
217
-
218
- # Define the state and transitions
219
- @current_state = :queued
220
- end
221
-
222
- # execute the workflow defined in the state object
223
- #
224
- # @todo add a catch if any job fails
225
- # @todo make a block method to provide feedback
226
- def run
227
- @logger.info "Starting workflow in #{@registry[:run_dir]}"
228
- begin
229
- next_state
230
- while @current_state != :finished && @current_state != :errored
231
- #sleep 2
232
- step
233
- end
234
-
235
- if !@options[:fast]
236
- @logger.info 'Finished workflow - communicating results and zipping files'
237
- @output_adapter.communicate_results(@registry[:run_dir], @registry[:results])
238
- end
239
- rescue => e
240
- @logger.info "Error occurred during running with #{e.message}"
241
- ensure
242
- @logger.info 'Workflow complete'
243
-
244
- if @current_state == :errored
245
- @registry[:workflow_json].setCompletedStatus('Fail') if @registry[:workflow_json]
246
- else
247
- # completed status will already be set if workflow was halted
248
- if @registry[:workflow_json].completedStatus.empty?
249
- @registry[:workflow_json].setCompletedStatus('Success')
250
- else
251
- @current_state = :errored if @registry[:workflow_json].completedStatus.get == 'Fail'
252
- end
253
- end
254
-
255
- # save all files before calling output adapter
256
- @registry[:log_targets].each(&:flush)
257
-
258
- # save workflow with results
259
- if @registry[:workflow_json] and !@options[:fast]
260
- out_path = @registry[:workflow_json].absoluteOutPath
261
- @registry[:workflow_json].saveAs(out_path)
262
- end
263
-
264
- # Write out the TimeLogger to the filesystem
265
- @registry[:time_logger].save(File.join(@registry[:run_dir], 'profile.json')) if @registry[:time_logger]
266
-
267
- if @current_state == :errored
268
- @output_adapter.communicate_failure
269
- else
270
- @output_adapter.communicate_complete
271
- end
272
-
273
- end
274
-
275
- @current_state
276
- end
277
-
278
- # Step through the states, if there is an error (e.g. exception) then go to error
279
- #
280
- def step
281
- step_instance = @jobs.find { |h| h[:state] == @current_state }
282
- require step_instance[:file]
283
- klass = OpenStudio::Workflow.new_class(step_instance[:job], @input_adapter, @output_adapter, @registry, @options)
284
- @output_adapter.communicate_transition("Starting state #{@current_state}", :state)
285
- state_return = klass.perform
286
- if state_return
287
- @output_adapter.communicate_transition("Returned from state #{@current_state} with message #{state_return}", :state)
288
- else
289
- @output_adapter.communicate_transition("Returned from state #{@current_state}", :state)
290
- end
291
- next_state
292
- rescue => e
293
- step_error("#{e.message}:#{e.backtrace.join("\n")}")
294
- end
295
-
296
- # Error handling for when there is an exception running any of the state transitions
297
- #
298
- def step_error(*args)
299
- # Make sure to set the instance variable @error to true in order to stop the :step
300
- # event from being fired.
301
- @final_message = "Found error in state '#{@current_state}' with message #{args}}"
302
- @logger.error @final_message
303
-
304
- # transition to an error state
305
- @current_state = :errored
306
- end
307
-
308
- # Return the finished state and exit
309
- #
310
- def run_finished(_, _, _)
311
- logger.info "Running #{__method__}"
312
-
313
- @current_state
314
- end
315
-
316
- private
317
-
318
- # Advance the @current_state to the next state
319
- #
320
- def next_state
321
- @logger.info "Current state: '#{@current_state}'"
322
- ns = @jobs.find { |h| h[:state] == @current_state }[:next_state]
323
- @logger.info "Next state will be: '#{ns}'"
324
- @current_state = ns
325
- end
326
- end
327
- end
328
- end
1
+ # *******************************************************************************
2
+ # OpenStudio(R), Copyright (c) 2008-2020, Alliance for Sustainable Energy, LLC.
3
+ # All rights reserved.
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # (1) Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # (2) Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # (3) Neither the name of the copyright holder nor the names of any contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission from the respective party.
17
+ #
18
+ # (4) Other than as required in clauses (1) and (2), distributions in any form
19
+ # of modifications or other derivative works may not use the "OpenStudio"
20
+ # trademark, "OS", "os", or any other confusingly similar designation without
21
+ # specific prior written permission from Alliance for Sustainable Energy, LLC.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER, THE UNITED STATES
27
+ # GOVERNMENT, OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28
+ # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
33
+ # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+ # *******************************************************************************
35
+
36
+ require_relative 'registry'
37
+ require_relative 'adapters/input/local'
38
+ require_relative 'adapters/output/local'
39
+
40
+ require 'logger'
41
+ require 'pathname'
42
+
43
+ # Run Class for OpenStudio workflow. All comments here need some love, as well as the code itself
44
+ module OpenStudio
45
+ module Workflow
46
+ class Run
47
+ attr_accessor :registry
48
+
49
+ attr_reader :options
50
+ attr_reader :input_adapter
51
+ attr_reader :output_adapter
52
+ attr_reader :final_message
53
+ attr_reader :job_results
54
+ attr_reader :current_state
55
+
56
+ # Define the default set of jobs. Note that the states of :queued of :finished need to exist for all job arrays.
57
+ #
58
+ def self.default_jobs
59
+ [
60
+ { state: :queued, next_state: :initialization, options: { initial: true } },
61
+ { state: :initialization, next_state: :os_measures, job: :RunInitialization,
62
+ file: 'openstudio/workflow/jobs/run_initialization', options: {} },
63
+ { state: :os_measures, next_state: :translator, job: :RunOpenStudioMeasures,
64
+ file: 'openstudio/workflow/jobs/run_os_measures.rb', options: {} },
65
+ { state: :translator, next_state: :ep_measures, job: :RunTranslation,
66
+ file: 'openstudio/workflow/jobs/run_translation.rb', options: {} },
67
+ { state: :ep_measures, next_state: :preprocess, job: :RunEnergyPlusMeasures,
68
+ file: 'openstudio/workflow/jobs/run_ep_measures.rb', options: {} },
69
+ { state: :preprocess, next_state: :simulation, job: :RunPreprocess,
70
+ file: 'openstudio/workflow/jobs/run_preprocess.rb', options: {} },
71
+ { state: :simulation, next_state: :reporting_measures, job: :RunEnergyPlus,
72
+ file: 'openstudio/workflow/jobs/run_energyplus.rb', options: {} },
73
+ { state: :reporting_measures, next_state: :postprocess, job: :RunReportingMeasures,
74
+ file: 'openstudio/workflow/jobs/run_reporting_measures.rb', options: {} },
75
+ { state: :postprocess, next_state: :finished, job: :RunPostprocess,
76
+ file: 'openstudio/workflow/jobs/run_postprocess.rb', options: {} },
77
+ { state: :finished },
78
+ { state: :errored }
79
+ ]
80
+ end
81
+
82
+ # Initialize a new run class
83
+ #
84
+ # @param [String] osw_path the path to the OSW file to run. It is highly recommended that this be an absolute
85
+ # path, however if not it will be made absolute relative to the current working directory
86
+ # @param [Hash] user_options ({}) A set of user-specified options that are used to override default behaviors.
87
+ # @option user_options [Hash] :cleanup Remove unneccessary files during post processing, overrides OSW option if set, defaults to true
88
+ # @option user_options [Hash] :debug Print debugging messages, overrides OSW option if set, defaults to false
89
+ # @option user_options [Hash] :energyplus_path Specifies path to energyplus executable, defaults to empty
90
+ # @option user_options [Hash] :fast Speeds up workflow by skipping steps not needed for running simulations, defaults to false
91
+ # @option user_options [Hash] :jobs Simulation workflow, overrides jobs in OSW if set, defaults to default_jobs
92
+ # @option user_options [Hash] :output_adapter Output adapter to use, overrides output adapter in OSW if set, defaults to local adapter
93
+ # @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
94
+ # @option user_options [Hash] :profile Produce additional output for profiling simulations, defaults to false
95
+ # @option user_options [Hash] :skip_energyplus_preprocess Does not add add default output requests to EnergyPlus input if true, requests from reporting measures are added in either case, defaults to false
96
+ # @option user_options [Hash] :skip_expand_objects Skips the call to the EnergyPlus ExpandObjects program, defaults to false
97
+ # @option user_options [Hash] :targets Log targets to write to, defaults to standard out and run.log
98
+ # @option user_options [Hash] :verify_osw Check OSW for correctness, defaults to true
99
+ # @option user_options [Hash] :weather_file Initial weather file to load, overrides OSW option if set, defaults to empty
100
+ def initialize(osw_path, user_options = {})
101
+ # DLM - what is final_message?
102
+ @final_message = ''
103
+ @current_state = nil
104
+ @options = {}
105
+
106
+ # 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
107
+ # - analysis - the current OSA parsed as a Ruby Hash
108
+ # - datapoint - the current OSD parsed as a Ruby Hash
109
+ # - log_targets - IO devices that are being logged to
110
+ # - logger - general logger
111
+ # - model - the current OpenStudio Model object, updated after each step
112
+ # - model_idf - the current EnergyPlus Workspace object, updated after each step
113
+ # - openstudio_2 - true if we are running in OpenStudio 2.X environment
114
+ # - osw_path - the path the OSW was loaded from as a string
115
+ # - osw_dir - the directory the OSW was loaded from as a string
116
+ # - output_attributes - added during simulation time
117
+ # - results - objective function values
118
+ # - root_dir - the root directory in the OSW as a string
119
+ # - run_dir - the run directory for the simulation as a string
120
+ # - runner - the current OSRunner object
121
+ # - sql - the path to the current EnergyPlus SQL file as a string
122
+ # - time_logger - logger for doing profiling - time to run each step will be captured in OSResult, deprecate
123
+ # - wf - the path to the current weather file as a string, updated after each step
124
+ # - workflow - the current OSW parsed as a Ruby Hash
125
+ # - workflow_json - the current WorkflowJSON object
126
+ @registry = Registry.new
127
+
128
+ openstudio_2 = false
129
+ begin
130
+ # OpenStudio 2.X test
131
+ OpenStudio::WorkflowJSON.new
132
+ openstudio_2 = true
133
+ rescue NameError => e
134
+ end
135
+ @registry.register(:openstudio_2) { openstudio_2 }
136
+
137
+ # get the input osw
138
+ @input_adapter = OpenStudio::Workflow::InputAdapter::Local.new(osw_path)
139
+
140
+ # DLM: need to check that we have correct permissions to all these paths
141
+ @registry.register(:osw_path) { Pathname.new(@input_adapter.osw_path).realpath }
142
+ @registry.register(:osw_dir) { Pathname.new(@input_adapter.osw_dir).realpath }
143
+ @registry.register(:run_dir) { Pathname.new(@input_adapter.run_dir).cleanpath } # run dir might not yet exist, calling realpath will throw
144
+
145
+ # get info to set up logging first in case of failures later
146
+ @options[:debug] = @input_adapter.debug(user_options, false)
147
+ @options[:preserve_run_dir] = @input_adapter.preserve_run_dir(user_options, false)
148
+ @options[:skip_expand_objects] = @input_adapter.skip_expand_objects(user_options, false)
149
+ @options[:skip_energyplus_preprocess] = @input_adapter.skip_energyplus_preprocess(user_options, false)
150
+ @options[:profile] = @input_adapter.profile(user_options, false)
151
+
152
+ # if running in osw dir, force preserve run dir
153
+ if @registry[:osw_dir] == @registry[:run_dir]
154
+ # force preserving the run directory
155
+ @options[:preserve_run_dir] = true
156
+ end
157
+
158
+ # By default blow away the entire run directory every time and recreate it
159
+ unless @options[:preserve_run_dir]
160
+ if File.exist?(@registry[:run_dir])
161
+ # logger is not initialized yet (it needs run dir to exist for log)
162
+ puts "Removing existing run directory #{@registry[:run_dir]}" if @options[:debug]
163
+
164
+ # DLM: this is dangerous, we are calling rm_rf on a user entered directory, need to check this first
165
+ # TODO: Echoing Dan's comment
166
+ FileUtils.rm_rf(@registry[:run_dir])
167
+ end
168
+ end
169
+ FileUtils.mkdir_p(@registry[:run_dir])
170
+
171
+ # set up logging after cleaning run dir
172
+ if user_options[:targets]
173
+ @options[:targets] = user_options[:targets]
174
+ else
175
+ # don't create these files unless we want to use them
176
+ # DLM: TODO, make sure that run.log will be closed later
177
+ @options[:targets] = [STDOUT, File.open(File.join(@registry[:run_dir], 'run.log'), 'a')]
178
+ end
179
+
180
+ @registry.register(:log_targets) { @options[:targets] }
181
+ @registry.register(:time_logger) { TimeLogger.new } if @options[:profile]
182
+
183
+ # Initialize the MultiDelegator logger
184
+ logger_level = @options[:debug] ? ::Logger::DEBUG : ::Logger::WARN
185
+ @logger = ::Logger.new(MultiDelegator.delegate(:write, :close).to(*@options[:targets])) # * is the splat operator
186
+ @logger.level = logger_level
187
+ @registry.register(:logger) { @logger }
188
+
189
+ @logger.info "openstudio_2 = #{@registry[:openstudio_2]}"
190
+
191
+ # get the output adapter
192
+ default_output_adapter = OpenStudio::Workflow::OutputAdapter::Local.new(output_directory: @input_adapter.run_dir)
193
+ @output_adapter = @input_adapter.output_adapter(user_options, default_output_adapter, @logger)
194
+
195
+ # get the jobs
196
+ default_jobs = OpenStudio::Workflow::Run.default_jobs
197
+ @jobs = @input_adapter.jobs(user_options, default_jobs, @logger)
198
+
199
+ # get other run options out of user_options and into permanent options
200
+ @options[:cleanup] = @input_adapter.cleanup(user_options, true)
201
+ @options[:energyplus_path] = @input_adapter.energyplus_path(user_options, nil)
202
+ @options[:verify_osw] = @input_adapter.verify_osw(user_options, true)
203
+ @options[:weather_file] = @input_adapter.weather_file(user_options, nil)
204
+ @options[:fast] = @input_adapter.fast(user_options, false)
205
+
206
+ openstudio_dir = 'unknown'
207
+ begin
208
+ openstudio_dir = $OpenStudio_Dir
209
+ if openstudio_dir.nil?
210
+ openstudio_dir = OpenStudio.getOpenStudioModuleDirectory.to_s
211
+ end
212
+ rescue StandardError
213
+ end
214
+ @logger.info "openstudio_dir = #{openstudio_dir}"
215
+
216
+ @logger.info "Initializing directory #{@registry[:run_dir]} for simulation with options #{@options}"
217
+
218
+ # Define the state and transitions
219
+ @current_state = :queued
220
+ end
221
+
222
+ # execute the workflow defined in the state object
223
+ #
224
+ # @todo add a catch if any job fails
225
+ # @todo make a block method to provide feedback
226
+ def run
227
+ @logger.info "Starting workflow in #{@registry[:run_dir]}"
228
+ begin
229
+ next_state
230
+ while @current_state != :finished && @current_state != :errored
231
+ # sleep 2
232
+ step
233
+ end
234
+
235
+ if !@options[:fast]
236
+ @logger.info 'Finished workflow - communicating results and zipping files'
237
+ @output_adapter.communicate_results(@registry[:run_dir], @registry[:results])
238
+ end
239
+ rescue StandardError => e
240
+ @logger.info "Error occurred during running with #{e.message}"
241
+ ensure
242
+ @logger.info 'Workflow complete'
243
+
244
+ # If we let the :reporting_measures step fail to continue with
245
+ # postprocess, we still want to report and error
246
+ if @final_message != ''
247
+ @current_state = :errored
248
+ end
249
+
250
+ if @current_state == :errored
251
+ @registry[:workflow_json].setCompletedStatus('Fail') if @registry[:workflow_json]
252
+ else
253
+ # completed status will already be set if workflow was halted
254
+ if @registry[:workflow_json].completedStatus.empty?
255
+ @registry[:workflow_json].setCompletedStatus('Success')
256
+ else
257
+ @current_state = :errored if @registry[:workflow_json].completedStatus.get == 'Fail'
258
+ end
259
+ end
260
+
261
+ # save all files before calling output adapter
262
+ @registry[:log_targets].each(&:flush)
263
+
264
+ # save workflow with results
265
+ if @registry[:workflow_json] && !@options[:fast]
266
+ out_path = @registry[:workflow_json].absoluteOutPath
267
+ @registry[:workflow_json].saveAs(out_path)
268
+ end
269
+
270
+ # Write out the TimeLogger to the filesystem
271
+ @registry[:time_logger].save(File.join(@registry[:run_dir], 'profile.json')) if @registry[:time_logger]
272
+
273
+ if @current_state == :errored
274
+ @output_adapter.communicate_failure
275
+ else
276
+ @output_adapter.communicate_complete
277
+ end
278
+ end
279
+
280
+ @current_state
281
+ end
282
+
283
+ # Step through the states, if there is an error (e.g. exception) then go to error
284
+ #
285
+ def step
286
+ step_instance = @jobs.find { |h| h[:state] == @current_state }
287
+ require step_instance[:file]
288
+ klass = OpenStudio::Workflow.new_class(step_instance[:job], @input_adapter, @output_adapter, @registry, @options)
289
+ @output_adapter.communicate_transition("Starting state #{@current_state}", :state)
290
+ state_return = klass.perform
291
+ if state_return
292
+ @output_adapter.communicate_transition("Returned from state #{@current_state} with message #{state_return}", :state)
293
+ else
294
+ @output_adapter.communicate_transition("Returned from state #{@current_state}", :state)
295
+ end
296
+ next_state
297
+ rescue StandardError => e
298
+ step_error("#{e.message}:#{e.backtrace.join("\n")}")
299
+ end
300
+
301
+ # Error handling for when there is an exception running any of the state transitions
302
+ #
303
+ def step_error(*args)
304
+ # Make sure to set the instance variable @error to true in order to stop the :step
305
+ # event from being fired.
306
+
307
+ # Allow continuing anyways if it fails in reporting measures
308
+ if @current_state == :reporting_measures
309
+ @final_message = "Found error in state '#{@current_state}' with message #{args}}"
310
+ @logger.error @final_message
311
+
312
+ next_state
313
+ else
314
+ @final_message = "Found error in state '#{@current_state}' with message #{args}}"
315
+ @logger.error @final_message
316
+
317
+ # transition to an error state
318
+ @current_state = :errored
319
+ end
320
+ end
321
+
322
+ # Return the finished state and exit
323
+ #
324
+ def run_finished(_, _, _)
325
+ logger.info "Running #{__method__}"
326
+
327
+ @current_state
328
+ end
329
+
330
+ private
331
+
332
+ # Advance the @current_state to the next state
333
+ #
334
+ def next_state
335
+ @logger.info "Current state: '#{@current_state}'"
336
+ ns = @jobs.find { |h| h[:state] == @current_state }[:next_state]
337
+ @logger.info "Next state will be: '#{ns}'"
338
+ @current_state = ns
339
+ end
340
+ end
341
+ end
342
+ end