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
data/docs/package.json
CHANGED
@@ -10,13 +10,17 @@
|
|
10
10
|
},
|
11
11
|
"author": "NREL",
|
12
12
|
"dependencies": {
|
13
|
-
"
|
13
|
+
"braces": "^3.0.2",
|
14
|
+
"highlight.js": "^10.4.1",
|
14
15
|
"json-schema-ref-parser": "^6.1.0",
|
15
16
|
"json-schema-view-js": "git+https://git@github.com/bgschiller/json-schema-view-js.git",
|
16
|
-
"
|
17
|
-
"
|
17
|
+
"serialize-javascript": "^5.0.1",
|
18
|
+
"vuepress": "^1.6.0",
|
19
|
+
"webpack-dev-middleware": "^3.6.0",
|
20
|
+
"yargs-parser": "^18.1.1"
|
18
21
|
},
|
19
22
|
"devDependencies": {
|
20
|
-
"gh-pages": "^2.0.1"
|
23
|
+
"gh-pages": "^2.0.1",
|
24
|
+
"ini": "^2.0.0"
|
21
25
|
}
|
22
26
|
}
|
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,17 +28,18 @@
|
|
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'
|
35
|
+
require_relative 'utilities'
|
35
36
|
require 'time'
|
36
37
|
|
37
38
|
module URBANopt # :nodoc:
|
38
39
|
module REopt # :nodoc:
|
39
40
|
class FeatureReportAdapter
|
40
41
|
##
|
41
|
-
# FeatureReportAdapter can convert a URBANopt::
|
42
|
+
# 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
43
|
##
|
43
44
|
# [*parameters:*]
|
44
45
|
##
|
@@ -52,7 +53,7 @@ module URBANopt # :nodoc:
|
|
52
53
|
#
|
53
54
|
# [*parameters:*]
|
54
55
|
#
|
55
|
-
# * +feature_report+ - _URBANopt::
|
56
|
+
# * +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
57
|
# * +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
58
|
#
|
58
59
|
# [*return:*] _Hash_ - Returns hash formatted for submittal to the \REopt Lite API
|
@@ -69,11 +70,11 @@ module URBANopt # :nodoc:
|
|
69
70
|
|
70
71
|
# Check FeatureReport has required data
|
71
72
|
requireds_names = ['latitude', 'longitude']
|
72
|
-
requireds = [feature_report.location.
|
73
|
+
requireds = [feature_report.location.latitude_deg, feature_report.location.longitude_deg]
|
73
74
|
|
74
75
|
if requireds.include?(nil) || requireds.include?(0)
|
75
76
|
requireds.each_with_index do |i, x|
|
76
|
-
if [nil
|
77
|
+
if [nil].include? x
|
77
78
|
n = requireds_names[i]
|
78
79
|
# @@logger.error("Missing value for #{n} - this is a required input")
|
79
80
|
raise "Missing value for #{n} - this is a required input"
|
@@ -84,27 +85,33 @@ module URBANopt # :nodoc:
|
|
84
85
|
reopt_inputs[:Scenario][:description] = description
|
85
86
|
|
86
87
|
# Parse Location
|
87
|
-
reopt_inputs[:Scenario][:Site][:latitude] = feature_report.location.
|
88
|
-
reopt_inputs[:Scenario][:Site][:longitude] = feature_report.location.
|
88
|
+
reopt_inputs[:Scenario][:Site][:latitude] = feature_report.location.latitude_deg
|
89
|
+
reopt_inputs[:Scenario][:Site][:longitude] = feature_report.location.longitude_deg
|
89
90
|
|
90
|
-
# Parse Optional FeatureReport metrics
|
91
|
-
|
92
|
-
|
91
|
+
# Parse Optional FeatureReport metrics - do not overwrite from assumptions file
|
92
|
+
if reopt_inputs[:Scenario][:Site][:roof_squarefeet].nil?
|
93
|
+
unless feature_report.program.roof_area_sqft.nil?
|
94
|
+
reopt_inputs[:Scenario][:Site][:roof_squarefeet] = feature_report.program.roof_area_sqft[:available_roof_area_sqft]
|
95
|
+
end
|
93
96
|
end
|
94
97
|
|
95
|
-
|
96
|
-
|
98
|
+
if reopt_inputs[:Scenario][:Site][:land_acres].nil?
|
99
|
+
unless feature_report.program.site_area_sqft.nil?
|
100
|
+
reopt_inputs[:Scenario][:Site][:land_acres] = feature_report.program.site_area_sqft * 1.0 / 43560 # acres/sqft
|
101
|
+
end
|
97
102
|
end
|
98
103
|
|
99
|
-
|
100
|
-
reopt_inputs[:Scenario][:time_steps_per_hour] =
|
104
|
+
if reopt_inputs[:Scenario][:time_steps_per_hour].nil?
|
105
|
+
reopt_inputs[:Scenario][:time_steps_per_hour] = 1
|
101
106
|
end
|
102
107
|
|
103
108
|
# Parse Load Profile
|
104
109
|
begin
|
110
|
+
#Convert kWh values in the timeseries CSV to kW
|
105
111
|
col_num = feature_report.timeseries_csv.column_names.index('Electricity:Facility(kWh)')
|
106
112
|
t = CSV.read(feature_report.timeseries_csv.path, headers: true, converters: :numeric)
|
107
113
|
energy_timeseries_kw = t.by_col[col_num].map { |e| ((e * feature_report.timesteps_per_hour || 0) ) }
|
114
|
+
#Fill in missing timestep values with 0 if a full year is not provided
|
108
115
|
if energy_timeseries_kw.length < (feature_report.timesteps_per_hour * 8760)
|
109
116
|
start_date = Time.parse(t.by_col["Datetime"][0])
|
110
117
|
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) /
|
@@ -113,59 +120,63 @@ module URBANopt # :nodoc:
|
|
113
120
|
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) /
|
114
121
|
(( 60 / feature_report.timesteps_per_hour ) * 60)).to_int
|
115
122
|
energy_timeseries_kw = [0.0]*(start_ts-1) + energy_timeseries_kw + [0.0]*((feature_report.timesteps_per_hour * 8760) - end_ts)
|
116
|
-
end
|
117
|
-
|
123
|
+
end
|
124
|
+
#Clip to one non-leap year's worth of data
|
125
|
+
energy_timeseries_kw = energy_timeseries_kw.map { |e| e ? e : 0 }[0,(feature_report.timesteps_per_hour * 8760)]
|
126
|
+
#Convert from the OpenDSS resolution to the REopt Lite resolution, if necessary
|
118
127
|
rescue StandardError
|
119
128
|
@@logger.error("Could not parse the annual electric load from the timeseries csv - #{feature_report.timeseries_csv.path}")
|
120
129
|
raise "Could not parse the annual electric load from the timeseries csv - #{feature_report.timeseries_csv.path}"
|
121
130
|
end
|
131
|
+
|
132
|
+
# Convert load to REopt Resolution
|
133
|
+
begin
|
134
|
+
reopt_inputs[:Scenario][:Site][:LoadProfile][:loads_kw] = convert_powerflow_resolution(energy_timeseries_kw, feature_report.timesteps_per_hour, reopt_inputs[:Scenario][:time_steps_per_hour])
|
135
|
+
|
136
|
+
rescue
|
137
|
+
@@logger.error("Could not convert the annual electric load from a resolution of #{feature_report.timesteps_per_hour} to #{reopt_inputs[:Scenario][:time_steps_per_hour]}")
|
138
|
+
raise "Could not convert the annual electric load from a resolution of #{feature_report.timesteps_per_hour} to #{reopt_inputs[:Scenario][:time_steps_per_hour]}"
|
139
|
+
end
|
140
|
+
|
141
|
+
if reopt_inputs[:Scenario][:Site][:ElectricTariff][:coincident_peak_load_active_timesteps].nil?
|
142
|
+
n_top_values = 100
|
143
|
+
tmp1 = reopt_inputs[:Scenario][:Site][:LoadProfile][:loads_kw]
|
144
|
+
tmp2 = tmp1.each_index.max_by(n_top_values*reopt_inputs[:Scenario][:time_steps_per_hour]){|i| tmp1[i]}
|
145
|
+
for i in (0...tmp2.count)
|
146
|
+
tmp2[i] += 1
|
147
|
+
end
|
148
|
+
reopt_inputs[:Scenario][:Site][:ElectricTariff][:coincident_peak_load_active_timesteps] = tmp2
|
149
|
+
end
|
150
|
+
|
151
|
+
if reopt_inputs[:Scenario][:Site][:ElectricTariff][:coincident_peak_load_charge_us_dollars_per_kw].nil?
|
152
|
+
reopt_inputs[:Scenario][:Site][:ElectricTariff][:coincident_peak_load_charge_us_dollars_per_kw] = 0
|
153
|
+
end
|
154
|
+
|
122
155
|
return reopt_inputs
|
123
156
|
end
|
124
157
|
|
158
|
+
|
125
159
|
##
|
126
160
|
# Update a FeatureReport from a \REopt Lite response
|
127
161
|
#
|
128
162
|
# [*parameters:*]
|
129
163
|
#
|
130
|
-
# * +feature_report+ - _URBANopt::
|
164
|
+
# * +feature_report+ - _URBANopt::Reporting::DefaultReports::FeatureReport_ - FeatureReport to update from a \REopt Lite reponse hash.
|
131
165
|
# * +reopt_output+ - _Hash_ - A reponse hash from the \REopt Lite API to use in overwriting FeatureReport technology sizes, costs and dispatch strategies.
|
132
166
|
# * +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
167
|
#
|
134
|
-
# [*return:*] _URBANopt::
|
168
|
+
# [*return:*] _URBANopt::Reporting::DefaultReports::FeatureReport_ - Returns an updated FeatureReport.
|
135
169
|
##
|
136
|
-
def update_feature_report(feature_report, reopt_output, timeseries_csv_path =
|
170
|
+
def update_feature_report(feature_report, reopt_output, timeseries_csv_path=nil, resilience_stats=nil)
|
137
171
|
# Check if the \REopt Lite response is valid
|
138
172
|
if reopt_output['outputs']['Scenario']['status'] != 'optimal'
|
139
173
|
@@logger.info("Warning cannot Feature Report #{feature_report.name} #{feature_report.id} - REopt optimization was non-optimal")
|
140
174
|
return feature_report
|
141
175
|
end
|
142
|
-
|
143
|
-
$ts_per_hour = feature_report.timesteps_per_hour
|
144
|
-
def scale_timeseries(input, ts_per_hr=$ts_per_hour)
|
145
|
-
if input.nil?
|
146
|
-
return nil
|
147
|
-
end
|
148
|
-
if input.length ==0
|
149
|
-
return nil
|
150
|
-
end
|
151
|
-
if input.length == (8760 * ts_per_hr)
|
152
|
-
return input
|
153
|
-
end
|
154
|
-
result = []
|
155
|
-
input.each do |val|
|
156
|
-
(1..ts_per_hr).each do |x|
|
157
|
-
result.push(val/ts_per_hr.to_f)
|
158
|
-
end
|
159
|
-
end
|
160
|
-
return result
|
161
|
-
end
|
162
|
-
|
176
|
+
|
163
177
|
# Update location
|
164
|
-
feature_report.location.
|
165
|
-
feature_report.location.
|
166
|
-
|
167
|
-
# Update timeseries csv from \REopt Lite dispatch data
|
168
|
-
feature_report.timesteps_per_hour = reopt_output['inputs']['Scenario']['time_steps_per_hour']
|
178
|
+
feature_report.location.latitude_deg = reopt_output['inputs']['Scenario']['Site']['latitude']
|
179
|
+
feature_report.location.longitude_deg = reopt_output['inputs']['Scenario']['Site']['longitude']
|
169
180
|
|
170
181
|
# Update distributed generation sizing and financials
|
171
182
|
feature_report.distributed_generation.lcc_us_dollars = reopt_output['outputs']['Scenario']['Site']['Financial']['lcc_us_dollars'] || 0
|
@@ -174,56 +185,63 @@ module URBANopt # :nodoc:
|
|
174
185
|
feature_report.distributed_generation.year_one_demand_cost_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_demand_cost_us_dollars'] || 0
|
175
186
|
feature_report.distributed_generation.year_one_bill_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_bill_us_dollars'] || 0
|
176
187
|
feature_report.distributed_generation.total_energy_cost_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['total_energy_cost_us_dollars'] || 0
|
188
|
+
feature_report.distributed_generation.total_demand_cost_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['total_demand_cost_us_dollars'] || 0
|
189
|
+
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
|
190
|
+
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
|
191
|
+
feature_report.distributed_generation.year_one_bill_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_bill_bau_us_dollars'] || 0
|
192
|
+
feature_report.distributed_generation.total_demand_cost_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['total_demand_cost_bau_us_dollars'] || 0
|
193
|
+
feature_report.distributed_generation.total_energy_cost_bau_us_dollars = reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['total_energy_cost_bau_us_dollars'] || 0
|
194
|
+
if !resilience_stats.nil?
|
195
|
+
feature_report.distributed_generation.resilience_hours_min = resilience_stats['resilience_hours_min']
|
196
|
+
feature_report.distributed_generation.resilience_hours_max = resilience_stats['resilience_hours_max']
|
197
|
+
feature_report.distributed_generation.resilience_hours_avg = resilience_stats['resilience_hours_avg']
|
198
|
+
feature_report.distributed_generation.probs_of_surviving = resilience_stats['probs_of_surviving']
|
199
|
+
feature_report.distributed_generation.probs_of_surviving_by_month = resilience_stats['probs_of_surviving_by_month']
|
200
|
+
feature_report.distributed_generation.probs_of_surviving_by_hour_of_the_day = resilience_stats['probs_of_surviving_by_hour_of_the_day']
|
201
|
+
end
|
177
202
|
|
178
203
|
if reopt_output['outputs']['Scenario']['Site']['PV'].class == Hash
|
179
204
|
reopt_output['outputs']['Scenario']['Site']['PV'] = [reopt_output['outputs']['Scenario']['Site']['PV']]
|
180
205
|
elsif reopt_output['outputs']['Scenario']['Site']['PV'].nil?
|
181
206
|
reopt_output['outputs']['Scenario']['Site']['PV'] = []
|
182
207
|
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::
|
208
|
+
|
209
|
+
reopt_output['outputs']['Scenario']['Site']['PV'].each_with_index do |pv, i|
|
210
|
+
feature_report.distributed_generation.add_tech 'solar_pv', URBANopt::Reporting::DefaultReports::SolarPV.new( {size_kw: (pv['size_kw'] || 0), id: i })
|
186
211
|
end
|
187
212
|
|
188
213
|
wind = reopt_output['outputs']['Scenario']['Site']['Wind']
|
189
214
|
if !wind['size_kw'].nil? and wind['size_kw'] != 0
|
190
|
-
feature_report.distributed_generation.add_tech 'wind', URBANopt::
|
215
|
+
feature_report.distributed_generation.add_tech 'wind', URBANopt::Reporting::DefaultReports::Wind.new( {size_kw: (wind['size_kw'] || 0) })
|
191
216
|
end
|
192
217
|
|
193
218
|
generator = reopt_output['outputs']['Scenario']['Site']['Generator']
|
194
219
|
if !generator['size_kw'].nil? and generator['size_kw'] != 0
|
195
|
-
feature_report.distributed_generation.add_tech 'generator', URBANopt::
|
220
|
+
feature_report.distributed_generation.add_tech 'generator', URBANopt::Reporting::DefaultReports::Generator.new( {size_kw: (generator['size_kw'] || 0) })
|
196
221
|
end
|
197
222
|
|
198
223
|
storage = reopt_output['outputs']['Scenario']['Site']['Storage']
|
199
224
|
if !storage['size_kw'].nil? and storage['size_kw'] != 0
|
200
|
-
feature_report.distributed_generation.add_tech 'storage', URBANopt::
|
225
|
+
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
226
|
end
|
202
|
-
|
227
|
+
|
203
228
|
generation_timeseries_kwh = Matrix[[0] * (8760 * feature_report.timesteps_per_hour)]
|
229
|
+
reopt_resolution = reopt_output['inputs']['Scenario']['time_steps_per_hour']
|
204
230
|
|
205
231
|
unless reopt_output['outputs']['Scenario']['Site']['PV'].nil?
|
206
|
-
reopt_output['outputs']['Scenario']['Site']['PV'].each do |pv|
|
232
|
+
reopt_output['outputs']['Scenario']['Site']['PV'].each do |pv|
|
207
233
|
if (pv['size_kw'] || 0) > 0
|
208
234
|
if !pv['year_one_power_production_series_kw'].nil?
|
209
|
-
generation_timeseries_kwh += Matrix[pv['year_one_power_production_series_kw']]
|
235
|
+
generation_timeseries_kwh += Matrix[convert_powerflow_resolution(pv['year_one_power_production_series_kw'], reopt_resolution, feature_report.timesteps_per_hour)]
|
210
236
|
end
|
211
237
|
end
|
212
238
|
end
|
213
239
|
end
|
214
240
|
|
215
|
-
unless reopt_output['outputs']['Scenario']['Site']['Storage'].nil?
|
216
|
-
if (reopt_output['outputs']['Scenario']['Site']['Storage']['size_kw'] or 0) > 0
|
217
|
-
if !reopt_output['outputs']['Scenario']['Site']['Storage']['year_one_to_grid_series_kw'].nil?
|
218
|
-
generation_timeseries_kwh = generation_timeseries_kwh + Matrix[reopt_output['outputs']['Scenario']['Site']['Storage']['year_one_to_grid_series_kw']]
|
219
|
-
end
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
241
|
unless reopt_output['outputs']['Scenario']['Site']['Wind'].nil?
|
224
242
|
if (reopt_output['outputs']['Scenario']['Site']['Wind']['size_kw'] || 0) > 0
|
225
243
|
if !reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_power_production_series_kw'].nil?
|
226
|
-
generation_timeseries_kwh += Matrix[reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_power_production_series_kw']]
|
244
|
+
generation_timeseries_kwh += Matrix[convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_power_production_series_kw'], reopt_resolution, feature_report.timesteps_per_hour)]
|
227
245
|
end
|
228
246
|
end
|
229
247
|
end
|
@@ -231,7 +249,7 @@ module URBANopt # :nodoc:
|
|
231
249
|
unless reopt_output['outputs']['Scenario']['Site']['Generator'].nil?
|
232
250
|
if (reopt_output['outputs']['Scenario']['Site']['Generator']['size_kw'] || 0) > 0
|
233
251
|
if !reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_power_production_series_kw'].nil?
|
234
|
-
generation_timeseries_kwh += Matrix[reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_power_production_series_kw']]
|
252
|
+
generation_timeseries_kwh += Matrix[convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_power_production_series_kw'], reopt_resolution, feature_report.timesteps_per_hour)]
|
235
253
|
end
|
236
254
|
end
|
237
255
|
end
|
@@ -243,70 +261,70 @@ module URBANopt # :nodoc:
|
|
243
261
|
feature_report.timeseries_csv.column_names.push('REopt:ElectricityProduced:Total(kw)')
|
244
262
|
end
|
245
263
|
|
246
|
-
$load =
|
264
|
+
$load = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['LoadProfile']['year_one_electric_load_series_kw'], reopt_resolution, feature_report.timesteps_per_hour) || [0] * (8760 * feature_report.timesteps_per_hour)
|
247
265
|
$load_col = feature_report.timeseries_csv.column_names.index('REopt:Electricity:Load:Total(kw)')
|
248
266
|
if $load_col.nil?
|
249
267
|
$load_col = feature_report.timeseries_csv.column_names.length
|
250
268
|
feature_report.timeseries_csv.column_names.push('REopt:Electricity:Load:Total(kw)')
|
251
269
|
end
|
252
270
|
|
253
|
-
$utility_to_load =
|
271
|
+
$utility_to_load = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_to_load_series_kw'], reopt_resolution, feature_report.timesteps_per_hour) || [0] * (8760 * feature_report.timesteps_per_hour)
|
254
272
|
$utility_to_load_col = feature_report.timeseries_csv.column_names.index('REopt:Electricity:Grid:ToLoad(kw)')
|
255
273
|
if $utility_to_load_col.nil?
|
256
274
|
$utility_to_load_col = feature_report.timeseries_csv.column_names.length
|
257
275
|
feature_report.timeseries_csv.column_names.push('REopt:Electricity:Grid:ToLoad(kw)')
|
258
276
|
end
|
259
277
|
|
260
|
-
$utility_to_battery =
|
278
|
+
$utility_to_battery = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['ElectricTariff']['year_one_to_battery_series_kw'], reopt_resolution, feature_report.timesteps_per_hour) || [0] * (8760 * feature_report.timesteps_per_hour)
|
261
279
|
$utility_to_battery_col = feature_report.timeseries_csv.column_names.index('REopt:Electricity:Grid:ToBattery(kw)')
|
262
280
|
if $utility_to_battery_col.nil?
|
263
281
|
$utility_to_battery_col = feature_report.timeseries_csv.column_names.length
|
264
282
|
feature_report.timeseries_csv.column_names.push('REopt:Electricity:Grid:ToBattery(kw)')
|
265
283
|
end
|
266
284
|
|
267
|
-
$storage_to_load =
|
285
|
+
$storage_to_load = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Storage']['year_one_to_load_series_kw'], reopt_resolution, feature_report.timesteps_per_hour) || [0] * (8760 * feature_report.timesteps_per_hour)
|
268
286
|
$storage_to_load_col = feature_report.timeseries_csv.column_names.index('REopt:Electricity:Storage:ToLoad(kw)')
|
269
287
|
if $storage_to_load_col.nil?
|
270
288
|
$storage_to_load_col = feature_report.timeseries_csv.column_names.length
|
271
289
|
feature_report.timeseries_csv.column_names.push('REopt:Electricity:Storage:ToLoad(kw)')
|
272
290
|
end
|
273
291
|
|
274
|
-
$storage_to_grid =
|
292
|
+
$storage_to_grid = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Storage']['year_one_to_grid_series_kw'], reopt_resolution, feature_report.timesteps_per_hour) || [0] * (8760 * feature_report.timesteps_per_hour)
|
275
293
|
$storage_to_grid_col = feature_report.timeseries_csv.column_names.index('REopt:Electricity:Storage:ToGrid(kw)')
|
276
294
|
if $storage_to_grid_col.nil?
|
277
295
|
$storage_to_grid_col = feature_report.timeseries_csv.column_names.length
|
278
296
|
feature_report.timeseries_csv.column_names.push('REopt:Electricity:Storage:ToGrid(kw)')
|
279
297
|
end
|
280
298
|
|
281
|
-
$storage_soc =
|
299
|
+
$storage_soc = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Storage']['year_one_soc_series_pct'], reopt_resolution, feature_report.timesteps_per_hour) || [0] * (8760 * feature_report.timesteps_per_hour)
|
282
300
|
$storage_soc_col = feature_report.timeseries_csv.column_names.index('REopt:Electricity:Storage:StateOfCharge(pct)')
|
283
301
|
if $storage_soc_col.nil?
|
284
302
|
$storage_soc_col = feature_report.timeseries_csv.column_names.length
|
285
303
|
feature_report.timeseries_csv.column_names.push('REopt:Electricity:Storage:StateOfCharge(pct)')
|
286
304
|
end
|
287
305
|
|
288
|
-
$generator_total =
|
306
|
+
$generator_total = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_power_production_series_kw'], reopt_resolution, feature_report.timesteps_per_hour) || [0] * (8760 * feature_report.timesteps_per_hour)
|
289
307
|
$generator_total_col = feature_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Generator:Total(kw)')
|
290
308
|
if $generator_total_col.nil?
|
291
309
|
$generator_total_col = feature_report.timeseries_csv.column_names.length
|
292
310
|
feature_report.timeseries_csv.column_names.push('REopt:ElectricityProduced:Generator:Total(kw)')
|
293
311
|
end
|
294
312
|
|
295
|
-
$generator_to_battery =
|
313
|
+
$generator_to_battery = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_to_battery_series_kw'], reopt_resolution, feature_report.timesteps_per_hour) || [0] * (8760 * feature_report.timesteps_per_hour)
|
296
314
|
$generator_to_battery_col = feature_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Generator:ToBattery(kw)')
|
297
315
|
if $generator_to_battery_col.nil?
|
298
316
|
$generator_to_battery_col = feature_report.timeseries_csv.column_names.length
|
299
317
|
feature_report.timeseries_csv.column_names.push('REopt:ElectricityProduced:Generator:ToBattery(kw)')
|
300
318
|
end
|
301
319
|
|
302
|
-
$generator_to_load =
|
320
|
+
$generator_to_load = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_to_load_series_kw'], reopt_resolution, feature_report.timesteps_per_hour) || [0] * (8760 * feature_report.timesteps_per_hour)
|
303
321
|
$generator_to_load_col = feature_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Generator:ToLoad(kw)')
|
304
322
|
if $generator_to_load_col.nil?
|
305
323
|
$generator_to_load_col = feature_report.timeseries_csv.column_names.length
|
306
324
|
feature_report.timeseries_csv.column_names.push('REopt:ElectricityProduced:Generator:ToLoad(kw)')
|
307
325
|
end
|
308
326
|
|
309
|
-
$generator_to_grid =
|
327
|
+
$generator_to_grid = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Generator']['year_one_to_grid_series_kw'], reopt_resolution, feature_report.timesteps_per_hour) || [0] * (8760 * feature_report.timesteps_per_hour)
|
310
328
|
$generator_to_grid_col = feature_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Generator:ToGrid(kw)')
|
311
329
|
if $generator_to_grid_col.nil?
|
312
330
|
$generator_to_grid_col = feature_report.timeseries_csv.column_names.length
|
@@ -345,10 +363,10 @@ module URBANopt # :nodoc:
|
|
345
363
|
|
346
364
|
reopt_output['outputs']['Scenario']['Site']['PV'].each_with_index do |pv, i|
|
347
365
|
if (pv['size_kw'] || 0) > 0
|
348
|
-
$pv_total += Matrix[
|
349
|
-
$pv_to_battery += Matrix[
|
350
|
-
$pv_to_load += Matrix[
|
351
|
-
$pv_to_grid += Matrix[
|
366
|
+
$pv_total += Matrix[convert_powerflow_resolution(pv['year_one_power_production_series_kw'], reopt_resolution, feature_report.timesteps_per_hour) || [0] * (8760 * feature_report.timesteps_per_hour)]
|
367
|
+
$pv_to_battery += Matrix[convert_powerflow_resolution(pv['year_one_to_battery_series_kw'], reopt_resolution, feature_report.timesteps_per_hour) || [0] * (8760 * feature_report.timesteps_per_hour)]
|
368
|
+
$pv_to_load += Matrix[convert_powerflow_resolution(pv['year_one_to_load_series_kw'], reopt_resolution, feature_report.timesteps_per_hour) || [0] * (8760 * feature_report.timesteps_per_hour)]
|
369
|
+
$pv_to_grid += Matrix[convert_powerflow_resolution(pv['year_one_to_grid_series_kw'], reopt_resolution, feature_report.timesteps_per_hour) || [0] * (8760 * feature_report.timesteps_per_hour)]
|
352
370
|
end
|
353
371
|
end
|
354
372
|
|
@@ -357,28 +375,28 @@ module URBANopt # :nodoc:
|
|
357
375
|
$pv_to_load = $pv_to_load.to_a[0]
|
358
376
|
$pv_to_grid = $pv_to_grid.to_a[0]
|
359
377
|
|
360
|
-
$wind_total =
|
378
|
+
$wind_total = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_power_production_series_kw'], reopt_resolution, feature_report.timesteps_per_hour) || [0] * (8760 * feature_report.timesteps_per_hour)
|
361
379
|
$wind_total_col = feature_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Wind:Total(kw)')
|
362
380
|
if $wind_total_col.nil?
|
363
381
|
$wind_total_col = feature_report.timeseries_csv.column_names.length
|
364
382
|
feature_report.timeseries_csv.column_names.push('REopt:ElectricityProduced:Wind:Total(kw)')
|
365
383
|
end
|
366
384
|
|
367
|
-
$wind_to_battery =
|
385
|
+
$wind_to_battery = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_to_battery_series_kw'], reopt_resolution, feature_report.timesteps_per_hour) || [0] * (8760 * feature_report.timesteps_per_hour)
|
368
386
|
$wind_to_battery_col = feature_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Wind:ToBattery(kw)')
|
369
387
|
if $wind_to_battery_col.nil?
|
370
388
|
$wind_to_battery_col = feature_report.timeseries_csv.column_names.length
|
371
389
|
feature_report.timeseries_csv.column_names.push('REopt:ElectricityProduced:Wind:ToBattery(kw)')
|
372
390
|
end
|
373
391
|
|
374
|
-
$wind_to_load =
|
392
|
+
$wind_to_load = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_to_load_series_kw'], reopt_resolution, feature_report.timesteps_per_hour) || [0] * (8760 * feature_report.timesteps_per_hour)
|
375
393
|
$wind_to_load_col = feature_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Wind:ToLoad(kw)')
|
376
394
|
if $wind_to_load_col.nil?
|
377
395
|
$wind_to_load_col = feature_report.timeseries_csv.column_names.length
|
378
396
|
feature_report.timeseries_csv.column_names.push('REopt:ElectricityProduced:Wind:ToLoad(kw)')
|
379
397
|
end
|
380
398
|
|
381
|
-
$wind_to_grid =
|
399
|
+
$wind_to_grid = convert_powerflow_resolution(reopt_output['outputs']['Scenario']['Site']['Wind']['year_one_to_grid_series_kw'], reopt_resolution, feature_report.timesteps_per_hour) || [0] * (8760 * feature_report.timesteps_per_hour)
|
382
400
|
$wind_to_grid_col = feature_report.timeseries_csv.column_names.index('REopt:ElectricityProduced:Wind:ToGrid(kw)')
|
383
401
|
if $wind_to_grid_col.nil?
|
384
402
|
$wind_to_grid_col = feature_report.timeseries_csv.column_names.length
|
@@ -410,8 +428,14 @@ module URBANopt # :nodoc:
|
|
410
428
|
|
411
429
|
old_data = CSV.open(feature_report.timeseries_csv.path).read
|
412
430
|
start_date = Time.parse(old_data[1][0])
|
413
|
-
start_ts = (
|
414
|
-
|
431
|
+
start_ts = (
|
432
|
+
(
|
433
|
+
((start_date.yday - 1) * 60.0 * 60.0 * 24) +
|
434
|
+
(((start_date.hour) - 1) * 60.0 * 60.0) +
|
435
|
+
(start_date.min * 60.0) + start_date.sec ) /
|
436
|
+
(( 60 / feature_report.timesteps_per_hour ) * 60)
|
437
|
+
).to_int
|
438
|
+
|
415
439
|
mod_data = old_data.map.with_index do |x, i|
|
416
440
|
if i > 0
|
417
441
|
modrow(x, start_ts + i -2)
|