urbanopt-reopt 0.3.0 → 0.5.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/pull_request_template.md +2 -2
- data/.gitignore +3 -0
- data/CHANGELOG.md +73 -0
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +2 -5
- data/LICENSE.md +1 -1
- data/RDOC_MAIN.md +13 -13
- data/README.md +15 -15
- data/Rakefile +30 -0
- data/doc_templates/LICENSE.md +1 -1
- data/doc_templates/copyright_erb.txt +1 -1
- data/doc_templates/copyright_js.txt +1 -1
- data/doc_templates/copyright_ruby.txt +1 -1
- data/docs/README.md +16 -16
- data/docs/package-lock.json +10084 -151
- data/docs/package.json +8 -4
- data/index.md +13 -13
- data/lib/urbanopt-reopt.rb +1 -1
- data/lib/urbanopt/reopt.rb +1 -1
- data/lib/urbanopt/reopt/extension.rb +1 -1
- data/lib/urbanopt/reopt/feature_report_adapter.rb +109 -85
- data/lib/urbanopt/reopt/reopt_lite_api.rb +117 -12
- data/lib/urbanopt/reopt/reopt_logger.rb +1 -1
- data/lib/urbanopt/reopt/reopt_post_processor.rb +57 -24
- data/lib/urbanopt/reopt/scenario/reopt_scenario_csv.rb +1 -1
- data/lib/urbanopt/reopt/scenario_report_adapter.rb +113 -94
- data/lib/urbanopt/reopt/utilities.rb +111 -0
- data/lib/urbanopt/reopt/version.rb +2 -2
- data/lib/urbanopt/reopt_scenario.rb +1 -1
- data/urbanopt-reopt.gemspec +8 -5
- metadata +33 -18
- data/.travis.yml +0 -22
@@ -1,5 +1,5 @@
|
|
1
1
|
# *********************************************************************************
|
2
|
-
# URBANopt, Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
|
2
|
+
# URBANopt (tm), Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
|
3
3
|
# contributors. All rights reserved.
|
4
4
|
#
|
5
5
|
# Redistribution and use in source and binary forms, with or without modification,
|
@@ -56,6 +56,7 @@ module URBANopt # :nodoc:
|
|
56
56
|
@use_localhost = use_localhost
|
57
57
|
if @use_localhost
|
58
58
|
@uri_submit = URI.parse('http//:127.0.0.1:8000/v1/job/')
|
59
|
+
@uri_submit_outagesimjob = URI.parse('http//:127.0.0.1:8000/v1/outagesimjob/')
|
59
60
|
else
|
60
61
|
if [nil, '', '<insert your key here>'].include? nrel_developer_key
|
61
62
|
if [nil, '', '<insert your key here>'].include? DEVELOPER_NREL_KEY
|
@@ -66,6 +67,7 @@ module URBANopt # :nodoc:
|
|
66
67
|
end
|
67
68
|
@nrel_developer_key = nrel_developer_key
|
68
69
|
@uri_submit = URI.parse("https://developer.nrel.gov/api/reopt/v1/job/?api_key=#{@nrel_developer_key}")
|
70
|
+
@uri_submit_outagesimjob = URI.parse("https://developer.nrel.gov/api/reopt/v1/outagesimjob/?api_key=#{@nrel_developer_key}")
|
69
71
|
# initialize @@logger
|
70
72
|
@@logger ||= URBANopt::REopt.reopt_logger
|
71
73
|
end
|
@@ -88,6 +90,23 @@ module URBANopt # :nodoc:
|
|
88
90
|
return URI.parse("https://developer.nrel.gov/api/reopt/v1/job/#{run_uuid}/results?api_key=#{@nrel_developer_key}")
|
89
91
|
end
|
90
92
|
|
93
|
+
##
|
94
|
+
# URL of the resilience statistics end point for a specific optimization task
|
95
|
+
##
|
96
|
+
#
|
97
|
+
# [*parameters:*]
|
98
|
+
#
|
99
|
+
# * +run_uuid+ - _String_ - Resilience statistics for a unique run_uuid obtained from the \REopt Lite job submittal URL for a specific optimization task.
|
100
|
+
#
|
101
|
+
# [*return:*] _URI_ - Returns URI object for use in calling the \REopt Lite resilience statistics endpoint for a specifc optimization task.
|
102
|
+
##
|
103
|
+
def uri_resilience(run_uuid) # :nodoc:
|
104
|
+
if @use_localhost
|
105
|
+
return URI.parse("http://127.0.0.1:8000/v1/job/#{run_uuid}/resilience_stats")
|
106
|
+
end
|
107
|
+
return URI.parse("https://developer.nrel.gov/api/reopt/v1/job/#{run_uuid}/resilience_stats?api_key=#{@nrel_developer_key}")
|
108
|
+
end
|
109
|
+
|
91
110
|
def make_request(http, r, max_tries = 3)
|
92
111
|
result = nil
|
93
112
|
tries = 0
|
@@ -117,10 +136,10 @@ module URBANopt # :nodoc:
|
|
117
136
|
http = Net::HTTP.new(@uri_submit.host, @uri_submit.port)
|
118
137
|
if !@use_localhost
|
119
138
|
http.use_ssl = true
|
120
|
-
|
139
|
+
end
|
121
140
|
|
122
141
|
request = Net::HTTP::Post.new(@uri_submit, header)
|
123
|
-
request.body = data
|
142
|
+
request.body = ::JSON.generate(data, allow_nan: true)
|
124
143
|
|
125
144
|
# Send the request
|
126
145
|
response = make_request(http, request)
|
@@ -132,6 +151,82 @@ module URBANopt # :nodoc:
|
|
132
151
|
return true
|
133
152
|
end
|
134
153
|
|
154
|
+
##
|
155
|
+
# Completes a \REopt Lite optimization. From a formatted hash, an optimization task is submitted to the API.
|
156
|
+
# Results are polled at 5 second interval until they are ready or an error is returned from the API. Results
|
157
|
+
# are written to disk.
|
158
|
+
##
|
159
|
+
#
|
160
|
+
# [*parameters:*]
|
161
|
+
#
|
162
|
+
# * +reopt_input+ - _Hash_ - \REopt Lite formatted post containing at least required parameters.
|
163
|
+
# * +filename+ - _String_ - Path to file that will be created containing the full \REopt Lite response.
|
164
|
+
#
|
165
|
+
# [*return:*] _Bool_ - Returns true if the post succeeeds. Otherwise returns false.
|
166
|
+
##
|
167
|
+
def resilience_request(run_uuid, filename)
|
168
|
+
|
169
|
+
if File.directory? filename
|
170
|
+
if run_uuid.nil?
|
171
|
+
run_uuid = 'error'
|
172
|
+
end
|
173
|
+
if run_uuid.downcase.include? 'error'
|
174
|
+
run_uuid = "error#{SecureRandom.uuid}"
|
175
|
+
end
|
176
|
+
filename = File.join(filename, "#{run_uuid}_resilience.json")
|
177
|
+
@@logger.info("REopt results saved to #{filename}")
|
178
|
+
end
|
179
|
+
|
180
|
+
#Submit Job
|
181
|
+
@@logger.info("Submitting Resilience Statistics job for #{run_uuid}")
|
182
|
+
header = { 'Content-Type' => 'application/json' }
|
183
|
+
http = Net::HTTP.new(@uri_submit_outagesimjob.host, @uri_submit_outagesimjob.port)
|
184
|
+
if !@use_localhost
|
185
|
+
http.use_ssl = true
|
186
|
+
end
|
187
|
+
request = Net::HTTP::Post.new(@uri_submit_outagesimjob, header)
|
188
|
+
request.body = ::JSON.generate({"run_uuid" => run_uuid, "bau" => false }, allow_nan: true)
|
189
|
+
submit_response = make_request(http, request)
|
190
|
+
@@logger.info(submit_response.body)
|
191
|
+
|
192
|
+
#Fetch Results
|
193
|
+
uri = uri_resilience(run_uuid)
|
194
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
195
|
+
if !@use_localhost
|
196
|
+
http.use_ssl = true
|
197
|
+
end
|
198
|
+
|
199
|
+
elapsed_time = 0
|
200
|
+
max_elapsed_time = 60 * 5
|
201
|
+
|
202
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
203
|
+
response = make_request(http, request)
|
204
|
+
|
205
|
+
while (elapsed_time < max_elapsed_time) & (response.code == "404")
|
206
|
+
response = make_request(http, request)
|
207
|
+
elapsed_time += 5
|
208
|
+
sleep 5
|
209
|
+
end
|
210
|
+
|
211
|
+
data = JSON.parse(response.body)
|
212
|
+
text = ::JSON.generate(data, allow_nan: true)
|
213
|
+
begin
|
214
|
+
File.open(filename, 'w+') do |f|
|
215
|
+
f.puts(text)
|
216
|
+
end
|
217
|
+
rescue
|
218
|
+
@@logger.info("Cannot write - #{filename}")
|
219
|
+
end
|
220
|
+
|
221
|
+
if response.code == "200"
|
222
|
+
return data
|
223
|
+
end
|
224
|
+
|
225
|
+
@@logger.info("Error from REopt API - #{data['Error']}")
|
226
|
+
return {}
|
227
|
+
|
228
|
+
end
|
229
|
+
|
135
230
|
##
|
136
231
|
# Completes a \REopt Lite optimization. From a formatted hash, an optimization task is submitted to the API.
|
137
232
|
# Results are polled at 5 second interval until they are ready or an error is returned from the API. Results
|
@@ -157,13 +252,13 @@ module URBANopt # :nodoc:
|
|
157
252
|
http.use_ssl = true
|
158
253
|
end
|
159
254
|
request = Net::HTTP::Post.new(@uri_submit, header)
|
160
|
-
request.body = reopt_input
|
161
|
-
|
255
|
+
request.body = ::JSON.generate(reopt_input, allow_nan: true)
|
256
|
+
|
162
257
|
# Send the request
|
163
258
|
response = make_request(http, request)
|
164
259
|
|
165
260
|
# Get UUID
|
166
|
-
run_uuid = JSON.parse(response.body)['run_uuid']
|
261
|
+
run_uuid = JSON.parse(response.body, allow_nan:true)['run_uuid']
|
167
262
|
|
168
263
|
if File.directory? filename
|
169
264
|
if run_uuid.nil?
|
@@ -176,10 +271,12 @@ module URBANopt # :nodoc:
|
|
176
271
|
@@logger.info("REopt results saved to #{filename}")
|
177
272
|
end
|
178
273
|
|
274
|
+
text = ::JSON.generate(response.body, allow_nan: true)
|
179
275
|
if response.code != '201'
|
180
|
-
File.open(filename, 'w') do |f|
|
181
|
-
f.
|
276
|
+
File.open(filename, 'w+') do |f|
|
277
|
+
f.puts(text)
|
182
278
|
end
|
279
|
+
@@logger.info("Cannot write - #{filename}")
|
183
280
|
raise "Error in REopt optimization post - see #{filename}"
|
184
281
|
end
|
185
282
|
|
@@ -195,7 +292,9 @@ module URBANopt # :nodoc:
|
|
195
292
|
|
196
293
|
while status == 'Optimizing...'
|
197
294
|
response = make_request(http, request)
|
198
|
-
|
295
|
+
|
296
|
+
data = JSON.parse(response.body, allow_nan:true)
|
297
|
+
|
199
298
|
if data['outputs']['Scenario']['Site']['PV'].kind_of?(Array)
|
200
299
|
pv_sizes = 0
|
201
300
|
data['outputs']['Scenario']['Site']['PV'].each do |x|
|
@@ -216,7 +315,7 @@ module URBANopt # :nodoc:
|
|
216
315
|
while (_tries < _max_retry) && check_complete
|
217
316
|
sleep 1
|
218
317
|
response = make_request(http, request)
|
219
|
-
data = JSON.parse(response.body)
|
318
|
+
data = JSON.parse(response.body, allow_nan:true)
|
220
319
|
if data['outputs']['Scenario']['Site']['PV'].kind_of?(Array)
|
221
320
|
pv_sizes = 0
|
222
321
|
data['outputs']['Scenario']['Site']['PV'].each do |x|
|
@@ -230,8 +329,14 @@ module URBANopt # :nodoc:
|
|
230
329
|
_tries += 1
|
231
330
|
end
|
232
331
|
|
233
|
-
|
234
|
-
|
332
|
+
data = JSON.parse(response.body)
|
333
|
+
text = ::JSON.generate(data, allow_nan: true)
|
334
|
+
begin
|
335
|
+
File.open(filename, 'w+') do |f|
|
336
|
+
f.puts(text)
|
337
|
+
end
|
338
|
+
rescue
|
339
|
+
@@logger.info("Cannot write - #{filename}")
|
235
340
|
end
|
236
341
|
|
237
342
|
if status == 'optimal'
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# *********************************************************************************
|
2
|
-
# URBANopt, Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
|
2
|
+
# URBANopt (tm), Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
|
3
3
|
# contributors. All rights reserved.
|
4
4
|
#
|
5
5
|
# Redistribution and use in source and binary forms, with or without modification,
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# *********************************************************************************
|
2
|
-
# URBANopt, Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
|
2
|
+
# URBANopt (tm), Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
|
3
3
|
# contributors. All rights reserved.
|
4
4
|
#
|
5
5
|
# Redistribution and use in source and binary forms, with or without modification,
|
@@ -29,9 +29,8 @@
|
|
29
29
|
# *********************************************************************************
|
30
30
|
|
31
31
|
require 'bundler/setup'
|
32
|
-
require 'urbanopt/
|
32
|
+
require 'urbanopt/reporting/default_reports'
|
33
33
|
require 'urbanopt/reopt/reopt_logger'
|
34
|
-
require 'urbanopt/reopt'
|
35
34
|
require 'csv'
|
36
35
|
|
37
36
|
module URBANopt # :nodoc:
|
@@ -43,7 +42,7 @@ module URBANopt # :nodoc:
|
|
43
42
|
#
|
44
43
|
# [*parameters:*]
|
45
44
|
#
|
46
|
-
# * +scenario_report+ - _ScenarioReport_ - Optional. A scenario report that has been returned from the URBANopt::
|
45
|
+
# * +scenario_report+ - _ScenarioReport_ - Optional. A scenario report that has been returned from the URBANopt::Reporting::ScenarioDefaultPostProcessor - used in creating default output file names in \REopt Lite optimizations.
|
47
46
|
# * +scenario_reopt_assumptions_file+ - _String_ - Optional. JSON file formatted for a \REopt Lite analysis containing custom input parameters for optimizations at the Scenario Report level
|
48
47
|
# * +reopt_feature_assumptions+ - _Array_ - Optional. A list of JSON file formatted for a \REopt Lite analysis containing custom input parameters for optimizations at the Feature Report level. The order and number of files must match the Feature Reports in the scenario_report input.
|
49
48
|
# * +use_localhost+ - _Bool_ - If this is true, requests will be sent to a version of the \REopt Lite API running on localhost. Default is false, such that the production version of \REopt Lite is accessed.
|
@@ -70,8 +69,9 @@ module URBANopt # :nodoc:
|
|
70
69
|
if !scenario_report.nil?
|
71
70
|
@scenario_report = scenario_report
|
72
71
|
|
73
|
-
if
|
72
|
+
if !Dir.exist?(File.join(@scenario_report.directory_name, "reopt"))
|
74
73
|
Dir.mkdir(File.join(@scenario_report.directory_name, "reopt"))
|
74
|
+
@@logger.info("Created directory: " + File.join(@scenario_report.directory_name, "reopt"))
|
75
75
|
end
|
76
76
|
|
77
77
|
@scenario_reopt_default_output_file = File.join(@scenario_report.directory_name, "reopt/scenario_report_#{@scenario_report.id}_reopt_run.json")
|
@@ -80,6 +80,7 @@ module URBANopt # :nodoc:
|
|
80
80
|
@scenario_report.feature_reports.each do |fr|
|
81
81
|
if !Dir.exist?(File.join(fr.directory_name, "reopt"))
|
82
82
|
Dir.mkdir(File.join(fr.directory_name, "reopt"))
|
83
|
+
@@logger.info("Created directory: " + File.join(fr.directory_name, "reopt"))
|
83
84
|
end
|
84
85
|
@feature_reports_reopt_default_output_files << File.join(fr.directory_name, "reopt/feature_report_#{fr.id}_reopt_run.json")
|
85
86
|
end
|
@@ -111,25 +112,36 @@ module URBANopt # :nodoc:
|
|
111
112
|
#
|
112
113
|
# [*parameters:*]
|
113
114
|
#
|
114
|
-
# * +feature_report+ - _URBANopt::
|
115
|
+
# * +feature_report+ - _URBANopt::Reporting::DefaultReports::FeatureReport_ - FeatureReport which will be used in creating and then updated by a \REopt Lite opimization response.
|
115
116
|
# * +reopt_assumptions_hash+ - _Hash_ - Optional. A \REopt Lite formatted hash containing default parameters (i.e. utility rate, escalation rate) which will be updated by the FeatureReport (i.e. location, roof availability)
|
116
117
|
# * +reopt_output_file+ - _String_ - Optional. Path to a file at which REpopt Lite responses will be saved.
|
117
118
|
# * +timeseries_csv_path+ - _String_ - Optional. Path to a file at which the new timeseries CSV for the FeatureReport will be saved.
|
118
119
|
#
|
119
|
-
# [*return:*] _URBANopt::
|
120
|
+
# [*return:*] _URBANopt::Reporting::DefaultReports::FeatureReport_ - Returns an updated FeatureReport
|
120
121
|
##
|
121
|
-
def run_feature_report(feature_report:, reopt_assumptions_hash:nil, reopt_output_file:nil, timeseries_csv_path:nil, save_name:nil)
|
122
|
+
def run_feature_report(feature_report:, reopt_assumptions_hash:nil, reopt_output_file:nil, timeseries_csv_path:nil, save_name:nil, run_resilience:true)
|
122
123
|
api = URBANopt::REopt::REoptLiteAPI.new(@nrel_developer_key, @localhost)
|
123
124
|
adapter = URBANopt::REopt::FeatureReportAdapter.new
|
124
125
|
|
125
126
|
reopt_input = adapter.reopt_json_from_feature_report(feature_report, reopt_assumptions_hash)
|
126
127
|
if reopt_output_file.nil?
|
127
|
-
reopt_output_file = feature_report.directory_name
|
128
|
+
reopt_output_file = File.join(feature_report.directory_name, 'reopt')
|
128
129
|
end
|
129
130
|
reopt_output = api.reopt_request(reopt_input, reopt_output_file)
|
130
|
-
|
131
|
+
if run_resilience
|
132
|
+
run_uuid = reopt_output['outputs']['Scenario']['run_uuid']
|
133
|
+
if File.directory? reopt_output_file
|
134
|
+
resilience_stats = api.resilience_request(run_uuid, reopt_output_file)
|
135
|
+
else
|
136
|
+
resilience_stats = api.resilience_request(run_uuid, reopt_output_file.sub!('.json','_resilience.json'))
|
137
|
+
end
|
138
|
+
else
|
139
|
+
resilience_stats = nil
|
140
|
+
end
|
141
|
+
result = adapter.update_feature_report(feature_report, reopt_output, timeseries_csv_path, resilience_stats)
|
131
142
|
if !save_name.nil?
|
132
|
-
result.save_feature_report save_name
|
143
|
+
#result.save_feature_report save_name
|
144
|
+
result.save_json_report(save_name)
|
133
145
|
end
|
134
146
|
return result
|
135
147
|
end
|
@@ -140,13 +152,13 @@ module URBANopt # :nodoc:
|
|
140
152
|
#
|
141
153
|
# [*parameters:*]
|
142
154
|
#
|
143
|
-
# * +feature_report+ - _URBANopt::
|
155
|
+
# * +feature_report+ - _URBANopt::Reporting::DefaultReports::ScenarioReport_ - ScenarioReport which will be used in creating and then updated by a \REopt Lite opimization response.
|
144
156
|
# * +reopt_assumptions_hash+ - _Hash_ - Optional. A \REopt Lite formatted hash containing default parameters (i.e. utility rate, escalation rate) which will be updated by the ScenarioReport (i.e. location, roof availability)
|
145
157
|
# * +reopt_output_file+ - _String_ - Optional. Path to a file at which REpopt Lite responses will be saved.
|
146
158
|
# * +timeseries_csv_path+ - _String_ - Optional. Path to a file at which the new timeseries CSV for the ScenarioReport will be saved.
|
147
159
|
#
|
148
160
|
# [*return:*] _URBANopt::Scenario::DefaultReports::ScenarioReport_ Returns an updated ScenarioReport
|
149
|
-
def run_scenario_report(scenario_report:, reopt_assumptions_hash:nil, reopt_output_file:nil, timeseries_csv_path:nil, save_name:nil)
|
161
|
+
def run_scenario_report(scenario_report:, reopt_assumptions_hash:nil, reopt_output_file:nil, timeseries_csv_path:nil, save_name:nil, run_resilience:true)
|
150
162
|
if !reopt_assumptions_hash.nil?
|
151
163
|
@scenario_reopt_default_assumptions_hash = reopt_assumptions_hash
|
152
164
|
end
|
@@ -163,8 +175,18 @@ module URBANopt # :nodoc:
|
|
163
175
|
reopt_input = adapter.reopt_json_from_scenario_report(scenario_report, @scenario_reopt_default_assumptions_hash)
|
164
176
|
|
165
177
|
reopt_output = api.reopt_request(reopt_input, @scenario_reopt_default_output_file)
|
178
|
+
if run_resilience
|
179
|
+
run_uuid = reopt_output['outputs']['Scenario']['run_uuid']
|
180
|
+
if File.directory? @scenario_reopt_default_output_file
|
181
|
+
resilience_stats = api.resilience_request(run_uuid, @scenario_reopt_default_output_file)
|
182
|
+
else
|
183
|
+
resilience_stats = api.resilience_request(run_uuid, @scenario_reopt_default_output_file.sub!('.json','_resilience.json'))
|
184
|
+
end
|
185
|
+
else
|
186
|
+
resilience_stats = nil
|
187
|
+
end
|
166
188
|
|
167
|
-
result = adapter.update_scenario_report(scenario_report, reopt_output, @scenario_timeseries_default_output_file)
|
189
|
+
result = adapter.update_scenario_report(scenario_report, reopt_output, @scenario_timeseries_default_output_file, resilience_stats)
|
168
190
|
if !save_name.nil?
|
169
191
|
result.save save_name
|
170
192
|
end
|
@@ -176,13 +198,13 @@ module URBANopt # :nodoc:
|
|
176
198
|
#
|
177
199
|
# [*parameters:*]
|
178
200
|
#
|
179
|
-
# * +feature_reports+ - _Array_ - An array of _URBANopt::
|
201
|
+
# * +feature_reports+ - _Array_ - An array of _URBANopt::Reporting::DefaultReports::FeatureReport_ objetcs which will each be used to create (and are subsquently updated by) a \REopt Lite opimization response.
|
180
202
|
# * +reopt_assumptions_hashes+ - _Array_ - Optional. An array of \REopt Lite formatted hashes containing default parameters (i.e. utility rate, escalation rate) which will be updated by the ScenarioReport (i.e. location, roof availability). The number and order of the hashes should match the feature_reports array.
|
181
203
|
# * +reopt_output_files+ - _Array_ - Optional. A array of paths to files at which REpopt Lite responses will be saved. The number and order of the paths should match the feature_reports array.
|
182
204
|
# * +timeseries_csv_path+ - _Array_ - Optional. A array of paths to files at which the new timeseries CSV for the FeatureReports will be saved. The number and order of the paths should match the feature_reports array.
|
183
205
|
#
|
184
206
|
# [*return:*] _Array_ Returns an array of updated _URBANopt::Scenario::DefaultReports::FeatureReport_ objects
|
185
|
-
def run_feature_reports(feature_reports:, reopt_assumptions_hashes:[], reopt_output_files:[], timeseries_csv_paths:[], save_names:nil)
|
207
|
+
def run_feature_reports(feature_reports:, reopt_assumptions_hashes:[], reopt_output_files:[], timeseries_csv_paths:[], save_names:nil, run_resilience:true)
|
186
208
|
|
187
209
|
if !reopt_assumptions_hashes.empty?
|
188
210
|
@feature_reports_reopt_default_assumption_hashes = reopt_assumptions_hashes
|
@@ -215,11 +237,22 @@ module URBANopt # :nodoc:
|
|
215
237
|
begin
|
216
238
|
reopt_input = feature_adapter.reopt_json_from_feature_report(feature_report, @feature_reports_reopt_default_assumption_hashes[idx])
|
217
239
|
reopt_output = api.reopt_request(reopt_input, @feature_reports_reopt_default_output_files[idx])
|
218
|
-
|
240
|
+
if run_resilience
|
241
|
+
run_uuid = reopt_output['outputs']['Scenario']['run_uuid']
|
242
|
+
if File.directory? @feature_reports_reopt_default_output_files[idx]
|
243
|
+
resilience_stats = api.resilience_request(run_uuid, @feature_reports_reopt_default_output_files[idx])
|
244
|
+
else
|
245
|
+
resilience_stats = api.resilience_request(run_uuid, @feature_reports_reopt_default_output_files[idx].sub!('.json','_resilience.json'))
|
246
|
+
end
|
247
|
+
else
|
248
|
+
resilience_stats = nil
|
249
|
+
end
|
250
|
+
new_feature_report = feature_adapter.update_feature_report(feature_report, reopt_output, @feature_reports_timeseries_default_output_files[idx], resilience_stats)
|
219
251
|
new_feature_reports.push(new_feature_report)
|
220
252
|
if !save_names.nil?
|
221
253
|
if save_names.length == feature_reports.length
|
222
|
-
new_feature_report.save_feature_report save_names[idx]
|
254
|
+
#new_feature_report.save_feature_report save_names[idx]
|
255
|
+
new_feature_report.save_json_report save_names[idx]
|
223
256
|
else
|
224
257
|
warn "Could not save feature reports - the number of save names provided did not match the number of feature reports"
|
225
258
|
end
|
@@ -228,7 +261,7 @@ module URBANopt # :nodoc:
|
|
228
261
|
@@logger.info("Could not optimize Feature Report #{feature_report.name} #{feature_report.id}")
|
229
262
|
end
|
230
263
|
end
|
231
|
-
|
264
|
+
|
232
265
|
return new_feature_reports
|
233
266
|
end
|
234
267
|
|
@@ -237,22 +270,22 @@ module URBANopt # :nodoc:
|
|
237
270
|
#
|
238
271
|
# [*parameters:*]
|
239
272
|
#
|
240
|
-
# * +scenario_report+ - _Array_ - A _URBANopt::
|
273
|
+
# * +scenario_report+ - _Array_ - A _URBANopt::Reporting::DefaultReports::ScenarioReport_ which will each be used to create (and is subsquently updated by) \REopt Lite opimization responses for each of its FeatureReports.
|
241
274
|
# * +reopt_assumptions_hashes+ - _Array_ - Optional. An array of \REopt Lite formatted hashes containing default parameters (i.e. utility rate, escalation rate) which will be updated by the ScenarioReport (i.e. location, roof availability). The number and order of the hashes should match the array in ScenarioReport.feature_reports.
|
242
275
|
# * +reopt_output_files+ - _Array_ - Optional. An array of paths to files at which REpopt Lite responses will be saved. The number and order of the paths should match the array in ScenarioReport.feature_reports.
|
243
276
|
# * +feature_report_timeseries_csv_paths+ - _Array_ - Optional. An array of paths to files at which the new timeseries CSV for the FeatureReports will be saved. The number and order of the paths should match the array in ScenarioReport.feature_reports.
|
244
277
|
#
|
245
278
|
# [*return:*] _URBANopt::Scenario::DefaultReports::ScenarioReport_ - Returns an updated ScenarioReport
|
246
|
-
def run_scenario_report_features(scenario_report:, reopt_assumptions_hashes:[], reopt_output_files:[], feature_report_timeseries_csv_paths:[], save_names_feature_reports:nil, save_name_scenario_report:nil)
|
247
|
-
new_feature_reports = run_feature_reports(feature_reports:scenario_report.feature_reports, reopt_assumptions_hashes:reopt_assumptions_hashes, reopt_output_files:reopt_output_files, timeseries_csv_paths:feature_report_timeseries_csv_paths,save_names:save_names_feature_reports)
|
279
|
+
def run_scenario_report_features(scenario_report:, reopt_assumptions_hashes:[], reopt_output_files:[], feature_report_timeseries_csv_paths:[], save_names_feature_reports:nil, save_name_scenario_report:nil, run_resilience:true)
|
280
|
+
new_feature_reports = run_feature_reports(feature_reports:scenario_report.feature_reports, reopt_assumptions_hashes:reopt_assumptions_hashes, reopt_output_files:reopt_output_files, timeseries_csv_paths:feature_report_timeseries_csv_paths,save_names:save_names_feature_reports, run_resilience:run_resilience)
|
248
281
|
|
249
|
-
new_scenario_report = URBANopt::
|
282
|
+
new_scenario_report = URBANopt::Reporting::DefaultReports::ScenarioReport.new
|
250
283
|
new_scenario_report.id = scenario_report.id
|
251
284
|
new_scenario_report.name = scenario_report.name
|
252
285
|
new_scenario_report.directory_name = scenario_report.directory_name
|
253
286
|
|
254
287
|
timeseries_hash = { column_names: scenario_report.timeseries_csv.column_names }
|
255
|
-
new_scenario_report.timeseries_csv = URBANopt::
|
288
|
+
new_scenario_report.timeseries_csv = URBANopt::Reporting::DefaultReports::TimeseriesCSV.new(timeseries_hash)
|
256
289
|
|
257
290
|
new_feature_reports.each do |feature_report|
|
258
291
|
new_scenario_report.add_feature_report(feature_report)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# *********************************************************************************
|
2
|
-
# URBANopt, Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
|
2
|
+
# URBANopt (tm), Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
|
3
3
|
# contributors. All rights reserved.
|
4
4
|
#
|
5
5
|
# Redistribution and use in source and binary forms, with or without modification,
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# *********************************************************************************
|
2
|
-
# URBANopt, Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
|
2
|
+
# URBANopt (tm), Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC, and other
|
3
3
|
# contributors. All rights reserved.
|
4
4
|
#
|
5
5
|
# Redistribution and use in source and binary forms, with or without modification,
|
@@ -28,11 +28,12 @@
|
|
28
28
|
# OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
29
|
# *********************************************************************************
|
30
30
|
|
31
|
-
require 'urbanopt/
|
31
|
+
require 'urbanopt/reporting/default_reports'
|
32
32
|
require 'urbanopt/reopt/reopt_logger'
|
33
33
|
require 'matrix'
|
34
34
|
require 'csv'
|
35
35
|
require 'time'
|
36
|
+
require_relative 'utilities'
|
36
37
|
|
37
38
|
module URBANopt # :nodoc:
|
38
39
|
module REopt # :nodoc:
|
@@ -51,7 +52,7 @@ module URBANopt # :nodoc:
|
|
51
52
|
#
|
52
53
|
# [*parameters:*]
|
53
54
|
#
|
54
|
-
# * +scenario_report+ - _URBANopt::
|
55
|
+
# * +scenario_report+ - _URBANopt::Reporting::DefaultReports::ScenarioReport_ - ScenarioReport to use in converting the +reopt_assumptions_hash+, if provided, to a \REopt Lite post. Otherwise, if the +reopt_assumptions_hash+ is nil a default post will be updated from this ScenarioReport and submitted to the \REopt Lite API.
|
55
56
|
# * +reopt_assumptions_hash+ - _Hash_ - Optional. A hash formatted for submittal to the \REopt Lite API containing default values. Values will be overwritten from the ScenarioReport where available (i.e. latitude, roof_squarefeet). Missing optional parameters will be filled in with default values by the API.
|
56
57
|
#
|
57
58
|
# [*return:*] _Hash_ - Returns hash formatted for submittal to the \REopt Lite API
|
@@ -70,31 +71,32 @@ module URBANopt # :nodoc:
|
|
70
71
|
end
|
71
72
|
|
72
73
|
# Update required info
|
73
|
-
if scenario_report.location.
|
74
|
+
if scenario_report.location.latitude_deg.nil? || scenario_report.location.longitude_deg.nil? || (scenario_report.location.latitude_deg == 0) || (scenario_report.location.longitude_deg == 0)
|
74
75
|
if !scenario_report.feature_reports.nil? && (scenario_report.feature_reports != [])
|
75
76
|
lats = []
|
76
77
|
longs = []
|
77
78
|
scenario_report.feature_reports.each do |x|
|
78
|
-
|
79
|
-
|
80
|
-
|
79
|
+
puts " ERROR: #{x.location.latitude_deg}"
|
80
|
+
if ![nil].include?(x.location.latitude_deg) && ![nil].include?(x.location.longitude_deg)
|
81
|
+
lats.push(x.location.latitude_deg)
|
82
|
+
longs.push(x.location.longitude_deg)
|
81
83
|
end
|
82
84
|
end
|
83
85
|
|
84
86
|
if !lats.empty? && !longs.empty?
|
85
|
-
scenario_report.location.
|
86
|
-
scenario_report.location.
|
87
|
+
scenario_report.location.latitude_deg = lats.reduce(:+) / lats.size.to_f
|
88
|
+
scenario_report.location.longitude_deg = longs.reduce(:+) / longs.size.to_f
|
87
89
|
end
|
88
90
|
end
|
89
91
|
end
|
90
92
|
|
91
93
|
# Update required info
|
92
94
|
requireds_names = ['latitude', 'longitude']
|
93
|
-
requireds = [scenario_report.location.
|
95
|
+
requireds = [scenario_report.location.latitude_deg, scenario_report.location.longitude_deg]
|
94
96
|
|
95
97
|
if requireds.include?(nil) || requireds.include?(0)
|
96
|
-
requireds.each_with_index do |
|
97
|
-
if [nil
|
98
|
+
requireds.each_with_index do |x, i|
|
99
|
+
if [nil].include? x
|
98
100
|
n = requireds_names[i]
|
99
101
|
raise "Missing value for #{n} - this is a required input"
|
100
102
|
end
|
@@ -103,20 +105,25 @@ module URBANopt # :nodoc:
|
|
103
105
|
|
104
106
|
reopt_inputs[:Scenario][:description] = description
|
105
107
|
|
106
|
-
reopt_inputs[:Scenario][:Site][:latitude] = scenario_report.location.
|
107
|
-
reopt_inputs[:Scenario][:Site][:longitude] = scenario_report.location.
|
108
|
+
reopt_inputs[:Scenario][:Site][:latitude] = scenario_report.location.latitude_deg
|
109
|
+
reopt_inputs[:Scenario][:Site][:longitude] = scenario_report.location.longitude_deg
|
108
110
|
|
109
111
|
# Update optional info
|
110
|
-
|
111
|
-
|
112
|
+
# REK: attribute names should be updated
|
113
|
+
if reopt_inputs[:Scenario][:Site][:roof_squarefeet].nil?
|
114
|
+
if !scenario_report.program.roof_area_sqft.nil?
|
115
|
+
reopt_inputs[:Scenario][:Site][:roof_squarefeet] = scenario_report.program.roof_area_sqft[:available_roof_area_sqft]
|
116
|
+
end
|
112
117
|
end
|
113
118
|
|
114
|
-
if
|
115
|
-
|
119
|
+
if reopt_inputs[:Scenario][:Site][:land_acres].nil?
|
120
|
+
if !scenario_report.program.site_area_sqft.nil?
|
121
|
+
reopt_inputs[:Scenario][:Site][:land_acres] = scenario_report.program.site_area_sqft * 1.0 / 43560 # acres/sqft
|
122
|
+
end
|
116
123
|
end
|
117
124
|
|
118
|
-
|
119
|
-
reopt_inputs[:Scenario][:time_steps_per_hour] =
|
125
|
+
if reopt_inputs[:Scenario][:time_steps_per_hour].nil?
|
126
|
+
reopt_inputs[:Scenario][:time_steps_per_hour] = 1
|
120
127
|
end
|
121
128
|
|
122
129
|
# Update load profile info
|
@@ -126,27 +133,51 @@ module URBANopt # :nodoc:
|
|
126
133
|
energy_timeseries_kw = t.by_col[col_num].map { |e| ((e * scenario_report.timesteps_per_hour || 0) ) }
|
127
134
|
if energy_timeseries_kw.length < (scenario_report.timesteps_per_hour * 8760)
|
128
135
|
start_date = Time.parse(t.by_col["Datetime"][0])
|
129
|
-
start_ts = (((start_date.yday * 60.0 * 60.0 * 24) + (start_date.hour * 60.0 * 60.0) + (start_date.min * 60.0) + start_date.sec) /
|
136
|
+
start_ts = (((start_date.yday * 60.0 * 60.0 * 24) + (start_date.hour * 60.0 * 60.0) + (start_date.min * 60.0) + start_date.sec) / \
|
130
137
|
(( 60 / scenario_report.timesteps_per_hour ) * 60)).to_int
|
131
138
|
end_date = Time.parse(t.by_col["Datetime"][-1])
|
132
|
-
end_ts = (((end_date.yday * 60.0 * 60.0 * 24) + (end_date.hour * 60.0 * 60.0) + (end_date.min * 60.0) + end_date.sec) /
|
139
|
+
end_ts = (((end_date.yday * 60.0 * 60.0 * 24) + (end_date.hour * 60.0 * 60.0) + (end_date.min * 60.0) + end_date.sec) / \
|
133
140
|
(( 60 / scenario_report.timesteps_per_hour ) * 60)).to_int
|
134
141
|
energy_timeseries_kw = [0.0]*(start_ts-1) + energy_timeseries_kw + [0.0]*((scenario_report.timesteps_per_hour * 8760) - end_ts)
|
135
142
|
end
|
136
|
-
|
143
|
+
energy_timeseries_kw = energy_timeseries_kw.map { |e| e ? e : 0 }[0,(scenario_report.timesteps_per_hour * 8760)]
|
137
144
|
rescue StandardError
|
138
145
|
@@logger.error("Could not parse the annual electric load from the timeseries csv - #{scenario_report.timeseries_csv.path}")
|
139
146
|
raise "Could not parse the annual electric load from the timeseries csv - #{scenario_report.timeseries_csv.path}"
|
140
147
|
end
|
148
|
+
|
149
|
+
# Convert load to REopt Resolution
|
150
|
+
begin
|
151
|
+
reopt_inputs[:Scenario][:Site][:LoadProfile][:loads_kw] = convert_powerflow_resolution(energy_timeseries_kw, scenario_report.timesteps_per_hour, reopt_inputs[:Scenario][:time_steps_per_hour])
|
152
|
+
rescue
|
153
|
+
@@logger.error("Could not convert the annual electric load from a resolution of #{scenario_report.timesteps_per_hour} to #{reopt_inputs[:Scenario][:time_steps_per_hour]}")
|
154
|
+
raise "Could not convert the annual electric load from a resolution of #{scenario_report.timesteps_per_hour} to #{reopt_inputs[:Scenario][:time_steps_per_hour]}"
|
155
|
+
end
|
156
|
+
|
157
|
+
if reopt_inputs[:Scenario][:Site][:ElectricTariff][:coincident_peak_load_active_timesteps].nil?
|
158
|
+
n_top_values = 100
|
159
|
+
tmp1 = reopt_inputs[:Scenario][:Site][:LoadProfile][:loads_kw]
|
160
|
+
tmp2 = tmp1.each_index.max_by(n_top_values*reopt_inputs[:Scenario][:time_steps_per_hour]){|i| tmp1[i]}
|
161
|
+
for i in (0...tmp2.count)
|
162
|
+
tmp2[i] += 1
|
163
|
+
end
|
164
|
+
reopt_inputs[:Scenario][:Site][:ElectricTariff][:coincident_peak_load_active_timesteps] = tmp2
|
165
|
+
end
|
166
|
+
|
167
|
+
if reopt_inputs[:Scenario][:Site][:ElectricTariff][:coincident_peak_load_charge_us_dollars_per_kw].nil?
|
168
|
+
reopt_inputs[:Scenario][:Site][:ElectricTariff][:coincident_peak_load_charge_us_dollars_per_kw] = 0
|
169
|
+
end
|
170
|
+
|
141
171
|
return reopt_inputs
|
142
172
|
end
|
143
173
|
|
174
|
+
|
144
175
|
##
|
145
176
|
# Converts a FeatureReport list from a ScenarioReport into an array of \REopt Lite posts
|
146
177
|
#
|
147
178
|
# [*parameters:*]
|
148
179
|
#
|
149
|
-
# * +scenario_report+ - _URBANopt::
|
180
|
+
# * +scenario_report+ - _URBANopt::Reporting::DefaultReports::ScenarioReport_ - ScenarioReport to use in converting FeatureReports and respecitive +reopt_assumptions_hashes+, if provided, to a \REopt Lite post. If no +reopt_assumptions_hashes+ are provided default posts will be updated from these FeatureReports and submitted to the \REopt Lite API.
|
150
181
|
# * +reopt_assumptions_hashes+ - _Array_ - Optional. An array of hashes formatted for submittal to the \REopt Lite API containing default values. Values will be overwritten from the ScenarioReport where available (i.e. latitude, roof_squarefeet). Missing optional parameters will be filled in with default values by the API. The order should match the list in ScenarioReport.feature_reports.
|
151
182
|
#
|
152
183
|
# [*return:*] _Array_ - Returns an array of hashes formatted for submittal to the \REopt Lite API in the order of the FeatureReports lited in ScenarioReport.feature_reports.
|
@@ -168,44 +199,21 @@ module URBANopt # :nodoc:
|
|
168
199
|
#
|
169
200
|
# [*parameters:*]
|
170
201
|
#
|
171
|
-
# * +scenario_report+ - _URBANopt::
|
202
|
+
# * +scenario_report+ - _URBANopt::Reporting::DefaultReports::ScenarioReport_ - ScenarioReport to update from a \REopt Lite response.
|
172
203
|
# * +reopt_output+ - _Hash_ - A hash response from the \REopt Lite API.
|
173
204
|
# * +timeseries_csv_path+ - _String_ - Optional. The path to a file at which new timeseries data will be written. If not provided a file is created based on the run_uuid of the \REopt Lite optimization task.
|
174
205
|
#
|
175
|
-
# [*return:*] _URBANopt::
|
206
|
+
# [*return:*] _URBANopt::Reporting::DefaultReports::ScenarioReport_ - Returns an updated ScenarioReport
|
176
207
|
##
|
177
|
-
def update_scenario_report(scenario_report, reopt_output, timeseries_csv_path =
|
208
|
+
def update_scenario_report(scenario_report, reopt_output, timeseries_csv_path=nil, resilience_stats=nil)
|
178
209
|
if reopt_output['outputs']['Scenario']['status'] != 'optimal'
|
179
210
|
@@logger.info("Warning cannot Feature Report #{scenario_report.name} #{scenario_report.id} - REopt optimization was non-optimal")
|
180
211
|
return scenario_report
|
181
212
|
end
|
182
213
|
|
183
|
-
$ts_per_hour = scenario_report.timesteps_per_hour
|
184
|
-
def scale_timeseries(input, ts_per_hr=$ts_per_hour)
|
185
|
-
if input.nil?
|
186
|
-
return nil
|
187
|
-
end
|
188
|
-
if input.length ==0
|
189
|
-
return nil
|
190
|
-
end
|
191
|
-
if input.length == (8760 * ts_per_hr)
|
192
|
-
return input
|
193
|
-
end
|
194
|
-
result = []
|
195
|
-
input.each do |val|
|
196
|
-
(1..ts_per_hr).each do |x|
|
197
|
-
result.push(val/ts_per_hr.to_f)
|
198
|
-
end
|
199
|
-
end
|
200
|
-
return result
|
201
|
-
end
|
202
|
-
|
203
214
|
# Update location
|
204
|
-
scenario_report.location.
|
205
|
-
scenario_report.location.
|
206
|
-
|
207
|
-
# Update timeseries csv from \REopt Lite dispatch data
|
208
|
-
scenario_report.timesteps_per_hour = reopt_output['inputs']['Scenario']['time_steps_per_hour']
|
215
|
+
scenario_report.location.latitude_deg = reopt_output['inputs']['Scenario']['Site']['latitude']
|
216
|
+
scenario_report.location.longitude_deg = reopt_output['inputs']['Scenario']['Site']['longitude']
|
209
217
|
|
210
218
|
# Update distributed generation sizing and financials
|
211
219
|
|
@@ -215,55 +223,61 @@ module URBANopt # :nodoc:
|
|
215
223
|
scenario_report.distributed_generation.year_one_demand_cost_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_demand_cost_us_dollars'] || 0
|
216
224
|
scenario_report.distributed_generation.year_one_bill_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_bill_us_dollars'] || 0
|
217
225
|
scenario_report.distributed_generation.total_energy_cost_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['total_energy_cost_us_dollars'] || 0
|
218
|
-
|
226
|
+
scenario_report.distributed_generation.total_demand_cost_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['total_demand_cost_us_dollars'] || 0
|
227
|
+
scenario_report.distributed_generation.year_one_energy_cost_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_energy_cost_bau_us_dollars'] || 0
|
228
|
+
scenario_report.distributed_generation.year_one_demand_cost_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_demand_cost_bau_us_dollars'] || 0
|
229
|
+
scenario_report.distributed_generation.year_one_bill_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_bill_bau_us_dollars'] || 0
|
230
|
+
scenario_report.distributed_generation.total_demand_cost_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['total_demand_cost_bau_us_dollars'] || 0
|
231
|
+
scenario_report.distributed_generation.total_energy_cost_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['total_energy_cost_bau_us_dollars'] || 0
|
232
|
+
if !resilience_stats.nil?
|
233
|
+
scenario_report.distributed_generation.resilience_hours_min = resilience_stats['resilience_hours_min']
|
234
|
+
scenario_report.distributed_generation.resilience_hours_max = resilience_stats['resilience_hours_max']
|
235
|
+
scenario_report.distributed_generation.resilience_hours_avg = resilience_stats['resilience_hours_avg']
|
236
|
+
scenario_report.distributed_generation.probs_of_surviving = resilience_stats['probs_of_surviving']
|
237
|
+
scenario_report.distributed_generation.probs_of_surviving_by_month = resilience_stats['probs_of_surviving_by_month']
|
238
|
+
scenario_report.distributed_generation.probs_of_surviving_by_hour_of_the_day = resilience_stats['probs_of_surviving_by_hour_of_the_day']
|
239
|
+
end
|
240
|
+
|
219
241
|
if reopt_output['outputs']['Scenario']['Site']['PV'].class == Hash
|
220
242
|
reopt_output['outputs']['Scenario']['Site']['PV'] = [reopt_output['outputs']['Scenario']['Site']['PV']]
|
221
243
|
elsif reopt_output['outputs']['Scenario']['Site']['PV'].nil?
|
222
244
|
reopt_output['outputs']['Scenario']['Site']['PV'] = []
|
223
245
|
end
|
224
|
-
|
225
|
-
reopt_output['outputs']['Scenario']['Site']['PV'].each_with_index do |pv, i|
|
226
|
-
scenario_report.distributed_generation.add_tech 'solar_pv', URBANopt::
|
246
|
+
|
247
|
+
reopt_output['outputs']['Scenario']['Site']['PV'].each_with_index do |pv, i|
|
248
|
+
scenario_report.distributed_generation.add_tech 'solar_pv', URBANopt::Reporting::DefaultReports::SolarPV.new( {size_kw: (pv['size_kw'] || 0), id: i })
|
227
249
|
end
|
228
250
|
|
229
251
|
wind = reopt_output['outputs']['Scenario']['Site']['Wind']
|
230
252
|
if !wind['size_kw'].nil? and wind['size_kw'] != 0
|
231
|
-
scenario_report.distributed_generation.add_tech 'wind', URBANopt::
|
253
|
+
scenario_report.distributed_generation.add_tech 'wind', URBANopt::Reporting::DefaultReports::Wind.new( {size_kw: (wind['size_kw'] || 0) })
|
232
254
|
end
|
233
255
|
|
234
256
|
generator = reopt_output['outputs']['Scenario']['Site']['Generator']
|
235
257
|
if !generator['size_kw'].nil? and generator['size_kw'] != 0
|
236
|
-
scenario_report.distributed_generation.add_tech 'generator', URBANopt::
|
258
|
+
scenario_report.distributed_generation.add_tech 'generator', URBANopt::Reporting::DefaultReports::Generator.new( {size_kw: (generator['size_kw'] || 0) })
|
237
259
|
end
|
238
260
|
|
239
261
|
storage = reopt_output['outputs']['Scenario']['Site']['Storage']
|
240
262
|
if !storage['size_kw'].nil? and storage['size_kw'] != 0
|
241
|
-
scenario_report.distributed_generation.add_tech 'storage', URBANopt::
|
263
|
+
scenario_report.distributed_generation.add_tech 'storage', URBANopt::Reporting::DefaultReports::Storage.new( {size_kwh: (storage['size_kwh'] || 0), size_kw: (storage['size_kw'] || 0) })
|
242
264
|
end
|
243
265
|
|
266
|
+
reopt_resolution = reopt_output['inputs']['Scenario']['time_steps_per_hour']
|
244
267
|
generation_timeseries_kwh = Matrix[[0] * (8760 * scenario_report.timesteps_per_hour)]
|
245
|
-
|
246
268
|
|
247
|
-
reopt_output['outputs']['Scenario']['Site']['PV'].each do |pv|
|
269
|
+
reopt_output['outputs']['Scenario']['Site']['PV'].each do |pv|
|
248
270
|
if (pv['size_kw'] || 0) > 0
|
249
271
|
if !pv['year_one_power_production_series_kw'].nil?
|
250
|
-
generation_timeseries_kwh += Matrix[pv['year_one_power_production_series_kw']]
|
272
|
+
generation_timeseries_kwh += Matrix[convert_powerflow_resolution(pv['year_one_power_production_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour)]
|
251
273
|
end
|
252
274
|
end
|
253
275
|
end
|
254
276
|
|
255
|
-
unless reopt_output['outputs']['Scenario']['Site']['Storage'].nil?
|
256
|
-
if (reopt_output['outputs']['Scenario']['Site']['Storage']['size_kw'] or 0) > 0
|
257
|
-
if !reopt_output['outputs']['Scenario']['Site']['Storage']['year_one_to_grid_series_kw'].nil?
|
258
|
-
generation_timeseries_kwh = generation_timeseries_kwh + Matrix[reopt_output['outputs']['Scenario']['Site']['Storage']['year_one_to_grid_series_kw']]
|
259
|
-
end
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
277
|
unless reopt_output['outputs']['Scenario']['Site']['Wind'].nil?
|
264
278
|
if (reopt_output['outputs']['Scenario']['Site']['Wind']['size_kw'] || 0) > 0
|
265
279
|
if !reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_power_production_series_kw'].nil?
|
266
|
-
generation_timeseries_kwh += Matrix[reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_power_production_series_kw']]
|
280
|
+
generation_timeseries_kwh += Matrix[convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_power_production_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour)]
|
267
281
|
end
|
268
282
|
end
|
269
283
|
end
|
@@ -271,7 +285,7 @@ module URBANopt # :nodoc:
|
|
271
285
|
unless reopt_output['outputs']['Scenario']['Site']['Generator'].nil?
|
272
286
|
if (reopt_output['outputs']['Scenario']['Site']['Generator']['size_kw'] || 0) > 0
|
273
287
|
if !reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_power_production_series_kw'].nil?
|
274
|
-
generation_timeseries_kwh += Matrix[reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_power_production_series_kw']]
|
288
|
+
generation_timeseries_kwh += Matrix[convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_power_production_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour)]
|
275
289
|
end
|
276
290
|
end
|
277
291
|
end
|
@@ -283,70 +297,70 @@ module URBANopt # :nodoc:
|
|
283
297
|
scenario_report.timeseries_csv.column_names.push('REopt:ElectricityProduced:Total(kw)')
|
284
298
|
end
|
285
299
|
|
286
|
-
$load =
|
300
|
+
$load = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['LoadProfile']['year_one_electric_load_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
|
287
301
|
$load_col = scenario_report.timeseries_csv.column_names.index('REopt:Electricity:Load:Total(kw)')
|
288
302
|
if $load_col.nil?
|
289
303
|
$load_col = scenario_report.timeseries_csv.column_names.length
|
290
304
|
scenario_report.timeseries_csv.column_names.push('REopt:Electricity:Load:Total(kw)')
|
291
305
|
end
|
292
306
|
|
293
|
-
$utility_to_load =
|
307
|
+
$utility_to_load = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_to_load_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
|
294
308
|
$utility_to_load_col = scenario_report.timeseries_csv.column_names.index('REopt:Electricity:Grid:ToLoad(kw)')
|
295
309
|
if $utility_to_load_col.nil?
|
296
310
|
$utility_to_load_col = scenario_report.timeseries_csv.column_names.length
|
297
311
|
scenario_report.timeseries_csv.column_names.push('REopt:Electricity:Grid:ToLoad(kw)')
|
298
312
|
end
|
299
313
|
|
300
|
-
$utility_to_battery =
|
314
|
+
$utility_to_battery = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_to_battery_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
|
301
315
|
$utility_to_battery_col = scenario_report.timeseries_csv.column_names.index('REopt:Electricity:Grid:ToBattery(kw)')
|
302
316
|
if $utility_to_battery_col.nil?
|
303
317
|
$utility_to_battery_col = scenario_report.timeseries_csv.column_names.length
|
304
318
|
scenario_report.timeseries_csv.column_names.push('REopt:Electricity:Grid:ToBattery(kw)')
|
305
319
|
end
|
306
320
|
|
307
|
-
$storage_to_load =
|
321
|
+
$storage_to_load = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Storage']['year_one_to_load_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
|
308
322
|
$storage_to_load_col = scenario_report.timeseries_csv.column_names.index('REopt:Electricity:Storage:ToLoad(kw)')
|
309
323
|
if $storage_to_load_col.nil?
|
310
324
|
$storage_to_load_col = scenario_report.timeseries_csv.column_names.length
|
311
325
|
scenario_report.timeseries_csv.column_names.push('REopt:Electricity:Storage:ToLoad(kw)')
|
312
326
|
end
|
313
327
|
|
314
|
-
$storage_to_grid =
|
328
|
+
$storage_to_grid = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Storage']['year_one_to_grid_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
|
315
329
|
$storage_to_grid_col = scenario_report.timeseries_csv.column_names.index('REopt:Electricity:Storage:ToGrid(kw)')
|
316
330
|
if $storage_to_grid_col.nil?
|
317
331
|
$storage_to_grid_col = scenario_report.timeseries_csv.column_names.length
|
318
332
|
scenario_report.timeseries_csv.column_names.push('REopt:Electricity:Storage:ToGrid(kw)')
|
319
333
|
end
|
320
334
|
|
321
|
-
$storage_soc =
|
335
|
+
$storage_soc = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Storage']['year_one_soc_series_pct'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
|
322
336
|
$storage_soc_col = scenario_report.timeseries_csv.column_names.index('REopt:Electricity:Storage:StateOfCharge(pct)')
|
323
337
|
if $storage_soc_col.nil?
|
324
338
|
$storage_soc_col = scenario_report.timeseries_csv.column_names.length
|
325
339
|
scenario_report.timeseries_csv.column_names.push('REopt:Electricity:Storage:StateOfCharge(pct)')
|
326
340
|
end
|
327
341
|
|
328
|
-
$generator_total =
|
342
|
+
$generator_total = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_power_production_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
|
329
343
|
$generator_total_col = scenario_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Generator:Total(kw)')
|
330
344
|
if $generator_total_col.nil?
|
331
345
|
$generator_total_col = scenario_report.timeseries_csv.column_names.length
|
332
346
|
scenario_report.timeseries_csv.column_names.push('REopt:ElectricityProduced:Generator:Total(kw)')
|
333
347
|
end
|
334
348
|
|
335
|
-
$generator_to_battery =
|
349
|
+
$generator_to_battery = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_to_battery_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
|
336
350
|
$generator_to_battery_col = scenario_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Generator:ToBattery(kw)')
|
337
351
|
if $generator_to_battery_col.nil?
|
338
352
|
$generator_to_battery_col = scenario_report.timeseries_csv.column_names.length
|
339
353
|
scenario_report.timeseries_csv.column_names.push('REopt:ElectricityProduced:Generator:ToBattery(kw)')
|
340
354
|
end
|
341
355
|
|
342
|
-
$generator_to_load =
|
356
|
+
$generator_to_load = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_to_load_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
|
343
357
|
$generator_to_load_col = scenario_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Generator:ToLoad(kw)')
|
344
358
|
if $generator_to_load_col.nil?
|
345
359
|
$generator_to_load_col = scenario_report.timeseries_csv.column_names.length
|
346
360
|
scenario_report.timeseries_csv.column_names.push('REopt:ElectricityProduced:Generator:ToLoad(kw)')
|
347
361
|
end
|
348
362
|
|
349
|
-
$generator_to_grid =
|
363
|
+
$generator_to_grid = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_to_grid_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
|
350
364
|
$generator_to_grid_col = scenario_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Generator:ToGrid(kw)')
|
351
365
|
if $generator_to_grid_col.nil?
|
352
366
|
$generator_to_grid_col = scenario_report.timeseries_csv.column_names.length
|
@@ -384,10 +398,10 @@ module URBANopt # :nodoc:
|
|
384
398
|
|
385
399
|
reopt_output['outputs']['Scenario']['Site']['PV'].each_with_index do |pv, i|
|
386
400
|
if (pv['size_kw'] || 0) > 0
|
387
|
-
$pv_total += Matrix[
|
388
|
-
$pv_to_battery += Matrix[
|
389
|
-
$pv_to_load += Matrix[
|
390
|
-
$pv_to_grid += Matrix[
|
401
|
+
$pv_total += Matrix[convert_powerflow_resolution(pv['year_one_power_production_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)]
|
402
|
+
$pv_to_battery += Matrix[convert_powerflow_resolution(pv['year_one_to_battery_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)]
|
403
|
+
$pv_to_load += Matrix[convert_powerflow_resolution(pv['year_one_to_load_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)]
|
404
|
+
$pv_to_grid += Matrix[convert_powerflow_resolution(pv['year_one_to_grid_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)]
|
391
405
|
end
|
392
406
|
end
|
393
407
|
|
@@ -396,28 +410,28 @@ module URBANopt # :nodoc:
|
|
396
410
|
$pv_to_load = $pv_to_load.to_a[0]
|
397
411
|
$pv_to_grid = $pv_to_grid.to_a[0]
|
398
412
|
|
399
|
-
$wind_total =
|
413
|
+
$wind_total = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_power_production_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
|
400
414
|
$wind_total_col = scenario_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Wind:Total(kw)')
|
401
415
|
if $wind_total_col.nil?
|
402
416
|
$wind_total_col = scenario_report.timeseries_csv.column_names.length
|
403
417
|
scenario_report.timeseries_csv.column_names.push('REopt:ElectricityProduced:Wind:Total(kw)')
|
404
418
|
end
|
405
419
|
|
406
|
-
$wind_to_battery =
|
420
|
+
$wind_to_battery = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_to_battery_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
|
407
421
|
$wind_to_battery_col = scenario_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Wind:ToBattery(kw)')
|
408
422
|
if $wind_to_battery_col.nil?
|
409
423
|
$wind_to_battery_col = scenario_report.timeseries_csv.column_names.length
|
410
424
|
scenario_report.timeseries_csv.column_names.push('REopt:ElectricityProduced:Wind:ToBattery(kw)')
|
411
425
|
end
|
412
426
|
|
413
|
-
$wind_to_load =
|
427
|
+
$wind_to_load = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_to_load_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
|
414
428
|
$wind_to_load_col = scenario_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Wind:ToLoad(kw)')
|
415
429
|
if $wind_to_load_col.nil?
|
416
430
|
$wind_to_load_col = scenario_report.timeseries_csv.column_names.length
|
417
431
|
scenario_report.timeseries_csv.column_names.push('REopt:ElectricityProduced:Wind:ToLoad(kw)')
|
418
432
|
end
|
419
433
|
|
420
|
-
$wind_to_grid =
|
434
|
+
$wind_to_grid = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_to_grid_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour) || [0] * (8760 * scenario_report.timesteps_per_hour)
|
421
435
|
$wind_to_grid_col = scenario_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Wind:ToGrid(kw)')
|
422
436
|
if $wind_to_grid_col.nil?
|
423
437
|
$wind_to_grid_col = scenario_report.timeseries_csv.column_names.length
|
@@ -448,17 +462,22 @@ module URBANopt # :nodoc:
|
|
448
462
|
end
|
449
463
|
|
450
464
|
old_data = CSV.open(scenario_report.timeseries_csv.path).read
|
451
|
-
start_date = Time.parse(old_data[1][0])
|
452
|
-
start_ts = (
|
465
|
+
start_date = Time.parse(old_data[1][0]) # Time is the end of the timestep
|
466
|
+
start_ts = (
|
467
|
+
(
|
468
|
+
((start_date.yday - 1) * 60.0 * 60.0 * 24) +
|
469
|
+
(((start_date.hour) - 1) * 60.0 * 60.0) +
|
470
|
+
(start_date.min * 60.0) + start_date.sec ) /
|
471
|
+
(( 60 / scenario_report.timesteps_per_hour ) * 60)
|
472
|
+
).to_int
|
453
473
|
mod_data = old_data.map.with_index do |x, i|
|
454
474
|
if i > 0
|
455
|
-
modrow(x, start_ts + i -
|
475
|
+
modrow(x, start_ts + i -1)
|
456
476
|
else
|
457
477
|
x
|
458
478
|
end
|
459
479
|
end
|
460
480
|
mod_data[0] = scenario_report.timeseries_csv.column_names
|
461
|
-
|
462
481
|
scenario_report.timeseries_csv.reload_data(mod_data)
|
463
482
|
return scenario_report
|
464
483
|
end
|