urbanopt-reopt 0.3.0 → 0.4.0

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.
data/index.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # **URBANopt REopt Gem**
2
2
 
3
- The **URBANopt REopt Gem** extends **URBANopt::Scenario::DefaultReports::ScenarioReport** and **URBANopt::Scenario::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.
3
+ The **URBANopt<sup>&trade;</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::Scenario::DefaultReports::FeatureReport.new(feature_reports_hash)
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::Scenario::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') }})
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::Scenario::DefaultReports::FeatureReport.new(feature_reports_hash)
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
 
@@ -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,
@@ -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/scenario/default_reports'
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::Scenario::DefaultReports::FeatureReport into a \REopt Lite posts or update a URBANopt::Scenario::DefaultReports::FeatureReport from a \REopt Lite response.
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::Scenario::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.
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
@@ -127,19 +127,19 @@ module URBANopt # :nodoc:
127
127
  #
128
128
  # [*parameters:*]
129
129
  #
130
- # * +feature_report+ - _URBANopt::Scenario::DefaultReports::FeatureReport_ - FeatureReport to update from a \REopt Lite reponse hash.
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::Scenario::DefaultReports::FeatureReport_ - Returns an updated FeatureReport.
134
+ # [*return:*] _URBANopt::Reporting::DefaultReports::FeatureReport_ - Returns an updated FeatureReport.
135
135
  ##
136
- def update_feature_report(feature_report, reopt_output, timeseries_csv_path = nil)
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
@@ -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::Scenario::DefaultReports::SolarPV.new( {size_kw: (pv['size_kw'] || 0), id: i })
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::Scenario::DefaultReports::Wind.new( {size_kw: (wind['size_kw'] || 0) })
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::Scenario::DefaultReports::Generator.new( {size_kw: (generator['size_kw'] || 0) })
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::Scenario::DefaultReports::Storage.new( {size_kwh: (storage['size_kwh'] || 0), size_kw: (storage['size_kw'] || 0) })
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
- end
139
+ end
121
140
 
122
141
  request = Net::HTTP::Post.new(@uri_submit, header)
123
- request.body = data.to_json
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.to_json
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.write(response.body)
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
- data = JSON.parse(response.body)
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
- File.open(filename, 'w') do |f|
234
- f.write(data.to_json)
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'
@@ -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,