urbanopt-reopt 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -2
- data/CHANGELOG.md +7 -0
- data/Gemfile +1 -1
- data/Rakefile +2 -2
- data/lib/urbanopt/reopt/feature_report_adapter.rb +38 -55
- data/lib/urbanopt/reopt/reopt_lite_api.rb +56 -42
- data/lib/urbanopt/reopt/reopt_logger.rb +1 -1
- data/lib/urbanopt/reopt/reopt_post_processor.rb +72 -47
- data/lib/urbanopt/reopt/scenario/reopt_scenario_csv.rb +7 -7
- data/lib/urbanopt/reopt/scenario_report_adapter.rb +46 -63
- data/lib/urbanopt/reopt/utilities.rb +106 -106
- data/lib/urbanopt/reopt/version.rb +1 -1
- data/urbanopt-reopt.gemspec +2 -5
- metadata +10 -24
@@ -79,18 +79,18 @@ module URBANopt # :nodoc:
|
|
79
79
|
if !scenario_report.nil?
|
80
80
|
@scenario_report = scenario_report
|
81
81
|
|
82
|
-
if !Dir.exist?(File.join(@scenario_report.directory_name,
|
83
|
-
Dir.mkdir(File.join(@scenario_report.directory_name,
|
84
|
-
@@logger.info("Created directory:
|
82
|
+
if !Dir.exist?(File.join(@scenario_report.directory_name, 'reopt'))
|
83
|
+
Dir.mkdir(File.join(@scenario_report.directory_name, 'reopt'))
|
84
|
+
@@logger.info("Created directory: #{File.join(@scenario_report.directory_name, 'reopt')}")
|
85
85
|
end
|
86
86
|
|
87
87
|
@scenario_reopt_default_output_file = File.join(@scenario_report.directory_name, "reopt/scenario_report_#{@scenario_report.id}_reopt_run.json")
|
88
88
|
@scenario_timeseries_default_output_file = File.join(@scenario_report.directory_name, "scenario_report_#{@scenario_report.id}_timeseries.csv")
|
89
89
|
|
90
90
|
@scenario_report.feature_reports.each do |fr|
|
91
|
-
if !Dir.exist?(File.join(fr.directory_name,
|
92
|
-
Dir.mkdir(File.join(fr.directory_name,
|
93
|
-
@@logger.info("Created directory:
|
91
|
+
if !Dir.exist?(File.join(fr.directory_name, 'reopt'))
|
92
|
+
Dir.mkdir(File.join(fr.directory_name, 'reopt'))
|
93
|
+
@@logger.info("Created directory: #{File.join(fr.directory_name, 'reopt')}")
|
94
94
|
end
|
95
95
|
@feature_reports_reopt_default_output_files << File.join(fr.directory_name, "reopt/feature_report_#{fr.id}_reopt_run.json")
|
96
96
|
end
|
@@ -113,8 +113,7 @@ module URBANopt # :nodoc:
|
|
113
113
|
end
|
114
114
|
end
|
115
115
|
|
116
|
-
attr_accessor :scenario_reopt_default_assumptions_hash, :scenario_reopt_default_output_file, :scenario_timeseries_default_output_file
|
117
|
-
attr_accessor :feature_reports_reopt_default_assumption_hashes, :feature_reports_reopt_default_output_files, :feature_reports_timeseries_default_output_files
|
116
|
+
attr_accessor :scenario_reopt_default_assumptions_hash, :scenario_reopt_default_output_file, :scenario_timeseries_default_output_file, :feature_reports_reopt_default_assumption_hashes, :feature_reports_reopt_default_output_files, :feature_reports_timeseries_default_output_files
|
118
117
|
|
119
118
|
##
|
120
119
|
# Updates a FeatureReport based on an optional set of \REopt Lite optimization assumptions.
|
@@ -129,7 +128,7 @@ module URBANopt # :nodoc:
|
|
129
128
|
#
|
130
129
|
# [*return:*] _URBANopt::Reporting::DefaultReports::FeatureReport_ - Returns an updated FeatureReport
|
131
130
|
##
|
132
|
-
def run_feature_report(feature_report:, reopt_assumptions_hash:nil, reopt_output_file:nil, timeseries_csv_path:nil, save_name:nil, run_resilience:true)
|
131
|
+
def run_feature_report(feature_report:, reopt_assumptions_hash: nil, reopt_output_file: nil, timeseries_csv_path: nil, save_name: nil, run_resilience: true)
|
133
132
|
api = URBANopt::REopt::REoptLiteAPI.new(@nrel_developer_key, @localhost)
|
134
133
|
adapter = URBANopt::REopt::FeatureReportAdapter.new
|
135
134
|
|
@@ -143,7 +142,7 @@ module URBANopt # :nodoc:
|
|
143
142
|
if File.directory? reopt_output_file
|
144
143
|
resilience_stats = api.resilience_request(run_uuid, reopt_output_file)
|
145
144
|
else
|
146
|
-
resilience_stats = api.resilience_request(run_uuid, reopt_output_file.sub!('.json','_resilience.json'))
|
145
|
+
resilience_stats = api.resilience_request(run_uuid, reopt_output_file.sub!('.json', '_resilience.json'))
|
147
146
|
end
|
148
147
|
else
|
149
148
|
resilience_stats = nil
|
@@ -167,7 +166,7 @@ module URBANopt # :nodoc:
|
|
167
166
|
# * +timeseries_csv_path+ - _String_ - Optional. Path to a file at which the new timeseries CSV for the ScenarioReport will be saved.
|
168
167
|
#
|
169
168
|
# [*return:*] _URBANopt::Scenario::DefaultReports::ScenarioReport_ Returns an updated ScenarioReport
|
170
|
-
def run_scenario_report(scenario_report:, reopt_assumptions_hash:nil, reopt_output_file:nil, timeseries_csv_path:nil, save_name:nil, run_resilience:true)
|
169
|
+
def run_scenario_report(scenario_report:, reopt_assumptions_hash: nil, reopt_output_file: nil, timeseries_csv_path: nil, save_name: nil, run_resilience: true)
|
171
170
|
if !reopt_assumptions_hash.nil?
|
172
171
|
@scenario_reopt_default_assumptions_hash = reopt_assumptions_hash
|
173
172
|
end
|
@@ -189,7 +188,7 @@ module URBANopt # :nodoc:
|
|
189
188
|
if File.directory? @scenario_reopt_default_output_file
|
190
189
|
resilience_stats = api.resilience_request(run_uuid, @scenario_reopt_default_output_file)
|
191
190
|
else
|
192
|
-
resilience_stats = api.resilience_request(run_uuid, @scenario_reopt_default_output_file.sub!('.json','_resilience.json'))
|
191
|
+
resilience_stats = api.resilience_request(run_uuid, @scenario_reopt_default_output_file.sub!('.json', '_resilience.json'))
|
193
192
|
end
|
194
193
|
else
|
195
194
|
resilience_stats = nil
|
@@ -213,8 +212,7 @@ module URBANopt # :nodoc:
|
|
213
212
|
# * +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.
|
214
213
|
#
|
215
214
|
# [*return:*] _Array_ Returns an array of updated _URBANopt::Scenario::DefaultReports::FeatureReport_ objects
|
216
|
-
def run_feature_reports(feature_reports:, reopt_assumptions_hashes:[], reopt_output_files:[], timeseries_csv_paths:[], save_names:nil, run_resilience:true)
|
217
|
-
|
215
|
+
def run_feature_reports(feature_reports:, reopt_assumptions_hashes: [], reopt_output_files: [], timeseries_csv_paths: [], save_names: nil, run_resilience: true, keep_existing_output: false)
|
218
216
|
if !reopt_assumptions_hashes.empty?
|
219
217
|
@feature_reports_reopt_default_assumption_hashes = reopt_assumptions_hashes
|
220
218
|
end
|
@@ -243,36 +241,59 @@ module URBANopt # :nodoc:
|
|
243
241
|
feature_adapter = URBANopt::REopt::FeatureReportAdapter.new
|
244
242
|
new_feature_reports = []
|
245
243
|
feature_reports.each_with_index do |feature_report, idx|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
if
|
252
|
-
|
244
|
+
# check if we should rerun
|
245
|
+
if !(keep_existing_output && output_exists(@feature_reports_reopt_default_output_files[idx]))
|
246
|
+
begin
|
247
|
+
reopt_input = feature_adapter.reopt_json_from_feature_report(feature_report, @feature_reports_reopt_default_assumption_hashes[idx])
|
248
|
+
reopt_output = api.reopt_request(reopt_input, @feature_reports_reopt_default_output_files[idx])
|
249
|
+
if run_resilience
|
250
|
+
run_uuid = reopt_output['outputs']['Scenario']['run_uuid']
|
251
|
+
if File.directory? @feature_reports_reopt_default_output_files[idx]
|
252
|
+
resilience_stats = api.resilience_request(run_uuid, @feature_reports_reopt_default_output_files[idx])
|
253
|
+
else
|
254
|
+
resilience_stats = api.resilience_request(run_uuid, @feature_reports_reopt_default_output_files[idx].sub!('.json', '_resilience.json'))
|
255
|
+
end
|
253
256
|
else
|
254
|
-
resilience_stats =
|
257
|
+
resilience_stats = nil
|
255
258
|
end
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
else
|
265
|
-
warn "Could not save feature reports - the number of save names provided did not match the number of feature reports"
|
259
|
+
new_feature_report = feature_adapter.update_feature_report(feature_report, reopt_output, @feature_reports_timeseries_default_output_files[idx], resilience_stats)
|
260
|
+
new_feature_reports.push(new_feature_report)
|
261
|
+
if !save_names.nil?
|
262
|
+
if save_names.length == feature_reports.length
|
263
|
+
new_feature_report.save save_names[idx]
|
264
|
+
else
|
265
|
+
warn 'Could not save feature reports - the number of save names provided did not match the number of feature reports'
|
266
|
+
end
|
266
267
|
end
|
268
|
+
rescue StandardError
|
269
|
+
@@logger.info("Could not optimize Feature Report #{feature_report.name} #{feature_report.id}")
|
267
270
|
end
|
268
|
-
|
269
|
-
|
271
|
+
else
|
272
|
+
puts('Output file already exists...skipping')
|
270
273
|
end
|
271
274
|
end
|
272
275
|
|
273
276
|
return new_feature_reports
|
274
277
|
end
|
275
278
|
|
279
|
+
# Checks whether a feature has already been run by determining if output files already exists (for rate limit issues and larger projects)
|
280
|
+
##
|
281
|
+
#
|
282
|
+
# [*parameters:*]
|
283
|
+
#
|
284
|
+
# * +output_file+ - _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.
|
285
|
+
# [*return:*] _Boolean_ - Returns true if file or nonempty directory exist
|
286
|
+
def output_exists(output_file)
|
287
|
+
res = false
|
288
|
+
if File.directory?(output_file) && !File.empty?(output_file)
|
289
|
+
res = true
|
290
|
+
elsif File.exist? output_file
|
291
|
+
res = true
|
292
|
+
end
|
293
|
+
|
294
|
+
return res
|
295
|
+
end
|
296
|
+
|
276
297
|
# Updates a ScenarioReport based on an optional set of \REopt Lite optimization assumptions.
|
277
298
|
##
|
278
299
|
#
|
@@ -284,22 +305,26 @@ module URBANopt # :nodoc:
|
|
284
305
|
# * +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.
|
285
306
|
#
|
286
307
|
# [*return:*] _URBANopt::Scenario::DefaultReports::ScenarioReport_ - Returns an updated ScenarioReport
|
287
|
-
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)
|
288
|
-
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)
|
289
|
-
|
308
|
+
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, keep_existing_output: false)
|
309
|
+
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, keep_existing_output: keep_existing_output)
|
310
|
+
puts("KEEP EXISTING? #{keep_existing_output}")
|
311
|
+
# only do this if you have run feature reports
|
290
312
|
new_scenario_report = URBANopt::Reporting::DefaultReports::ScenarioReport.new
|
291
|
-
|
292
|
-
new_scenario_report.name = scenario_report.name
|
293
|
-
new_scenario_report.directory_name = scenario_report.directory_name
|
313
|
+
if !new_feature_reports.empty?
|
294
314
|
|
295
|
-
|
296
|
-
|
315
|
+
new_scenario_report.id = scenario_report.id
|
316
|
+
new_scenario_report.name = scenario_report.name
|
317
|
+
new_scenario_report.directory_name = scenario_report.directory_name
|
297
318
|
|
298
|
-
|
299
|
-
new_scenario_report.
|
300
|
-
|
301
|
-
|
302
|
-
|
319
|
+
timeseries_hash = { column_names: scenario_report.timeseries_csv.column_names }
|
320
|
+
new_scenario_report.timeseries_csv = URBANopt::Reporting::DefaultReports::TimeseriesCSV.new(timeseries_hash)
|
321
|
+
|
322
|
+
new_feature_reports.each do |feature_report|
|
323
|
+
new_scenario_report.add_feature_report(feature_report)
|
324
|
+
end
|
325
|
+
if !save_name_scenario_report.nil?
|
326
|
+
new_scenario_report.save save_name_scenario_report
|
327
|
+
end
|
303
328
|
end
|
304
329
|
return new_scenario_report
|
305
330
|
end
|
@@ -44,8 +44,10 @@ require 'urbanopt/scenario/simulation_dir_osw'
|
|
44
44
|
require 'csv'
|
45
45
|
require 'fileutils'
|
46
46
|
|
47
|
-
|
48
|
-
|
47
|
+
# nodoc:
|
48
|
+
module URBANopt
|
49
|
+
# nodoc:
|
50
|
+
module Scenario
|
49
51
|
class REoptScenarioCSV < ScenarioCSV
|
50
52
|
##
|
51
53
|
# REoptScenarioCSV is an extension of ScenarioCSV which assigns a Simulation Mapper to each Feature in a FeatureFile using a simple CSV format.
|
@@ -101,10 +103,8 @@ module URBANopt # nodoc:
|
|
101
103
|
mapper_class = row[2].chomp
|
102
104
|
# Assume fourth columns, if exists, contains the name of the JSON file in the reopt_files_dir to use when running \REopt Lite for the feature report
|
103
105
|
|
104
|
-
if row.length > 3
|
105
|
-
|
106
|
-
@reopt_feature_assumptions[idx - 1] = File.join(@reopt_files_dir, row[3].chomp)
|
107
|
-
end
|
106
|
+
if row.length > 3 && !@reopt_files_dir.nil?
|
107
|
+
@reopt_feature_assumptions[idx - 1] = File.join(@reopt_files_dir, row[3].chomp)
|
108
108
|
end
|
109
109
|
|
110
110
|
# gets +features+ from the feature_file.
|
@@ -120,6 +120,6 @@ module URBANopt # nodoc:
|
|
120
120
|
end
|
121
121
|
return result
|
122
122
|
end
|
123
|
-
|
123
|
+
end
|
124
124
|
end
|
125
125
|
end
|
@@ -81,22 +81,20 @@ module URBANopt # :nodoc:
|
|
81
81
|
end
|
82
82
|
|
83
83
|
# Update required info
|
84
|
-
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)
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
longs.push(x.location.longitude_deg)
|
93
|
-
end
|
84
|
+
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)) && (!scenario_report.feature_reports.nil? && (scenario_report.feature_reports != []))
|
85
|
+
lats = []
|
86
|
+
longs = []
|
87
|
+
scenario_report.feature_reports.each do |x|
|
88
|
+
puts " ERROR: #{x.location.latitude_deg}"
|
89
|
+
if ![nil].include?(x.location.latitude_deg) && ![nil].include?(x.location.longitude_deg)
|
90
|
+
lats.push(x.location.latitude_deg)
|
91
|
+
longs.push(x.location.longitude_deg)
|
94
92
|
end
|
93
|
+
end
|
95
94
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
end
|
95
|
+
if !lats.empty? && !longs.empty?
|
96
|
+
scenario_report.location.latitude_deg = lats.reduce(:+) / lats.size.to_f
|
97
|
+
scenario_report.location.longitude_deg = longs.reduce(:+) / longs.size.to_f
|
100
98
|
end
|
101
99
|
end
|
102
100
|
|
@@ -120,16 +118,12 @@ module URBANopt # :nodoc:
|
|
120
118
|
|
121
119
|
# Update optional info
|
122
120
|
# REK: attribute names should be updated
|
123
|
-
if reopt_inputs[:Scenario][:Site][:roof_squarefeet].nil?
|
124
|
-
|
125
|
-
reopt_inputs[:Scenario][:Site][:roof_squarefeet] = scenario_report.program.roof_area_sqft[:available_roof_area_sqft]
|
126
|
-
end
|
121
|
+
if reopt_inputs[:Scenario][:Site][:roof_squarefeet].nil? && !scenario_report.program.roof_area_sqft.nil?
|
122
|
+
reopt_inputs[:Scenario][:Site][:roof_squarefeet] = scenario_report.program.roof_area_sqft[:available_roof_area_sqft]
|
127
123
|
end
|
128
124
|
|
129
|
-
if reopt_inputs[:Scenario][:Site][:land_acres].nil?
|
130
|
-
|
131
|
-
reopt_inputs[:Scenario][:Site][:land_acres] = scenario_report.program.site_area_sqft * 1.0 / 43560 # acres/sqft
|
132
|
-
end
|
125
|
+
if reopt_inputs[:Scenario][:Site][:land_acres].nil? && !scenario_report.program.site_area_sqft.nil?
|
126
|
+
reopt_inputs[:Scenario][:Site][:land_acres] = scenario_report.program.site_area_sqft * 1.0 / 43560 # acres/sqft
|
133
127
|
end
|
134
128
|
|
135
129
|
if reopt_inputs[:Scenario][:time_steps_per_hour].nil?
|
@@ -140,17 +134,17 @@ module URBANopt # :nodoc:
|
|
140
134
|
begin
|
141
135
|
col_num = scenario_report.timeseries_csv.column_names.index('Electricity:Facility(kWh)')
|
142
136
|
t = CSV.read(scenario_report.timeseries_csv.path, headers: true, converters: :numeric)
|
143
|
-
energy_timeseries_kw = t.by_col[col_num].map { |e| ((e * scenario_report.timesteps_per_hour || 0)
|
137
|
+
energy_timeseries_kw = t.by_col[col_num].map { |e| ((e * scenario_report.timesteps_per_hour || 0)) }
|
144
138
|
if energy_timeseries_kw.length < (scenario_report.timesteps_per_hour * 8760)
|
145
|
-
start_date = Time.parse(t.by_col[
|
139
|
+
start_date = Time.parse(t.by_col['Datetime'][0])
|
146
140
|
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) / \
|
147
|
-
((
|
148
|
-
end_date = Time.parse(t.by_col[
|
141
|
+
((60 / scenario_report.timesteps_per_hour) * 60)).to_int
|
142
|
+
end_date = Time.parse(t.by_col['Datetime'][-1])
|
149
143
|
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) / \
|
150
|
-
((
|
151
|
-
energy_timeseries_kw = [0.0]*(start_ts-1) + energy_timeseries_kw + [0.0]*((scenario_report.timesteps_per_hour * 8760) - end_ts)
|
144
|
+
((60 / scenario_report.timesteps_per_hour) * 60)).to_int
|
145
|
+
energy_timeseries_kw = [0.0] * (start_ts - 1) + energy_timeseries_kw + [0.0] * ((scenario_report.timesteps_per_hour * 8760) - end_ts)
|
152
146
|
end
|
153
|
-
energy_timeseries_kw = energy_timeseries_kw.map { |e| e
|
147
|
+
energy_timeseries_kw = energy_timeseries_kw.map { |e| e || 0 }[0, (scenario_report.timesteps_per_hour * 8760)]
|
154
148
|
rescue StandardError
|
155
149
|
@@logger.error("Could not parse the annual electric load from the timeseries csv - #{scenario_report.timeseries_csv.path}")
|
156
150
|
raise "Could not parse the annual electric load from the timeseries csv - #{scenario_report.timeseries_csv.path}"
|
@@ -159,7 +153,7 @@ module URBANopt # :nodoc:
|
|
159
153
|
# Convert load to REopt Resolution
|
160
154
|
begin
|
161
155
|
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])
|
162
|
-
rescue
|
156
|
+
rescue StandardError
|
163
157
|
@@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]}")
|
164
158
|
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]}"
|
165
159
|
end
|
@@ -167,9 +161,9 @@ module URBANopt # :nodoc:
|
|
167
161
|
if reopt_inputs[:Scenario][:Site][:ElectricTariff][:coincident_peak_load_active_timesteps].nil?
|
168
162
|
n_top_values = 100
|
169
163
|
tmp1 = reopt_inputs[:Scenario][:Site][:LoadProfile][:loads_kw]
|
170
|
-
tmp2 = tmp1.each_index.max_by(n_top_values*reopt_inputs[:Scenario][:time_steps_per_hour]){|i| tmp1[i]}
|
164
|
+
tmp2 = tmp1.each_index.max_by(n_top_values * reopt_inputs[:Scenario][:time_steps_per_hour]) { |i| tmp1[i] }
|
171
165
|
for i in (0...tmp2.count)
|
172
|
-
|
166
|
+
tmp2[i] += 1
|
173
167
|
end
|
174
168
|
reopt_inputs[:Scenario][:Site][:ElectricTariff][:coincident_peak_load_active_timesteps] = tmp2
|
175
169
|
end
|
@@ -181,7 +175,6 @@ module URBANopt # :nodoc:
|
|
181
175
|
return reopt_inputs
|
182
176
|
end
|
183
177
|
|
184
|
-
|
185
178
|
##
|
186
179
|
# Converts a FeatureReport list from a ScenarioReport into an array of \REopt Lite posts
|
187
180
|
#
|
@@ -215,7 +208,7 @@ module URBANopt # :nodoc:
|
|
215
208
|
#
|
216
209
|
# [*return:*] _URBANopt::Reporting::DefaultReports::ScenarioReport_ - Returns an updated ScenarioReport
|
217
210
|
##
|
218
|
-
def update_scenario_report(scenario_report, reopt_output, timeseries_csv_path=nil, resilience_stats=nil)
|
211
|
+
def update_scenario_report(scenario_report, reopt_output, timeseries_csv_path = nil, resilience_stats = nil)
|
219
212
|
if reopt_output['outputs']['Scenario']['status'] != 'optimal'
|
220
213
|
@@logger.info("Warning cannot Feature Report #{scenario_report.name} #{scenario_report.id} - REopt optimization was non-optimal")
|
221
214
|
return scenario_report
|
@@ -248,56 +241,46 @@ module URBANopt # :nodoc:
|
|
248
241
|
scenario_report.distributed_generation.probs_of_surviving_by_hour_of_the_day = resilience_stats['probs_of_surviving_by_hour_of_the_day']
|
249
242
|
end
|
250
243
|
|
251
|
-
if reopt_output['outputs']['Scenario']['Site']['PV'].
|
244
|
+
if reopt_output['outputs']['Scenario']['Site']['PV'].instance_of?(Hash)
|
252
245
|
reopt_output['outputs']['Scenario']['Site']['PV'] = [reopt_output['outputs']['Scenario']['Site']['PV']]
|
253
246
|
elsif reopt_output['outputs']['Scenario']['Site']['PV'].nil?
|
254
247
|
reopt_output['outputs']['Scenario']['Site']['PV'] = []
|
255
248
|
end
|
256
249
|
|
257
250
|
reopt_output['outputs']['Scenario']['Site']['PV'].each_with_index do |pv, i|
|
258
|
-
scenario_report.distributed_generation.add_tech 'solar_pv',
|
251
|
+
scenario_report.distributed_generation.add_tech 'solar_pv', URBANopt::Reporting::DefaultReports::SolarPV.new({ size_kw: (pv['size_kw'] || 0), id: i })
|
259
252
|
end
|
260
253
|
|
261
254
|
wind = reopt_output['outputs']['Scenario']['Site']['Wind']
|
262
|
-
if !wind['size_kw'].nil?
|
263
|
-
scenario_report.distributed_generation.add_tech 'wind',
|
255
|
+
if !wind['size_kw'].nil? && (wind['size_kw'] != 0)
|
256
|
+
scenario_report.distributed_generation.add_tech 'wind', URBANopt::Reporting::DefaultReports::Wind.new({ size_kw: (wind['size_kw'] || 0) })
|
264
257
|
end
|
265
258
|
|
266
259
|
generator = reopt_output['outputs']['Scenario']['Site']['Generator']
|
267
|
-
if !generator['size_kw'].nil?
|
268
|
-
scenario_report.distributed_generation.add_tech 'generator',
|
260
|
+
if !generator['size_kw'].nil? && (generator['size_kw'] != 0)
|
261
|
+
scenario_report.distributed_generation.add_tech 'generator', URBANopt::Reporting::DefaultReports::Generator.new({ size_kw: (generator['size_kw'] || 0) })
|
269
262
|
end
|
270
263
|
|
271
264
|
storage = reopt_output['outputs']['Scenario']['Site']['Storage']
|
272
|
-
if !storage['size_kw'].nil?
|
273
|
-
scenario_report.distributed_generation.add_tech 'storage',
|
265
|
+
if !storage['size_kw'].nil? && (storage['size_kw'] != 0)
|
266
|
+
scenario_report.distributed_generation.add_tech 'storage', URBANopt::Reporting::DefaultReports::Storage.new({ size_kwh: (storage['size_kwh'] || 0), size_kw: (storage['size_kw'] || 0) })
|
274
267
|
end
|
275
268
|
|
276
269
|
reopt_resolution = reopt_output['inputs']['Scenario']['time_steps_per_hour']
|
277
270
|
generation_timeseries_kwh = Matrix[[0] * (8760 * scenario_report.timesteps_per_hour)]
|
278
271
|
|
279
272
|
reopt_output['outputs']['Scenario']['Site']['PV'].each do |pv|
|
280
|
-
if (pv['size_kw'] || 0) > 0
|
281
|
-
|
282
|
-
generation_timeseries_kwh += Matrix[convert_powerflow_resolution(pv['year_one_power_production_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour)]
|
283
|
-
end
|
273
|
+
if (pv['size_kw'] || 0) > 0 && !pv['year_one_power_production_series_kw'].nil?
|
274
|
+
generation_timeseries_kwh += Matrix[convert_powerflow_resolution(pv['year_one_power_production_series_kw'], reopt_resolution, scenario_report.timesteps_per_hour)]
|
284
275
|
end
|
285
|
-
|
276
|
+
end
|
286
277
|
|
287
|
-
|
288
|
-
|
289
|
-
if !reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_power_production_series_kw'].nil?
|
290
|
-
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)]
|
291
|
-
end
|
292
|
-
end
|
278
|
+
if !reopt_output['outputs']['Scenario']['Site']['Wind'].nil? && ((reopt_output['outputs']['Scenario']['Site']['Wind']['size_kw'] || 0) > 0) && !reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_power_production_series_kw'].nil?
|
279
|
+
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)]
|
293
280
|
end
|
294
281
|
|
295
|
-
|
296
|
-
|
297
|
-
if !reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_power_production_series_kw'].nil?
|
298
|
-
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)]
|
299
|
-
end
|
300
|
-
end
|
282
|
+
if !reopt_output['outputs']['Scenario']['Site']['Generator'].nil? && ((reopt_output['outputs']['Scenario']['Site']['Generator']['size_kw'] || 0) > 0) && !reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_power_production_series_kw'].nil?
|
283
|
+
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)]
|
301
284
|
end
|
302
285
|
|
303
286
|
$generation_timeseries_kwh = generation_timeseries_kwh.to_a[0] || [0] * (8760 * scenario_report.timesteps_per_hour)
|
@@ -476,13 +459,13 @@ module URBANopt # :nodoc:
|
|
476
459
|
start_ts = (
|
477
460
|
(
|
478
461
|
((start_date.yday - 1) * 60.0 * 60.0 * 24) +
|
479
|
-
((
|
480
|
-
(start_date.min * 60.0) + start_date.sec
|
481
|
-
((
|
462
|
+
((start_date.hour - 1) * 60.0 * 60.0) +
|
463
|
+
(start_date.min * 60.0) + start_date.sec) /
|
464
|
+
((60 / scenario_report.timesteps_per_hour) * 60)
|
482
465
|
).to_int
|
483
466
|
mod_data = old_data.map.with_index do |x, i|
|
484
467
|
if i > 0
|
485
|
-
modrow(x, start_ts + i -1)
|
468
|
+
modrow(x, start_ts + i - 1)
|
486
469
|
else
|
487
470
|
x
|
488
471
|
end
|
@@ -1,111 +1,111 @@
|
|
1
|
-
def convert_powerflow_resolution(timeseries_kw, original_res, destination_res)
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
if timeseries_kw.length == 0
|
7
|
-
return nil
|
8
|
-
end
|
9
|
-
|
10
|
-
if original_res > destination_res
|
11
|
-
# Timesteps will be reduced, i.e. 35040 -> 8760
|
12
|
-
|
13
|
-
# This algorithm works by stepping along the origin timeseries at an interval equal to
|
14
|
-
# one timestep in the destintion timeseries and then averaging all origin values that
|
15
|
-
# coincide with the interval. Averages are weighted if a destination timestep
|
16
|
-
# only partaially overlaps an origin timestep.
|
17
|
-
|
18
|
-
# EX 1
|
19
|
-
# stepping interval 2
|
20
|
-
# origin stepping | 1 | 2 | 2 | 4 |
|
21
|
-
# destination stepping | 1.5 | 3 |
|
1
|
+
def convert_powerflow_resolution(timeseries_kw, original_res, destination_res)
|
2
|
+
if timeseries_kw.nil?
|
3
|
+
return nil
|
4
|
+
end
|
22
5
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
# Calc next step length
|
40
|
-
delta_to_next_origin_ts = next_origin_ts - current_origin_ts
|
41
|
-
# Add the proportional origin timestep value to the total power variable
|
42
|
-
total_power.push(Float(timeseries_kw[current_origin_idx]) * delta_to_next_origin_ts)
|
43
|
-
# Only move on to the next origin timestep if you are not ending mid way though an origin timestep
|
44
|
-
# i.e in EX 2 above, the value 4 is needed in destination timestep 1 and 2
|
45
|
-
if next_origin_ts_int <= next_stopping_ts
|
46
|
-
current_origin_idx += 1
|
47
|
-
end
|
48
|
-
# Step to the next stopping point
|
49
|
-
current_origin_ts += delta_to_next_origin_ts
|
50
|
-
end
|
51
|
-
# Add averaged total power variable for the destination time step
|
52
|
-
result.push(Float(total_power.sum) / stepping_interval)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
if destination_res > original_res
|
56
|
-
#Timesteps will be expanded, i.e. 8760 -> 35040
|
6
|
+
if timeseries_kw.empty?
|
7
|
+
return nil
|
8
|
+
end
|
9
|
+
|
10
|
+
if original_res > destination_res
|
11
|
+
# Timesteps will be reduced, i.e. 35040 -> 8760
|
12
|
+
|
13
|
+
# This algorithm works by stepping along the origin timeseries at an interval equal to
|
14
|
+
# one timestep in the destintion timeseries and then averaging all origin values that
|
15
|
+
# coincide with the interval. Averages are weighted if a destination timestep
|
16
|
+
# only partaially overlaps an origin timestep.
|
17
|
+
|
18
|
+
# EX 1
|
19
|
+
# stepping interval 2
|
20
|
+
# origin stepping | 1 | 2 | 2 | 4 |
|
21
|
+
# destination stepping | 1.5 | 3 |
|
57
22
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
# timesteps. In EX 2, the origin overlaps with the first 2 destination timesteps but the third
|
63
|
-
# destination value much be compose of half the 1st origin timestep value and half the second
|
64
|
-
# (i.e 4, 4, (4 * 1/2) + (3 * 1/2), 3, and 3 are added to the destination).
|
65
|
-
|
66
|
-
# EX 1
|
67
|
-
# stepping interval 2
|
68
|
-
# origin stepping | 1 | 2 |
|
69
|
-
# destination stepping | 1 | 1 | 2 | 2 |
|
23
|
+
# EX 2
|
24
|
+
# stepping interval 2.5
|
25
|
+
# origin stepping | 1 | 1 | 4 | 2 | 2 |
|
26
|
+
# destination stepping | 1.6 | 2.4 |
|
70
27
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
end
|
97
|
-
# See if you need to end your step in the middle of a destination time step and
|
98
|
-
# just add the proportional value from the current origin timestep
|
99
|
-
remainder = (stepping_interval - original_indices_stepped)
|
100
|
-
if remainder > 0
|
101
|
-
result.push((Float(timeseries_kw[original_ts]) * remainder))
|
102
|
-
current_destination_ts += remainder
|
103
|
-
end
|
104
|
-
end
|
28
|
+
result = []
|
29
|
+
stepping_interval = Float(original_res) / Float(destination_res)
|
30
|
+
current_origin_ts = 0 # fraction stepped along the origin time series
|
31
|
+
current_origin_idx = 0 # current integer index of the origin timeseries
|
32
|
+
(0..(8760 * destination_res - 1)).each do |ts|
|
33
|
+
next_stopping_ts = current_origin_ts + stepping_interval # stop at the next destination interval
|
34
|
+
total_power = [] # create to store wieghted origin timestep values to average
|
35
|
+
while current_origin_ts != next_stopping_ts
|
36
|
+
next_origin_ts_int = Integer(current_origin_ts) + 1
|
37
|
+
# Calc next stopping point that will being you to the next origin or destination time step
|
38
|
+
next_origin_ts = [next_origin_ts_int, next_stopping_ts].min
|
39
|
+
# Calc next step length
|
40
|
+
delta_to_next_origin_ts = next_origin_ts - current_origin_ts
|
41
|
+
# Add the proportional origin timestep value to the total power variable
|
42
|
+
total_power.push(Float(timeseries_kw[current_origin_idx]) * delta_to_next_origin_ts)
|
43
|
+
# Only move on to the next origin timestep if you are not ending mid way though an origin timestep
|
44
|
+
# i.e in EX 2 above, the value 4 is needed in destination timestep 1 and 2
|
45
|
+
if next_origin_ts_int <= next_stopping_ts
|
46
|
+
current_origin_idx += 1
|
47
|
+
end
|
48
|
+
# Step to the next stopping point
|
49
|
+
current_origin_ts += delta_to_next_origin_ts
|
50
|
+
end
|
51
|
+
# Add averaged total power variable for the destination time step
|
52
|
+
result.push(Float(total_power.sum) / stepping_interval)
|
105
53
|
end
|
106
|
-
|
107
|
-
|
108
|
-
|
54
|
+
end
|
55
|
+
if destination_res > original_res
|
56
|
+
# Timesteps will be expanded, i.e. 8760 -> 35040
|
57
|
+
|
58
|
+
# This algorithm works by stepping along the destination timeseries. Steps are made to the next
|
59
|
+
# destination or origin breakpoint, and at each step the propotional amount of the origin stepped
|
60
|
+
# is added to the destination. For example, in in EX 1 below 4 steps are made each with adding the full amount of
|
61
|
+
# the origin (1, 1, 2 and 2) since each in the destination overlaps perfectly with 2 origin
|
62
|
+
# timesteps. In EX 2, the origin overlaps with the first 2 destination timesteps but the third
|
63
|
+
# destination value much be compose of half the 1st origin timestep value and half the second
|
64
|
+
# (i.e 4, 4, (4 * 1/2) + (3 * 1/2), 3, and 3 are added to the destination).
|
65
|
+
|
66
|
+
# EX 1
|
67
|
+
# stepping interval 2
|
68
|
+
# origin stepping | 1 | 2 |
|
69
|
+
# destination stepping | 1 | 1 | 2 | 2 |
|
70
|
+
|
71
|
+
# EX 2
|
72
|
+
# stepping interval 2.5
|
73
|
+
# origin stepping | 4 | 3 |
|
74
|
+
# destination stepping | 4 | 4 | 3.5 | 3 | 3 |
|
75
|
+
|
76
|
+
result = []
|
77
|
+
stepping_interval = (Float(destination_res) / Float(original_res))
|
78
|
+
current_destination_ts = 0 # fraction stepped along the origin time series
|
79
|
+
(0..(8760 * original_res - 1)).each do |original_ts|
|
80
|
+
# keep track of step length along the destination time series
|
81
|
+
original_indices_stepped = 0
|
82
|
+
# See if you are start in the middle of a destination time step and add the proportional
|
83
|
+
# value to the most recent (and incomplete) destination value
|
84
|
+
remainder = (current_destination_ts - Integer(current_destination_ts))
|
85
|
+
if remainder > 0
|
86
|
+
current_destination_ts += (1 - remainder)
|
87
|
+
original_indices_stepped += (1 - remainder)
|
88
|
+
result[-1] = result[-1] + (Float(timeseries_kw[original_ts]) * (1 - remainder))
|
89
|
+
end
|
90
|
+
# Make whole steps along the destination timeseries that overlap perfectly with the
|
91
|
+
# origin timeseries
|
92
|
+
while (original_indices_stepped < stepping_interval) && ((original_indices_stepped + 1) <= stepping_interval)
|
93
|
+
result.push(Float(timeseries_kw[original_ts]))
|
94
|
+
original_indices_stepped += 1
|
95
|
+
current_destination_ts += 1
|
96
|
+
end
|
97
|
+
# See if you need to end your step in the middle of a destination time step and
|
98
|
+
# just add the proportional value from the current origin timestep
|
99
|
+
remainder = (stepping_interval - original_indices_stepped)
|
100
|
+
if remainder > 0
|
101
|
+
result.push((Float(timeseries_kw[original_ts]) * remainder))
|
102
|
+
current_destination_ts += remainder
|
103
|
+
end
|
109
104
|
end
|
110
|
-
|
111
|
-
|
105
|
+
end
|
106
|
+
if destination_res == original_res
|
107
|
+
# No resolution conversion necessary
|
108
|
+
result = timeseries_kw
|
109
|
+
end
|
110
|
+
return result
|
111
|
+
end
|