openstudio-workflow 1.3.3 → 1.3.4

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 +77 -72
  3. data/README.md +93 -93
  4. data/Rakefile +36 -36
  5. data/lib/openstudio-workflow.rb +65 -49
  6. data/lib/openstudio/workflow/adapters/input/local.rb +324 -301
  7. data/lib/openstudio/workflow/adapters/output/local.rb +161 -97
  8. data/lib/openstudio/workflow/adapters/output/socket.rb +107 -91
  9. data/lib/openstudio/workflow/adapters/output/web.rb +82 -66
  10. data/lib/openstudio/workflow/adapters/output_adapter.rb +163 -147
  11. data/lib/openstudio/workflow/job.rb +57 -22
  12. data/lib/openstudio/workflow/jobs/resources/monthly_report.idf +222 -222
  13. data/lib/openstudio/workflow/jobs/run_energyplus.rb +70 -54
  14. data/lib/openstudio/workflow/jobs/run_ep_measures.rb +73 -57
  15. data/lib/openstudio/workflow/jobs/run_initialization.rb +203 -171
  16. data/lib/openstudio/workflow/jobs/run_os_measures.rb +89 -73
  17. data/lib/openstudio/workflow/jobs/run_postprocess.rb +73 -57
  18. data/lib/openstudio/workflow/jobs/run_preprocess.rb +104 -80
  19. data/lib/openstudio/workflow/jobs/run_reporting_measures.rb +118 -102
  20. data/lib/openstudio/workflow/jobs/run_translation.rb +84 -68
  21. data/lib/openstudio/workflow/multi_delegator.rb +62 -46
  22. data/lib/openstudio/workflow/registry.rb +172 -137
  23. data/lib/openstudio/workflow/run.rb +328 -312
  24. data/lib/openstudio/workflow/time_logger.rb +96 -53
  25. data/lib/openstudio/workflow/util.rb +49 -14
  26. data/lib/openstudio/workflow/util/energyplus.rb +605 -570
  27. data/lib/openstudio/workflow/util/io.rb +68 -33
  28. data/lib/openstudio/workflow/util/measure.rb +650 -615
  29. data/lib/openstudio/workflow/util/model.rb +151 -100
  30. data/lib/openstudio/workflow/util/post_process.rb +238 -187
  31. data/lib/openstudio/workflow/util/weather_file.rb +143 -108
  32. data/lib/openstudio/workflow/version.rb +40 -24
  33. data/lib/openstudio/workflow_json.rb +476 -443
  34. data/lib/openstudio/workflow_runner.rb +268 -252
  35. metadata +23 -23
@@ -1,137 +1,172 @@
1
- module OpenStudio
2
- module Workflow
3
- # Registers objects in a single place. Based on Hashicorps's Vagrant Registry class
4
- #
5
- # This allows certain components (such as models, weather files, proxy settings, etc.) to be registered as blocks
6
- # and lazily updated. This allows for the state of various objects to be updated through evaluation of or
7
- # overwriting the key. An instance of this class is passed between jobs to allow for highly flexible workflow
8
- # definitions. Note that hashes can be passed into the registry as follows: hash = {...};
9
- # Registry.new.register(:hash) { hash } or Registry.new.register(:hash) { {...} }. This class will likely absorb
10
- # un-abstracted elements of the adapter class, see Workflow#Adapter
11
-
12
- # @todo (rhorsey) registry should be a member of WorkflowRunner - DLM
13
- # @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
14
- class Registry
15
- def initialize
16
- @items = {}
17
- @results_cache = {}
18
- end
19
-
20
- # Register a key and cache it's value. Note that if a key with the given name already exists it is overwritten
21
- #
22
- # @param [] key The key for the passed in block. Symbols are highly recommended
23
- # @param [Proc] block The block (Proc) which contains the registered information
24
- # @return [] Returns block.call from the registries cache
25
- #
26
- def register(key, &block)
27
- raise ArgumentError, 'block required' unless block_given?
28
- @items[key] = block
29
- @results_cache[key] = @items[key].call
30
- end
31
-
32
- # Get the cached value of the given key
33
- #
34
- # @param [] key The key defining the block
35
- # @return [] Returns the registries cached value for the key or nil if the key was not found
36
- #
37
- def get(key)
38
- return nil unless @items.key?(key)
39
- @results_cache[key]
40
- end
41
- alias [] get
42
-
43
- # Re-evaluate the proc of a key and update the cache
44
- #
45
- # @param [Sym or String] key This will evaluate the item assigned to the key and update the cache if possible
46
- # @return [] If successful the method returns the new value, and if it cannot find or cannot update the key it
47
- # returns nil
48
- #
49
- def eval(key)
50
- return nil unless @items.key?(key)
51
- begin
52
- @items[key].call
53
- rescue
54
- return nil
55
- end
56
- @results_cache[key] = @items[key].call
57
- end
58
-
59
- # Checks if the given key is registered with the registry
60
- #
61
- # @return [Boolean]
62
- #
63
- def key?(key)
64
- @items.key?(key)
65
- end
66
- alias has_key? key?
67
-
68
- # Returns an array populated with the keys of this object
69
- #
70
- # @return [Array]
71
- #
72
- def keys
73
- @items.keys
74
- end
75
-
76
- # Return the number of elements in this registry
77
- #
78
- # @return [Fixnum]
79
- #
80
- def length
81
- @items.keys.length
82
- end
83
- alias size length
84
-
85
- # Checks if this registry has any items
86
- #
87
- # @return [Boolean]
88
- #
89
- def empty?
90
- @items.keys.empty?
91
- end
92
-
93
- # Merge one registry with another and return a completely new registry. Note that the result cache is completely
94
- # busted, so any gets on the new registry will result in a cache miss
95
- #
96
- # @param [Registry] other The other #Registry to merge onto of self
97
- # @return [Registry] A merged #Registry
98
- #
99
- def merge(other)
100
- self.class.new.tap do |result|
101
- result.merge!(self)
102
- result.merge!(other)
103
- end
104
- end
105
-
106
- # Like #merge but updates self
107
- #
108
- # @param [Registry] other The other #Registry to merge onto of self
109
- # @return [Void]
110
- #
111
- def merge!(other)
112
- @items.merge!(other.__internal_state[:items])
113
- self
114
- end
115
-
116
- # Converts the registry to a hash
117
- #
118
- # @return [Hash] The registry as a hash
119
- #
120
- def to_hash
121
- result = {}
122
- @results_cache.each_pair do |key, value|
123
- result[key] = value
124
- end
125
-
126
- result
127
- end
128
-
129
- def __internal_state
130
- {
131
- items: @items,
132
- results_cache: @results_cache
133
- }
134
- end
135
- end
136
- end
137
- end
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,312 +1,328 @@
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] :fast Speeds up workflow by skipping steps not needed for running simulations, defaults to false
75
- # @option user_options [Hash] :jobs Simulation workflow, overrides jobs in OSW if set, defaults to default_jobs
76
- # @option user_options [Hash] :output_adapter Output adapter to use, overrides output adapter in OSW if set, defaults to local adapter
77
- # @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
78
- # @option user_options [Hash] :profile Produce additional output for profiling simulations, defaults to false
79
- # @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
80
- # @option user_options [Hash] :skip_expand_objects Skips the call to the EnergyPlus ExpandObjects program, defaults to false
81
- # @option user_options [Hash] :targets Log targets to write to, defaults to standard out and run.log
82
- # @option user_options [Hash] :verify_osw Check OSW for correctness, defaults to true
83
- # @option user_options [Hash] :weather_file Initial weather file to load, overrides OSW option if set, defaults to empty
84
- def initialize(osw_path, user_options = {})
85
- # DLM - what is final_message?
86
- @final_message = ''
87
- @current_state = nil
88
- @options = {}
89
-
90
- # 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
91
- # - analysis - the current OSA parsed as a Ruby Hash
92
- # - datapoint - the current OSD parsed as a Ruby Hash
93
- # - log_targets - IO devices that are being logged to
94
- # - logger - general logger
95
- # - model - the current OpenStudio Model object, updated after each step
96
- # - model_idf - the current EnergyPlus Workspace object, updated after each step
97
- # - openstudio_2 - true if we are running in OpenStudio 2.X environment
98
- # - osw_path - the path the OSW was loaded from as a string
99
- # - osw_dir - the directory the OSW was loaded from as a string
100
- # - output_attributes - added during simulation time
101
- # - results - objective function values
102
- # - root_dir - the root directory in the OSW as a string
103
- # - run_dir - the run directory for the simulation as a string
104
- # - runner - the current OSRunner object
105
- # - sql - the path to the current EnergyPlus SQL file as a string
106
- # - time_logger - logger for doing profiling - time to run each step will be captured in OSResult, deprecate
107
- # - wf - the path to the current weather file as a string, updated after each step
108
- # - workflow - the current OSW parsed as a Ruby Hash
109
- # - workflow_json - the current WorkflowJSON object
110
- @registry = Registry.new
111
-
112
- openstudio_2 = false
113
- begin
114
- # OpenStudio 2.X test
115
- OpenStudio::WorkflowJSON.new
116
- openstudio_2 = true
117
- rescue NameError => e
118
- end
119
- @registry.register(:openstudio_2) { openstudio_2 }
120
-
121
- # get the input osw
122
- @input_adapter = OpenStudio::Workflow::InputAdapter::Local.new(osw_path)
123
-
124
- # DLM: need to check that we have correct permissions to all these paths
125
- @registry.register(:osw_path) { Pathname.new(@input_adapter.osw_path).realpath }
126
- @registry.register(:osw_dir) { Pathname.new(@input_adapter.osw_dir).realpath }
127
- @registry.register(:run_dir) { Pathname.new(@input_adapter.run_dir).cleanpath } # run dir might not yet exist, calling realpath will throw
128
-
129
- # get info to set up logging first in case of failures later
130
- @options[:debug] = @input_adapter.debug(user_options, false)
131
- @options[:preserve_run_dir] = @input_adapter.preserve_run_dir(user_options, false)
132
- @options[:skip_expand_objects] = @input_adapter.skip_expand_objects(user_options, false)
133
- @options[:skip_energyplus_preprocess] = @input_adapter.skip_energyplus_preprocess(user_options, false)
134
- @options[:profile] = @input_adapter.profile(user_options, false)
135
-
136
- # if running in osw dir, force preserve run dir
137
- if @registry[:osw_dir] == @registry[:run_dir]
138
- # force preserving the run directory
139
- @options[:preserve_run_dir] = true
140
- end
141
-
142
- # By default blow away the entire run directory every time and recreate it
143
- unless @options[:preserve_run_dir]
144
- if File.exist?(@registry[:run_dir])
145
- # logger is not initialized yet (it needs run dir to exist for log)
146
- puts "Removing existing run directory #{@registry[:run_dir]}" if @options[:debug]
147
-
148
- # DLM: this is dangerous, we are calling rm_rf on a user entered directory, need to check this first
149
- # TODO: Echoing Dan's comment
150
- FileUtils.rm_rf(@registry[:run_dir])
151
- end
152
- end
153
- FileUtils.mkdir_p(@registry[:run_dir])
154
-
155
- # set up logging after cleaning run dir
156
- if user_options[:targets]
157
- @options[:targets] = user_options[:targets]
158
- else
159
- # don't create these files unless we want to use them
160
- # DLM: TODO, make sure that run.log will be closed later
161
- @options[:targets] = [STDOUT, File.open(File.join(@registry[:run_dir], 'run.log'), 'a')]
162
- end
163
-
164
- @registry.register(:log_targets) { @options[:targets] }
165
- @registry.register(:time_logger) { TimeLogger.new } if @options[:profile]
166
-
167
- # Initialize the MultiDelegator logger
168
- logger_level = @options[:debug] ? ::Logger::DEBUG : ::Logger::WARN
169
- @logger = ::Logger.new(MultiDelegator.delegate(:write, :close).to(*@options[:targets])) # * is the splat operator
170
- @logger.level = logger_level
171
- @registry.register(:logger) { @logger }
172
-
173
- @logger.info "openstudio_2 = #{@registry[:openstudio_2]}"
174
-
175
- # get the output adapter
176
- default_output_adapter = OpenStudio::Workflow::OutputAdapter::Local.new(output_directory: @input_adapter.run_dir)
177
- @output_adapter = @input_adapter.output_adapter(user_options, default_output_adapter, @logger)
178
-
179
- # get the jobs
180
- default_jobs = OpenStudio::Workflow::Run.default_jobs
181
- @jobs = @input_adapter.jobs(user_options, default_jobs, @logger)
182
-
183
- # get other run options out of user_options and into permanent options
184
- @options[:cleanup] = @input_adapter.cleanup(user_options, true)
185
- @options[:energyplus_path] = @input_adapter.energyplus_path(user_options, nil)
186
- @options[:verify_osw] = @input_adapter.verify_osw(user_options, true)
187
- @options[:weather_file] = @input_adapter.weather_file(user_options, nil)
188
- @options[:fast] = @input_adapter.fast(user_options, false)
189
-
190
- openstudio_dir = "unknown"
191
- begin
192
- openstudio_dir = $OpenStudio_Dir
193
- if openstudio_dir.nil?
194
- openstudio_dir = OpenStudio::getOpenStudioModuleDirectory.to_s
195
- end
196
- rescue
197
- end
198
- @logger.info "openstudio_dir = #{openstudio_dir}"
199
-
200
- @logger.info "Initializing directory #{@registry[:run_dir]} for simulation with options #{@options}"
201
-
202
- # Define the state and transitions
203
- @current_state = :queued
204
- end
205
-
206
- # execute the workflow defined in the state object
207
- #
208
- # @todo add a catch if any job fails
209
- # @todo make a block method to provide feedback
210
- def run
211
- @logger.info "Starting workflow in #{@registry[:run_dir]}"
212
- begin
213
- next_state
214
- while @current_state != :finished && @current_state != :errored
215
- #sleep 2
216
- step
217
- end
218
-
219
- if !@options[:fast]
220
- @logger.info 'Finished workflow - communicating results and zipping files'
221
- @output_adapter.communicate_results(@registry[:run_dir], @registry[:results])
222
- end
223
- rescue => e
224
- @logger.info "Error occurred during running with #{e.message}"
225
- ensure
226
- @logger.info 'Workflow complete'
227
-
228
- if @current_state == :errored
229
- @registry[:workflow_json].setCompletedStatus('Fail') if @registry[:workflow_json]
230
- else
231
- # completed status will already be set if workflow was halted
232
- if @registry[:workflow_json].completedStatus.empty?
233
- @registry[:workflow_json].setCompletedStatus('Success')
234
- else
235
- @current_state = :errored if @registry[:workflow_json].completedStatus.get == 'Fail'
236
- end
237
- end
238
-
239
- # save all files before calling output adapter
240
- @registry[:log_targets].each(&:flush)
241
-
242
- # save workflow with results
243
- if @registry[:workflow_json] and !@options[:fast]
244
- out_path = @registry[:workflow_json].absoluteOutPath
245
- @registry[:workflow_json].saveAs(out_path)
246
- end
247
-
248
- # Write out the TimeLogger to the filesystem
249
- @registry[:time_logger].save(File.join(@registry[:run_dir], 'profile.json')) if @registry[:time_logger]
250
-
251
- if @current_state == :errored
252
- @output_adapter.communicate_failure
253
- else
254
- @output_adapter.communicate_complete
255
- end
256
-
257
- end
258
-
259
- @current_state
260
- end
261
-
262
- # Step through the states, if there is an error (e.g. exception) then go to error
263
- #
264
- def step
265
- step_instance = @jobs.find { |h| h[:state] == @current_state }
266
- require step_instance[:file]
267
- klass = OpenStudio::Workflow.new_class(step_instance[:job], @input_adapter, @output_adapter, @registry, @options)
268
- @output_adapter.communicate_transition("Starting state #{@current_state}", :state)
269
- state_return = klass.perform
270
- if state_return
271
- @output_adapter.communicate_transition("Returned from state #{@current_state} with message #{state_return}", :state)
272
- else
273
- @output_adapter.communicate_transition("Returned from state #{@current_state}", :state)
274
- end
275
- next_state
276
- rescue => e
277
- step_error("#{e.message}:#{e.backtrace.join("\n")}")
278
- end
279
-
280
- # Error handling for when there is an exception running any of the state transitions
281
- #
282
- def step_error(*args)
283
- # Make sure to set the instance variable @error to true in order to stop the :step
284
- # event from being fired.
285
- @final_message = "Found error in state '#{@current_state}' with message #{args}}"
286
- @logger.error @final_message
287
-
288
- # transition to an error state
289
- @current_state = :errored
290
- end
291
-
292
- # Return the finished state and exit
293
- #
294
- def run_finished(_, _, _)
295
- logger.info "Running #{__method__}"
296
-
297
- @current_state
298
- end
299
-
300
- private
301
-
302
- # Advance the @current_state to the next state
303
- #
304
- def next_state
305
- @logger.info "Current state: '#{@current_state}'"
306
- ns = @jobs.find { |h| h[:state] == @current_state }[:next_state]
307
- @logger.info "Next state will be: '#{ns}'"
308
- @current_state = ns
309
- end
310
- end
311
- end
312
- end
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