urbanopt-reopt 0.2.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/pull_request_template.md +2 -2
- data/.gitignore +3 -0
- data/.rdoc_options +1 -1
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +43 -0
- data/CONTRIBUTING.md +3 -3
- data/Gemfile +12 -22
- data/LICENSE.md +1 -1
- data/RDOC_MAIN.md +13 -13
- data/README.md +15 -15
- data/Rakefile +31 -1
- data/a.txt +1 -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 +11376 -1
- data/docs/package.json +1 -1
- 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 +41 -27
- data/lib/urbanopt/reopt/reopt_lite_api.rb +114 -11
- 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 +55 -38
- data/lib/urbanopt/reopt/version.rb +2 -2
- data/lib/urbanopt/reopt_scenario.rb +1 -1
- data/urbanopt-reopt.gemspec +12 -7
- metadata +55 -27
- data/.travis.yml +0 -22
data/docs/package.json
CHANGED
@@ -10,7 +10,7 @@
|
|
10
10
|
},
|
11
11
|
"author": "NREL",
|
12
12
|
"dependencies": {
|
13
|
-
"highlight.js": "^
|
13
|
+
"highlight.js": "^10.4.1",
|
14
14
|
"json-schema-ref-parser": "^6.1.0",
|
15
15
|
"json-schema-view-js": "git+https://git@github.com/bgschiller/json-schema-view-js.git",
|
16
16
|
"vuepress": "^0.14.10",
|
data/index.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# **URBANopt REopt Gem**
|
2
2
|
|
3
|
-
The **URBANopt REopt Gem** extends **URBANopt::
|
4
|
-
REopt Lite is a technoeconomic model which leverages mixed integer linear programming to identify the cost-optimal sizing of solar PV, Wind, Storage and/or diesel generation given an electric load profile, a utility rate tariff and other technoeconomic parameters. See [https://developer.nrel.gov/docs/energy-optimization/reopt-v1/](https://developer.nrel.gov/docs/energy-optimization/reopt-v1/) for more detailed information on input parameters and default assumptions.
|
3
|
+
The **URBANopt<sup>™</sup> REopt Gem** extends **URBANopt::Reporting::DefaultReports::ScenarioReport** and **URBANopt::Reporting::DefaultReports::FeatureReport** with the ability to derive cost-optimal distributed energy resource (DER) technology sizes and annual dispatch strageties via the [REopt Lite](https://reopt.nrel.gov/tool) decision support platform.
|
4
|
+
REopt Lite is a technoeconomic model which leverages mixed integer linear programming to identify the cost-optimal sizing of solar PV, Wind, Storage and/or diesel generation given an electric load profile, a utility rate tariff and other technoeconomic parameters. See [https://developer.nrel.gov/docs/energy-optimization/reopt-v1/](https://developer.nrel.gov/docs/energy-optimization/reopt-v1/) for more detailed information on input parameters and default assumptions.
|
5
5
|
|
6
6
|
See the [example project](https://github.com/urbanopt/urbanopt-example-reopt-project.git) for more infomation about usage of this gem.
|
7
7
|
|
@@ -9,7 +9,7 @@ See the [example project](https://github.com/urbanopt/urbanopt-example-reopt-pro
|
|
9
9
|
|
10
10
|
## Installation
|
11
11
|
|
12
|
-
See [https://docs.urbanopt.net/installation/installation.html](https://docs.urbanopt.net/installation/installation.html) for instructions on prerequiste software, including:
|
12
|
+
See [https://docs.urbanopt.net/installation/installation.html](https://docs.urbanopt.net/installation/installation.html) for instructions on prerequiste software, including:
|
13
13
|
- Ruby 2.2.6
|
14
14
|
- Bundler 1.17.0
|
15
15
|
- OpenStudio 2.8.1
|
@@ -31,7 +31,7 @@ Or install it yourself as:
|
|
31
31
|
|
32
32
|
## Functionality
|
33
33
|
|
34
|
-
This gem is used to call the REopt Lite API on a Scenario Report or Feature Report to update the object's Distributed Generation attributes (including system financial and sizing metrics) as shown in an example below:
|
34
|
+
This gem is used to call the REopt Lite API on a Scenario Report or Feature Report to update the object's Distributed Generation attributes (including system financial and sizing metrics) as shown in an example below:
|
35
35
|
|
36
36
|
```
|
37
37
|
"distributed_generation": {
|
@@ -83,7 +83,7 @@ Moreover, the following optimal dispatch fields are added to its timeseries CSV.
|
|
83
83
|
| ElectricityProduced:Wind:ToGrid | kWh |
|
84
84
|
```
|
85
85
|
|
86
|
-
The REopt Lite has default values for all non-required input parameters that are used unless the user specifies custom assumptions. See [https://developer.nrel.gov/docs/energy-optimization/reopt-v1/](https://developer.nrel.gov/docs/energy-optimization/reopt-v1/) for more detailed information on input parameters and default assumptions.
|
86
|
+
The REopt Lite has default values for all non-required input parameters that are used unless the user specifies custom assumptions. See [https://developer.nrel.gov/docs/energy-optimization/reopt-v1/](https://developer.nrel.gov/docs/energy-optimization/reopt-v1/) for more detailed information on input parameters and default assumptions.
|
87
87
|
|
88
88
|
<b>Note:</b> Required attributes for a REopt run include latitude and longitude. If no utility rate is specified in your REopt Lite assumption settings, then a constant default rate of $0.13 is assumed without demand charges. Also, by default, only solar PV and storage are considered in the analysis (i.e. Wind and Generators are excluded from consideration).
|
89
89
|
|
@@ -99,7 +99,7 @@ require 'urbanopt/reopt'
|
|
99
99
|
feature_reports_hash = {} # <insert a Feature Report hash here>
|
100
100
|
|
101
101
|
#Create a Feature Report
|
102
|
-
feature_report = URBANopt::
|
102
|
+
feature_report = URBANopt::Reporting::DefaultReports::FeatureReport.new(feature_reports_hash)
|
103
103
|
|
104
104
|
#Specify a file name where REopt Lite results will be written in JSON format
|
105
105
|
reopt_output_file = File.join(feature_report.directory_name, 'feature_report_reopt_run1.json')
|
@@ -115,7 +115,7 @@ reopt_post_processor = URBANopt::REopt::REoptPostProcessor.new(nil, nil, nil, DE
|
|
115
115
|
|
116
116
|
#Call REopt Lite with the post processor to update the feature's distributed generation attributes and timeseries CSV.
|
117
117
|
updated_feature_report = reopt_post_processor.run_feature_report(feature_report,reopt_assumptions_file,reopt_output_file,timeseries_output_file)
|
118
|
-
|
118
|
+
|
119
119
|
```
|
120
120
|
|
121
121
|
More commonly, this gem can be used to run REopt a collection of features stored in a Scenario Report as show here:
|
@@ -123,9 +123,9 @@ More commonly, this gem can be used to run REopt a collection of features stored
|
|
123
123
|
```ruby
|
124
124
|
require 'urbanopt/reopt'
|
125
125
|
#Create a Scenario Report
|
126
|
-
scenario_report = URBANopt::
|
126
|
+
scenario_report = URBANopt::Reporting::DefaultReports::ScenarioReport.new({:directory_name => File.join(File.dirname(__FILE__), '../run/example_scenario'), :timeseries_csv => {:path => File.join(File.dirname(__FILE__), '../run/example_scenario/timeseries.csv') }})
|
127
127
|
|
128
|
-
#Load Feature Reports into the Scenario Report
|
128
|
+
#Load Feature Reports into the Scenario Report
|
129
129
|
(1..2).each do |i|
|
130
130
|
feature_reports_path = File.join(File.dirname(__FILE__), "../run/example_scenario/#{i}/010_default_feature_reports/default_feature_reports.json")
|
131
131
|
|
@@ -134,8 +134,8 @@ scenario_report = URBANopt::Scenario::DefaultReports::ScenarioReport.new({:direc
|
|
134
134
|
feature_reports_hash = JSON.parse(file.read, symbolize_names: true)
|
135
135
|
end
|
136
136
|
|
137
|
-
feature_report = URBANopt::
|
138
|
-
|
137
|
+
feature_report = URBANopt::Reporting::DefaultReports::FeatureReport.new(feature_reports_hash)
|
138
|
+
|
139
139
|
feature_report_dir = File.join(File.dirname(__FILE__), "../run/example_scenario/#{i}")
|
140
140
|
feature_report.directory_name = feature_report_dir
|
141
141
|
|
@@ -150,7 +150,7 @@ reopt_post_processor = URBANopt::REopt::REoptPostProcessor.new(scenario_report,
|
|
150
150
|
|
151
151
|
#Call REopt Lite with the post processor once on the sceanrio's aggregated load to update the scenario's distributed generation attributes and timeseries CSV.
|
152
152
|
updated_scenario_report = reopt_post_processor.run_scenario_report(scenario_report)
|
153
|
-
|
153
|
+
|
154
154
|
```
|
155
155
|
|
156
156
|
## Testing
|
@@ -164,7 +164,7 @@ Next, obtain a developer.nrel.gov API key from the [NREL Developer Network](http
|
|
164
164
|
Finally, execute:
|
165
165
|
|
166
166
|
$ bundle install
|
167
|
-
$ bundle update
|
167
|
+
$ bundle update
|
168
168
|
$ bundle exec rake
|
169
169
|
|
170
170
|
|
data/lib/urbanopt-reopt.rb
CHANGED
@@ -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,
|
data/lib/urbanopt/reopt.rb
CHANGED
@@ -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,
|
@@ -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,7 +28,7 @@
|
|
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 'csv'
|
34
34
|
require 'matrix'
|
@@ -38,7 +38,7 @@ module URBANopt # :nodoc:
|
|
38
38
|
module REopt # :nodoc:
|
39
39
|
class FeatureReportAdapter
|
40
40
|
##
|
41
|
-
# FeatureReportAdapter can convert a URBANopt::
|
41
|
+
# FeatureReportAdapter can convert a URBANopt::Reporting::DefaultReports::FeatureReport into a \REopt Lite posts or update a URBANopt::Reporting::DefaultReports::FeatureReport from a \REopt Lite response.
|
42
42
|
##
|
43
43
|
# [*parameters:*]
|
44
44
|
##
|
@@ -52,7 +52,7 @@ module URBANopt # :nodoc:
|
|
52
52
|
#
|
53
53
|
# [*parameters:*]
|
54
54
|
#
|
55
|
-
# * +feature_report+ - _URBANopt::
|
55
|
+
# * +feature_report+ - _URBANopt::Reporting::DefaultReports::FeatureReport_ - FeatureReport to use in converting the optional +reopt_assumptions_hash+ to a \REopt Lite post. If a +reopt_assumptions_hash+ is not provided, a default post will be updated from this FeatureReport and submitted to the \REopt Lite API.
|
56
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 FeatureReport where available (i.e. latitude, roof_squarefeet). Missing optional parameters will be filled in with default values by the API.
|
57
57
|
#
|
58
58
|
# [*return:*] _Hash_ - Returns hash formatted for submittal to the \REopt Lite API
|
@@ -69,11 +69,11 @@ module URBANopt # :nodoc:
|
|
69
69
|
|
70
70
|
# Check FeatureReport has required data
|
71
71
|
requireds_names = ['latitude', 'longitude']
|
72
|
-
requireds = [feature_report.location.
|
72
|
+
requireds = [feature_report.location.latitude_deg, feature_report.location.longitude_deg]
|
73
73
|
|
74
74
|
if requireds.include?(nil) || requireds.include?(0)
|
75
75
|
requireds.each_with_index do |i, x|
|
76
|
-
if [nil
|
76
|
+
if [nil].include? x
|
77
77
|
n = requireds_names[i]
|
78
78
|
# @@logger.error("Missing value for #{n} - this is a required input")
|
79
79
|
raise "Missing value for #{n} - this is a required input"
|
@@ -84,16 +84,16 @@ module URBANopt # :nodoc:
|
|
84
84
|
reopt_inputs[:Scenario][:description] = description
|
85
85
|
|
86
86
|
# Parse Location
|
87
|
-
reopt_inputs[:Scenario][:Site][:latitude] = feature_report.location.
|
88
|
-
reopt_inputs[:Scenario][:Site][:longitude] = feature_report.location.
|
87
|
+
reopt_inputs[:Scenario][:Site][:latitude] = feature_report.location.latitude_deg
|
88
|
+
reopt_inputs[:Scenario][:Site][:longitude] = feature_report.location.longitude_deg
|
89
89
|
|
90
90
|
# Parse Optional FeatureReport metrics
|
91
|
-
unless feature_report.program.
|
92
|
-
reopt_inputs[:Scenario][:Site][:roof_squarefeet] = feature_report.program.
|
91
|
+
unless feature_report.program.roof_area_sqft.nil?
|
92
|
+
reopt_inputs[:Scenario][:Site][:roof_squarefeet] = feature_report.program.roof_area_sqft[:available_roof_area]
|
93
93
|
end
|
94
94
|
|
95
|
-
unless feature_report.program.
|
96
|
-
reopt_inputs[:Scenario][:Site][:land_acres] = feature_report.program.
|
95
|
+
unless feature_report.program.site_area_sqft.nil?
|
96
|
+
reopt_inputs[:Scenario][:Site][:land_acres] = feature_report.program.site_area_sqft * 1.0 / 43560 # acres/sqft
|
97
97
|
end
|
98
98
|
|
99
99
|
unless feature_report.timesteps_per_hour.nil?
|
@@ -127,19 +127,19 @@ module URBANopt # :nodoc:
|
|
127
127
|
#
|
128
128
|
# [*parameters:*]
|
129
129
|
#
|
130
|
-
# * +feature_report+ - _URBANopt::
|
130
|
+
# * +feature_report+ - _URBANopt::Reporting::DefaultReports::FeatureReport_ - FeatureReport to update from a \REopt Lite reponse hash.
|
131
131
|
# * +reopt_output+ - _Hash_ - A reponse hash from the \REopt Lite API to use in overwriting FeatureReport technology sizes, costs and dispatch strategies.
|
132
132
|
# * +timeseries_csv_path+ - _String_ - Optional. The path to a file at which a new timeseries CSV will be written. If not provided a file is created based on the run_uuid of the \REopt Lite optimization task.
|
133
133
|
#
|
134
|
-
# [*return:*] _URBANopt::
|
134
|
+
# [*return:*] _URBANopt::Reporting::DefaultReports::FeatureReport_ - Returns an updated FeatureReport.
|
135
135
|
##
|
136
|
-
def update_feature_report(feature_report, reopt_output, timeseries_csv_path =
|
136
|
+
def update_feature_report(feature_report, reopt_output, timeseries_csv_path=nil, resilience_stats=nil)
|
137
137
|
# Check if the \REopt Lite response is valid
|
138
138
|
if reopt_output['outputs']['Scenario']['status'] != 'optimal'
|
139
139
|
@@logger.info("Warning cannot Feature Report #{feature_report.name} #{feature_report.id} - REopt optimization was non-optimal")
|
140
140
|
return feature_report
|
141
141
|
end
|
142
|
-
|
142
|
+
|
143
143
|
$ts_per_hour = feature_report.timesteps_per_hour
|
144
144
|
def scale_timeseries(input, ts_per_hr=$ts_per_hour)
|
145
145
|
if input.nil?
|
@@ -152,7 +152,7 @@ module URBANopt # :nodoc:
|
|
152
152
|
return input
|
153
153
|
end
|
154
154
|
result = []
|
155
|
-
input.each do |val|
|
155
|
+
input.each do |val|
|
156
156
|
(1..ts_per_hr).each do |x|
|
157
157
|
result.push(val/ts_per_hr.to_f)
|
158
158
|
end
|
@@ -161,8 +161,8 @@ module URBANopt # :nodoc:
|
|
161
161
|
end
|
162
162
|
|
163
163
|
# Update location
|
164
|
-
feature_report.location.
|
165
|
-
feature_report.location.
|
164
|
+
feature_report.location.latitude_deg = reopt_output['inputs']['Scenario']['Site']['latitude']
|
165
|
+
feature_report.location.longitude_deg = reopt_output['inputs']['Scenario']['Site']['longitude']
|
166
166
|
|
167
167
|
# Update timeseries csv from \REopt Lite dispatch data
|
168
168
|
feature_report.timesteps_per_hour = reopt_output['inputs']['Scenario']['time_steps_per_hour']
|
@@ -174,36 +174,50 @@ module URBANopt # :nodoc:
|
|
174
174
|
feature_report.distributed_generation.year_one_demand_cost_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_demand_cost_us_dollars'] || 0
|
175
175
|
feature_report.distributed_generation.year_one_bill_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_bill_us_dollars'] || 0
|
176
176
|
feature_report.distributed_generation.total_energy_cost_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['total_energy_cost_us_dollars'] || 0
|
177
|
+
feature_report.distributed_generation.total_demand_cost_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['total_demand_cost_us_dollars'] || 0
|
178
|
+
feature_report.distributed_generation.year_one_energy_cost_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_energy_cost_bau_us_dollars'] || 0
|
179
|
+
feature_report.distributed_generation.year_one_demand_cost_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_demand_cost_bau_us_dollars'] || 0
|
180
|
+
feature_report.distributed_generation.year_one_bill_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_bill_bau_us_dollars'] || 0
|
181
|
+
feature_report.distributed_generation.total_demand_cost_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['total_demand_cost_bau_us_dollars'] || 0
|
182
|
+
feature_report.distributed_generation.total_energy_cost_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['total_energy_cost_bau_us_dollars'] || 0
|
183
|
+
if !resilience_stats.nil?
|
184
|
+
feature_report.distributed_generation.resilience_hours_min = resilience_stats['resilience_hours_min']
|
185
|
+
feature_report.distributed_generation.resilience_hours_max = resilience_stats['resilience_hours_max']
|
186
|
+
feature_report.distributed_generation.resilience_hours_avg = resilience_stats['resilience_hours_avg']
|
187
|
+
feature_report.distributed_generation.probs_of_surviving = resilience_stats['probs_of_surviving']
|
188
|
+
feature_report.distributed_generation.probs_of_surviving_by_month = resilience_stats['probs_of_surviving_by_month']
|
189
|
+
feature_report.distributed_generation.probs_of_surviving_by_hour_of_the_day = resilience_stats['probs_of_surviving_by_hour_of_the_day']
|
190
|
+
end
|
177
191
|
|
178
192
|
if reopt_output['outputs']['Scenario']['Site']['PV'].class == Hash
|
179
193
|
reopt_output['outputs']['Scenario']['Site']['PV'] = [reopt_output['outputs']['Scenario']['Site']['PV']]
|
180
194
|
elsif reopt_output['outputs']['Scenario']['Site']['PV'].nil?
|
181
195
|
reopt_output['outputs']['Scenario']['Site']['PV'] = []
|
182
196
|
end
|
183
|
-
|
184
|
-
reopt_output['outputs']['Scenario']['Site']['PV'].each_with_index do |pv, i|
|
185
|
-
feature_report.distributed_generation.add_tech 'solar_pv', URBANopt::
|
197
|
+
|
198
|
+
reopt_output['outputs']['Scenario']['Site']['PV'].each_with_index do |pv, i|
|
199
|
+
feature_report.distributed_generation.add_tech 'solar_pv', URBANopt::Reporting::DefaultReports::SolarPV.new( {size_kw: (pv['size_kw'] || 0), id: i })
|
186
200
|
end
|
187
201
|
|
188
202
|
wind = reopt_output['outputs']['Scenario']['Site']['Wind']
|
189
203
|
if !wind['size_kw'].nil? and wind['size_kw'] != 0
|
190
|
-
feature_report.distributed_generation.add_tech 'wind', URBANopt::
|
204
|
+
feature_report.distributed_generation.add_tech 'wind', URBANopt::Reporting::DefaultReports::Wind.new( {size_kw: (wind['size_kw'] || 0) })
|
191
205
|
end
|
192
206
|
|
193
207
|
generator = reopt_output['outputs']['Scenario']['Site']['Generator']
|
194
208
|
if !generator['size_kw'].nil? and generator['size_kw'] != 0
|
195
|
-
feature_report.distributed_generation.add_tech 'generator', URBANopt::
|
209
|
+
feature_report.distributed_generation.add_tech 'generator', URBANopt::Reporting::DefaultReports::Generator.new( {size_kw: (generator['size_kw'] || 0) })
|
196
210
|
end
|
197
211
|
|
198
212
|
storage = reopt_output['outputs']['Scenario']['Site']['Storage']
|
199
213
|
if !storage['size_kw'].nil? and storage['size_kw'] != 0
|
200
|
-
feature_report.distributed_generation.add_tech 'storage', URBANopt::
|
214
|
+
feature_report.distributed_generation.add_tech 'storage', URBANopt::Reporting::DefaultReports::Storage.new( {size_kwh: (storage['size_kwh'] || 0), size_kw: (storage['size_kw'] || 0) })
|
201
215
|
end
|
202
|
-
|
216
|
+
|
203
217
|
generation_timeseries_kwh = Matrix[[0] * (8760 * feature_report.timesteps_per_hour)]
|
204
218
|
|
205
219
|
unless reopt_output['outputs']['Scenario']['Site']['PV'].nil?
|
206
|
-
reopt_output['outputs']['Scenario']['Site']['PV'].each do |pv|
|
220
|
+
reopt_output['outputs']['Scenario']['Site']['PV'].each do |pv|
|
207
221
|
if (pv['size_kw'] || 0) > 0
|
208
222
|
if !pv['year_one_power_production_series_kw'].nil?
|
209
223
|
generation_timeseries_kwh += Matrix[pv['year_one_power_production_series_kw']]
|
@@ -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,80 @@ 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
|
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
|
+
raise "Error from REopt API - #{data['Error']}"
|
226
|
+
end
|
227
|
+
|
135
228
|
##
|
136
229
|
# Completes a \REopt Lite optimization. From a formatted hash, an optimization task is submitted to the API.
|
137
230
|
# Results are polled at 5 second interval until they are ready or an error is returned from the API. Results
|
@@ -157,13 +250,13 @@ module URBANopt # :nodoc:
|
|
157
250
|
http.use_ssl = true
|
158
251
|
end
|
159
252
|
request = Net::HTTP::Post.new(@uri_submit, header)
|
160
|
-
request.body = reopt_input
|
253
|
+
request.body = ::JSON.generate(reopt_input, allow_nan: true)
|
161
254
|
|
162
255
|
# Send the request
|
163
256
|
response = make_request(http, request)
|
164
257
|
|
165
258
|
# Get UUID
|
166
|
-
run_uuid = JSON.parse(response.body)['run_uuid']
|
259
|
+
run_uuid = JSON.parse(response.body, allow_nan:true)['run_uuid']
|
167
260
|
|
168
261
|
if File.directory? filename
|
169
262
|
if run_uuid.nil?
|
@@ -176,10 +269,12 @@ module URBANopt # :nodoc:
|
|
176
269
|
@@logger.info("REopt results saved to #{filename}")
|
177
270
|
end
|
178
271
|
|
272
|
+
text = ::JSON.generate(response.body, allow_nan: true)
|
179
273
|
if response.code != '201'
|
180
|
-
File.open(filename, 'w') do |f|
|
181
|
-
f.
|
274
|
+
File.open(filename, 'w+') do |f|
|
275
|
+
f.puts(text)
|
182
276
|
end
|
277
|
+
@@logger.info("Cannot write - #{filename}")
|
183
278
|
raise "Error in REopt optimization post - see #{filename}"
|
184
279
|
end
|
185
280
|
|
@@ -195,7 +290,9 @@ module URBANopt # :nodoc:
|
|
195
290
|
|
196
291
|
while status == 'Optimizing...'
|
197
292
|
response = make_request(http, request)
|
198
|
-
|
293
|
+
|
294
|
+
data = JSON.parse(response.body, allow_nan:true)
|
295
|
+
|
199
296
|
if data['outputs']['Scenario']['Site']['PV'].kind_of?(Array)
|
200
297
|
pv_sizes = 0
|
201
298
|
data['outputs']['Scenario']['Site']['PV'].each do |x|
|
@@ -216,7 +313,7 @@ module URBANopt # :nodoc:
|
|
216
313
|
while (_tries < _max_retry) && check_complete
|
217
314
|
sleep 1
|
218
315
|
response = make_request(http, request)
|
219
|
-
data = JSON.parse(response.body)
|
316
|
+
data = JSON.parse(response.body, allow_nan:true)
|
220
317
|
if data['outputs']['Scenario']['Site']['PV'].kind_of?(Array)
|
221
318
|
pv_sizes = 0
|
222
319
|
data['outputs']['Scenario']['Site']['PV'].each do |x|
|
@@ -230,8 +327,14 @@ module URBANopt # :nodoc:
|
|
230
327
|
_tries += 1
|
231
328
|
end
|
232
329
|
|
233
|
-
|
234
|
-
|
330
|
+
data = JSON.parse(response.body)
|
331
|
+
text = ::JSON.generate(data, allow_nan: true)
|
332
|
+
begin
|
333
|
+
File.open(filename, 'w+') do |f|
|
334
|
+
f.puts(text)
|
335
|
+
end
|
336
|
+
rescue
|
337
|
+
@@logger.info("Cannot write - #{filename}")
|
235
338
|
end
|
236
339
|
|
237
340
|
if status == 'optimal'
|