openstudio-analysis 1.3.6 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,279 +1,302 @@
1
- # *******************************************************************************
2
- # OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC.
3
- # See also https://openstudio.net/license
4
- # *******************************************************************************
5
-
6
- # OpenStudio::Analysis::Workflow configured the list of measures to run and in what order
7
- module OpenStudio
8
- module Analysis
9
- class Workflow
10
- attr_reader :items
11
- # allow users to access the items via the measures attribute accessor
12
- alias measures items
13
-
14
- # Create an instance of the OpenStudio::Analysis::Workflow
15
- #
16
- # @return [Object] An OpenStudio::Analysis::Workflow object
17
- def initialize
18
- @items = []
19
- end
20
-
21
- # Remove all the items in the workflow
22
- def clear
23
- @items.clear
24
- end
25
-
26
- # Add a measure to the workflow from a path. This will parse the measure.xml which must exist.
27
- #
28
- # @params instance_name [String] The name of the instance. This allows for multiple measures to be added to the workflow with uni que names
29
- # @params instance_display_name [String] The display name of the instance. This allows for multiple measures to be added to the workflow with unique names
30
- # @param local_path_to_measure [String] This is the local path to the measure directory, relative or absolute. It is used when zipping up all the measures.
31
- # @return [Object] Returns the measure that was added as an OpenStudio::AnalysisWorkflowStep object
32
- def add_measure_from_path(instance_name, instance_display_name, local_path_to_measure)
33
- measure_filename = 'measure.rb'
34
-
35
- if File.exist?(local_path_to_measure) && File.file?(local_path_to_measure)
36
- measure_filename = File.basename(local_path_to_measure)
37
- local_path_to_measure = File.dirname(local_path_to_measure)
38
- end
39
-
40
- if Dir.exist?(local_path_to_measure) && File.directory?(local_path_to_measure)
41
- measure_hash = nil
42
- if File.exist?(File.join(local_path_to_measure, 'measure.xml'))
43
- measure_hash = parse_measure_xml(File.join(local_path_to_measure, 'measure.xml'))
44
- else
45
- raise 'Could not find measure.xml'
46
- end
47
-
48
- add_measure(instance_name, instance_display_name, local_path_to_measure, measure_hash)
49
- else
50
- raise "could not find measure to add to workflow #{local_path_to_measure}"
51
- end
52
-
53
- @items.last
54
- end
55
-
56
- # Add a measure from the custom hash format without reading the measure.rb or measure.xml file
57
- #
58
- # @params instance_name [String] The name of the instance. This allows for multiple measures to be added to the workflow with unique names
59
- # @params instance_display_name [String] The display name of the instance. This allows for multiple measures to be added to the workflow with unique names
60
- # @param local_path_to_measure [String] This is the local path to the measure directory, relative or absolute. It is used when zipping up all the measures.
61
- # @param measure_metadata [Hash] Format of the measure.xml in JSON format
62
- # @return [Object] Returns the measure that was added as an OpenStudio::AnalysisWorkflowStep object
63
- def add_measure(instance_name, instance_display_name, local_path_to_measure, measure_metadata)
64
- @items << OpenStudio::Analysis::WorkflowStep.from_measure_hash(instance_name, instance_display_name, local_path_to_measure, measure_metadata)
65
-
66
- @items.last
67
- end
68
-
69
- # Add a measure from the analysis hash format
70
- #
71
- # @params instance_name [String] The name of the instance. This allows for multiple measures to be added to the workflow with unique names
72
- # @params instance_display_name [String] The display name of the instance. This allows for multiple measures to be added to the workflow with unique names
73
- # @param local_path_to_measure [String] This is the local path to the measure directory, relative or absolute. It is used when zipping up all the measures.
74
- # @param measure_metadata [Hash] Format of the measure.xml in JSON format
75
- # @return [Object] Returns the measure that was added as an OpenStudio::AnalysisWorkflowStep object
76
- def add_measure_from_analysis_hash(instance_name, instance_display_name, local_path_to_measure, measure_metadata)
77
- @items << OpenStudio::Analysis::WorkflowStep.from_analysis_hash(instance_name, instance_display_name, local_path_to_measure, measure_metadata)
78
-
79
- @items.last
80
- end
81
-
82
- # Add a measure from the format that Excel parses into. This is a helper method to map the excel data to the new
83
- # programmatic interface format
84
- #
85
- # @params measure [Hash] The measure in the format of the Excel translator
86
- # @return [Object] Returns the measure that was added as an OpenStudio::AnalysisWorkflowStep object
87
- def add_measure_from_excel(measure)
88
- hash = {}
89
- hash[:classname] = measure['measure_file_name']
90
- hash[:name] = measure['name']
91
- hash[:display_name] = measure['display_name']
92
- hash[:measure_type] = measure['measure_type']
93
- hash[:uid] = measure['uid'] ? measure['uid'] : SecureRandom.uuid
94
- hash[:version_id] = measure['version_id'] ? measure['version_id'] : SecureRandom.uuid
95
-
96
- # map the arguments - this can be a variable or argument, add them all as arguments first,
97
- # the make_variable will remove from arg and place into variables
98
- args = []
99
-
100
- measure['variables'].each do |variable|
101
- args << {
102
- local_variable: variable['name'],
103
- variable_type: variable['type'],
104
- name: variable['name'],
105
- display_name: variable['display_name'],
106
- display_name_short: variable['display_name_short'],
107
- units: variable['units'],
108
- default_value: variable['distribution']['static_value'],
109
- value: variable['distribution']['static_value']
110
- }
111
- end
112
- hash[:arguments] = args
113
-
114
- m = add_measure(measure['name'], measure['display_name'], measure['local_path_to_measure'], hash)
115
-
116
- measure['variables'].each do |variable|
117
- next unless ['variable', 'pivot'].include? variable['variable_type']
118
-
119
- dist = {
120
- type: variable['distribution']['type'],
121
- minimum: variable['distribution']['min'],
122
- maximum: variable['distribution']['max'],
123
- mean: variable['distribution']['mean'],
124
- standard_deviation: variable['distribution']['stddev'],
125
- values: variable['distribution']['discrete_values'],
126
- weights: variable['distribution']['discrete_weights'],
127
- step_size: variable['distribution']['delta_x']
128
- }
129
- opt = {
130
- variable_type: variable['variable_type'],
131
- variable_display_name_short: variable['display_name_short'],
132
- static_value: variable['distribution']['static_value']
133
- }
134
-
135
- m.make_variable(variable['name'], variable['display_name'], dist, opt)
136
- end
137
- end
138
-
139
- # Add a measure from the format that CSV parses into. This is a helper method to map the csv data to the new
140
- # programmatic interface format
141
- #
142
- # @params measure [Hash] The measure in the format of the CSV translator
143
- # @return [Object] Returns the measure that was added as an OpenStudio::AnalysisWorkflowStep object
144
- def add_measure_from_csv(measure)
145
- hash = {}
146
- hash[:classname] = measure[:measure_data][:classname]
147
- hash[:name] = measure[:measure_data][:name]
148
- hash[:display_name] = measure[:measure_data][:display_name]
149
- hash[:measure_type] = measure[:measure_data][:measure_type]
150
- hash[:uid] = measure[:measure_data][:uid] ? measure[:measure_data][:uid] : SecureRandom.uuid
151
- hash[:version_id] = measure[:measure_data][:version_id] ? measure[:measure_data][:version_id] : SecureRandom.uuid
152
-
153
- # map the arguments - this can be a variable or argument, add them all as arguments first,
154
- # the make_variable will remove from arg and place into variables
155
- hash[:arguments] = measure[:args]
156
-
157
- m = add_measure(measure[:measure_data][:name], measure[:measure_data][:display_name], measure[:measure_data][:local_path_to_measure], hash)
158
-
159
- measure[:vars].each do |variable|
160
- next unless ['variable', 'pivot'].include? variable[:variable_type]
161
-
162
- dist = variable[:distribution]
163
- opt = {
164
- variable_type: variable[:variable_type],
165
- variable_display_name_short: variable[:display_name_short],
166
- static_value: variable[:distribution][:mode]
167
- }
168
-
169
- m.make_variable(variable[:name], variable[:display_name], dist, opt)
170
- end
171
- end
172
-
173
- # Iterate over all the WorkflowItems
174
- def each
175
- @items.each { |i| yield i }
176
- end
177
-
178
- # Find the measure by its instance name
179
- #
180
- # @params instance_name [String] instance name of the measure
181
- # @return [Object] The WorkflowStep with the instance_name
182
- def find_measure(instance_name)
183
- @items.find { |i| i.name == instance_name }
184
- end
185
- alias find_workflow_step find_measure
186
-
187
- # Return all the variables in the analysis as an array. The list that is returned is read only.
188
- #
189
- # @return [Array] All variables in the workflow
190
- def all_variables
191
- @items.map(&:variables).flatten
192
- end
193
-
194
- # Save the workflow to a hash object
195
- def to_hash(version = 1)
196
- h = nil
197
- if version == 1
198
- arr = []
199
- @items.each_with_index do |item, index|
200
- temp_h = item.to_hash(version)
201
- temp_h[:workflow_index] = index
202
-
203
- arr << temp_h
204
- end
205
-
206
- h = arr
207
- else
208
- raise "Version #{version} not yet implemented for to_hash"
209
- end
210
-
211
- h
212
- end
213
-
214
- # Save the workflow to a JSON string
215
- #
216
- # @return [String] JSON formatted string
217
- def to_json(version = 1)
218
- if version == 1
219
- JSON.pretty_generate(to_hash(version))
220
- else
221
- raise "Version #{version} not yet implemented for to_json"
222
- end
223
- end
224
-
225
- # Load from a from a hash
226
- #
227
- # @param h [Hash or String] Path to file or hash
228
- def self.load(h)
229
- # get the version of the file
230
- file_format_version = h[:file_format_version] ? h[:file_format_version] : 1
231
- puts "Parsing file version #{file_format_version}"
232
-
233
- o = OpenStudio::Analysis::Workflow.new
234
-
235
- if h[:workflow]
236
- h[:workflow].each do |wf|
237
- puts "Adding measure #{wf[:name]}"
238
-
239
- # Go though the defined measure paths and try and find the local measure
240
- local_measure_dir = nil
241
- if wf[:measure_definition_directory_local] && Dir.exist?(wf[:measure_definition_directory_local])
242
- local_measure_dir = wf[:measure_definition_directory_local]
243
- else
244
- # search in the measure paths for the measure
245
- OpenStudio::Analysis.measure_paths.each do |p|
246
- test_dir = File.join(p, File.basename(wf[:measure_definition_directory]))
247
- if Dir.exist?(test_dir)
248
- local_measure_dir = test_dir
249
- break
250
- end
251
- end
252
- end
253
-
254
- raise "Could not find local measure when loading workflow for #{wf[:measure_definition_class_name]} in #{File.basename(wf[:measure_definition_directory])}. You may have to add measure paths to OpenStudio::Analysis.measure_paths" unless local_measure_dir
255
-
256
- o.add_measure_from_analysis_hash(wf[:name], wf[:display_name], local_measure_dir, wf)
257
- end
258
- end
259
- o
260
- end
261
-
262
- # Read the Workflow description from a persisted file. The format at the moment is the current analysis.json
263
- #
264
- # @params filename [String] Path to file with the analysis.json to load
265
- # @return [Object] Return an instance of the workflow object
266
- def self.from_file(filename)
267
- o = nil
268
- if File.exist? filename
269
- j = JSON.parse(File.read(filename), symbolize_names: true)
270
- o = OpenStudio::Analysis::Workflow.load(j)
271
- else
272
- raise "Could not find workflow file #{filename}"
273
- end
274
-
275
- o
276
- end
277
- end
278
- end
279
- end
1
+ # *******************************************************************************
2
+ # OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC.
3
+ # See also https://openstudio.net/license
4
+ # *******************************************************************************
5
+
6
+ # OpenStudio::Analysis::Workflow configured the list of measures to run and in what order
7
+ module OpenStudio
8
+ module Analysis
9
+ class Workflow
10
+ attr_reader :items
11
+ # allow users to access the items via the measures attribute accessor
12
+ alias measures items
13
+
14
+ # Create an instance of the OpenStudio::Analysis::Workflow
15
+ #
16
+ # @return [Object] An OpenStudio::Analysis::Workflow object
17
+ def initialize
18
+ @items = []
19
+ end
20
+
21
+ # Remove all the items in the workflow
22
+ def clear
23
+ @items.clear
24
+ end
25
+
26
+ # Add a measure to the workflow from a path. This will parse the measure.xml which must exist.
27
+ #
28
+ # @params instance_name [String] The name of the instance. This allows for multiple measures to be added to the workflow with uni que names
29
+ # @params instance_display_name [String] The display name of the instance. This allows for multiple measures to be added to the workflow with unique names
30
+ # @param local_path_to_measure [String] This is the local path to the measure directory, relative or absolute. It is used when zipping up all the measures.
31
+ # @return [Object] Returns the measure that was added as an OpenStudio::AnalysisWorkflowStep object
32
+ def add_measure_from_path(instance_name, instance_display_name, local_path_to_measure)
33
+ measure_filename = 'measure.rb'
34
+
35
+ if File.exist?(local_path_to_measure) && File.file?(local_path_to_measure)
36
+ measure_filename = File.basename(local_path_to_measure)
37
+ local_path_to_measure = File.dirname(local_path_to_measure)
38
+ end
39
+
40
+ if Dir.exist?(local_path_to_measure) && File.directory?(local_path_to_measure)
41
+ measure_hash = nil
42
+ if File.exist?(File.join(local_path_to_measure, 'measure.xml'))
43
+ measure_hash = parse_measure_xml(File.join(local_path_to_measure, 'measure.xml'))
44
+ else
45
+ raise 'Could not find measure.xml'
46
+ end
47
+
48
+ add_measure(instance_name, instance_display_name, local_path_to_measure, measure_hash)
49
+ else
50
+ raise "could not find measure to add to workflow #{local_path_to_measure}"
51
+ end
52
+
53
+ @items.last
54
+ end
55
+
56
+ # Add a measure from the custom hash format without reading the measure.rb or measure.xml file
57
+ #
58
+ # @params instance_name [String] The name of the instance. This allows for multiple measures to be added to the workflow with unique names
59
+ # @params instance_display_name [String] The display name of the instance. This allows for multiple measures to be added to the workflow with unique names
60
+ # @param local_path_to_measure [String] This is the local path to the measure directory, relative or absolute. It is used when zipping up all the measures.
61
+ # @param measure_metadata [Hash] Format of the measure.xml in JSON format
62
+ # @return [Object] Returns the measure that was added as an OpenStudio::AnalysisWorkflowStep object
63
+ def add_measure(instance_name, instance_display_name, local_path_to_measure, measure_metadata)
64
+ @items << OpenStudio::Analysis::WorkflowStep.from_measure_hash(instance_name, instance_display_name, local_path_to_measure, measure_metadata)
65
+
66
+ @items.last
67
+ end
68
+
69
+ # Add a measure from the analysis hash format
70
+ #
71
+ # @params instance_name [String] The name of the instance. This allows for multiple measures to be added to the workflow with unique names
72
+ # @params instance_display_name [String] The display name of the instance. This allows for multiple measures to be added to the workflow with unique names
73
+ # @param local_path_to_measure [String] This is the local path to the measure directory, relative or absolute. It is used when zipping up all the measures.
74
+ # @param measure_metadata [Hash] Format of the measure.xml in JSON format
75
+ # @return [Object] Returns the measure that was added as an OpenStudio::AnalysisWorkflowStep object
76
+ def add_measure_from_analysis_hash(instance_name, instance_display_name, local_path_to_measure, measure_metadata)
77
+ @items << OpenStudio::Analysis::WorkflowStep.from_analysis_hash(instance_name, instance_display_name, local_path_to_measure, measure_metadata)
78
+
79
+ @items.last
80
+ end
81
+
82
+ # Add a measure from the format that Excel parses into. This is a helper method to map the excel data to the new
83
+ # programmatic interface format
84
+ #
85
+ # @params measure [Hash] The measure in the format of the Excel translator
86
+ # @return [Object] Returns the measure that was added as an OpenStudio::AnalysisWorkflowStep object
87
+ def add_measure_from_excel(measure)
88
+ hash = {}
89
+ hash[:classname] = measure['measure_file_name']
90
+ hash[:name] = measure['name']
91
+ hash[:display_name] = measure['display_name']
92
+ hash[:measure_type] = measure['measure_type']
93
+ hash[:uid] = measure['uid'] ? measure['uid'] : SecureRandom.uuid
94
+ hash[:version_id] = measure['version_id'] ? measure['version_id'] : SecureRandom.uuid
95
+
96
+ # map the arguments - this can be a variable or argument, add them all as arguments first,
97
+ # the make_variable will remove from arg and place into variables
98
+ args = []
99
+
100
+ measure['variables'].each do |variable|
101
+ args << {
102
+ local_variable: variable['name'],
103
+ variable_type: variable['type'],
104
+ name: variable['name'],
105
+ display_name: variable['display_name'],
106
+ display_name_short: variable['display_name_short'],
107
+ units: variable['units'],
108
+ default_value: variable['distribution']['static_value'],
109
+ value: variable['distribution']['static_value']
110
+ }
111
+ end
112
+ hash[:arguments] = args
113
+
114
+ m = add_measure(measure['name'], measure['display_name'], measure['local_path_to_measure'], hash)
115
+
116
+ measure['variables'].each do |variable|
117
+ next unless ['variable', 'pivot'].include? variable['variable_type']
118
+
119
+ dist = {
120
+ type: variable['distribution']['type'],
121
+ minimum: variable['distribution']['min'],
122
+ maximum: variable['distribution']['max'],
123
+ mean: variable['distribution']['mean'],
124
+ standard_deviation: variable['distribution']['stddev'],
125
+ values: variable['distribution']['discrete_values'],
126
+ weights: variable['distribution']['discrete_weights'],
127
+ step_size: variable['distribution']['delta_x']
128
+ }
129
+ opt = {
130
+ variable_type: variable['variable_type'],
131
+ variable_display_name_short: variable['display_name_short'],
132
+ static_value: variable['distribution']['static_value']
133
+ }
134
+
135
+ m.make_variable(variable['name'], variable['display_name'], dist, opt)
136
+ end
137
+ end
138
+
139
+ # Add a measure from the format that CSV parses into. This is a helper method to map the csv data to the new
140
+ # programmatic interface format
141
+ #
142
+ # @params measure [Hash] The measure in the format of the CSV translator
143
+ # @return [Object] Returns the measure that was added as an OpenStudio::AnalysisWorkflowStep object
144
+ def add_measure_from_csv(measure)
145
+ hash = {}
146
+ hash[:classname] = measure[:measure_data][:classname]
147
+ hash[:name] = measure[:measure_data][:name]
148
+ hash[:display_name] = measure[:measure_data][:display_name]
149
+ hash[:measure_type] = measure[:measure_data][:measure_type]
150
+ hash[:uid] = measure[:measure_data][:uid] ? measure[:measure_data][:uid] : SecureRandom.uuid
151
+ hash[:version_id] = measure[:measure_data][:version_id] ? measure[:measure_data][:version_id] : SecureRandom.uuid
152
+
153
+ # map the arguments - this can be a variable or argument, add them all as arguments first,
154
+ # the make_variable will remove from arg and place into variables
155
+ hash[:arguments] = measure[:args]
156
+
157
+ m = add_measure(measure[:measure_data][:name], measure[:measure_data][:display_name], measure[:measure_data][:local_path_to_measure], hash)
158
+
159
+ measure[:vars].each do |variable|
160
+ next unless ['variable', 'pivot'].include? variable[:variable_type]
161
+
162
+ dist = variable[:distribution]
163
+ opt = {
164
+ variable_type: variable[:variable_type],
165
+ variable_display_name_short: variable[:display_name_short],
166
+ static_value: variable[:distribution][:mode]
167
+ }
168
+
169
+ m.make_variable(variable[:name], variable[:display_name], dist, opt)
170
+ end
171
+ end
172
+
173
+ # Iterate over all the WorkflowItems
174
+ def each
175
+ @items.each { |i| yield i }
176
+ end
177
+
178
+ # Find the measure by its instance name
179
+ #
180
+ # @params instance_name [String] instance name of the measure
181
+ # @return [Object] The WorkflowStep with the instance_name
182
+ def find_measure(instance_name)
183
+ @items.find { |i| i.name == instance_name }
184
+ end
185
+ alias find_workflow_step find_measure
186
+
187
+ # Move a measure by its name to after another measure by its name,
188
+ # error if there is no measure with the name. If no after measure
189
+ # name is passed, then it will be at the beginning
190
+ #
191
+ # @params measure_name [String] instance name of the measure
192
+ # @params after_measure_name [String] instance name of the measure to move after
193
+ def move_measure_after(measure_name, after_measure_name=nil)
194
+ measure = self.find_measure(measure_name)
195
+ raise "Could not find measure with name #{measure_name}" unless measure
196
+
197
+ if after_measure_name.nil?
198
+ # put the measure at the beginning
199
+ @items.insert(0, @items.delete(measure))
200
+ else
201
+ after_measure = self.find_measure(after_measure_name)
202
+ raise "Could not find measure with name #{after_measure_name}" unless after_measure
203
+
204
+ # the index will be the index of the after measure plus 1 or the len of the list
205
+ idx = [@items.index(after_measure)+1, @items.length-1].min
206
+ @items.insert(idx, @items.delete(measure))
207
+ end
208
+ end
209
+
210
+ # Return all the variables in the analysis as an array. The list that is returned is read only.
211
+ #
212
+ # @return [Array] All variables in the workflow
213
+ def all_variables
214
+ @items.map(&:variables).flatten
215
+ end
216
+
217
+ # Save the workflow to a hash object
218
+ def to_hash(version = 1)
219
+ h = nil
220
+ if version == 1
221
+ arr = []
222
+ @items.each_with_index do |item, index|
223
+ temp_h = item.to_hash(version)
224
+ temp_h[:workflow_index] = index
225
+
226
+ arr << temp_h
227
+ end
228
+
229
+ h = arr
230
+ else
231
+ raise "Version #{version} not yet implemented for to_hash"
232
+ end
233
+
234
+ h
235
+ end
236
+
237
+ # Save the workflow to a JSON string
238
+ #
239
+ # @return [String] JSON formatted string
240
+ def to_json(version = 1)
241
+ if version == 1
242
+ JSON.pretty_generate(to_hash(version))
243
+ else
244
+ raise "Version #{version} not yet implemented for to_json"
245
+ end
246
+ end
247
+
248
+ # Load from a from a hash
249
+ #
250
+ # @param h [Hash or String] Path to file or hash
251
+ def self.load(h)
252
+ # get the version of the file
253
+ file_format_version = h[:file_format_version] ? h[:file_format_version] : 1
254
+ puts "Parsing file version #{file_format_version}"
255
+
256
+ o = OpenStudio::Analysis::Workflow.new
257
+
258
+ if h[:workflow]
259
+ h[:workflow].each do |wf|
260
+ puts "Adding measure #{wf[:name]}"
261
+
262
+ # Go though the defined measure paths and try and find the local measure
263
+ local_measure_dir = nil
264
+ if wf[:measure_definition_directory_local] && Dir.exist?(wf[:measure_definition_directory_local])
265
+ local_measure_dir = wf[:measure_definition_directory_local]
266
+ else
267
+ # search in the measure paths for the measure
268
+ OpenStudio::Analysis.measure_paths.each do |p|
269
+ test_dir = File.join(p, File.basename(wf[:measure_definition_directory]))
270
+ if Dir.exist?(test_dir)
271
+ local_measure_dir = test_dir
272
+ break
273
+ end
274
+ end
275
+ end
276
+
277
+ raise "Could not find local measure when loading workflow for #{wf[:measure_definition_class_name]} in #{File.basename(wf[:measure_definition_directory])}. You may have to add measure paths to OpenStudio::Analysis.measure_paths" unless local_measure_dir
278
+
279
+ o.add_measure_from_analysis_hash(wf[:name], wf[:display_name], local_measure_dir, wf)
280
+ end
281
+ end
282
+ o
283
+ end
284
+
285
+ # Read the Workflow description from a persisted file. The format at the moment is the current analysis.json
286
+ #
287
+ # @params filename [String] Path to file with the analysis.json to load
288
+ # @return [Object] Return an instance of the workflow object
289
+ def self.from_file(filename)
290
+ o = nil
291
+ if File.exist? filename
292
+ j = JSON.parse(File.read(filename), symbolize_names: true)
293
+ o = OpenStudio::Analysis::Workflow.load(j)
294
+ else
295
+ raise "Could not find workflow file #{filename}"
296
+ end
297
+
298
+ o
299
+ end
300
+ end
301
+ end
302
+ end