Class: URBANopt::REopt::REoptLiteAPI

Inherits:
Object
  • Object
show all
Defined in:
lib/urbanopt/reopt/reopt_lite_api.rb

Instance Method Summary collapse

Constructor Details

#initialize(nrel_developer_key = nil, use_localhost = false) ⇒ REoptLiteAPI

REoptLiteAPI manages submitting optimization tasks to the REopt Lite API and recieving results. Results can either be sourced from the production REopt Lite API with an API key from developer.nrel.gov, or from a version running at localhost.

parameters:
  • use_localhost - Bool - If this is true, requests will be sent to a version of the REopt Lite API running on localhost. Default is false, such that the production version of REopt Lite is accessed.

  • nrel_developer_key - String - API key used to access the REopt Lite APi. Required only if localhost is false. Obtain from developer.nrel.gov/signup/



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/urbanopt/reopt/reopt_lite_api.rb', line 55

def initialize(nrel_developer_key = nil, use_localhost = false)
  @use_localhost = use_localhost
  if @use_localhost
    @uri_submit = URI.parse('http//:127.0.0.1:8000/v1/job/')
    @uri_submit_outagesimjob = URI.parse('http//:127.0.0.1:8000/v1/outagesimjob/')
  else
    if [nil, '', '<insert your key here>'].include? nrel_developer_key
      if [nil, '', '<insert your key here>'].include? DEVELOPER_NREL_KEY
        raise 'A developer.nrel.gov API key is required. Please see https://developer.nrel.gov/signup/ then update the file urbanopt-reopt-gem/developer_nrel_key.rb'
      else
        nrel_developer_key = DEVELOPER_NREL_KEY
      end
    end
    @nrel_developer_key = nrel_developer_key
    @uri_submit = URI.parse("https://developer.nrel.gov/api/reopt/v1/job/?api_key=#{@nrel_developer_key}")
    @uri_submit_outagesimjob = URI.parse("https://developer.nrel.gov/api/reopt/v1/outagesimjob/?api_key=#{@nrel_developer_key}")
    # initialize @@logger
    @@logger ||= URBANopt::REopt.reopt_logger
  end
end

Instance Method Details

#check_connection(data) ⇒ Object

Checks if a optimization task can be submitted to the REopt Lite API

parameters:
  • data - Hash - Default REopt Lite formatted post containing at least all the required parameters.

return:

Bool - Returns true if the post succeeeds. Otherwise returns false.



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/urbanopt/reopt/reopt_lite_api.rb', line 134

def check_connection(data)
  header = { 'Content-Type' => 'application/json' }
  http = Net::HTTP.new(@uri_submit.host, @uri_submit.port)
  if !@use_localhost
    http.use_ssl = true
  end

  request = Net::HTTP::Post.new(@uri_submit, header)
  request.body = ::JSON.generate(data, allow_nan: true)

  # Send the request
  response = make_request(http, request)

  if !response.is_a?(Net::HTTPSuccess)
    @@logger.error('Check_connection Failed')
    raise 'Check_connection Failed'
  end
  return true
end

#make_request(http, r, max_tries = 3) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/urbanopt/reopt/reopt_lite_api.rb', line 110

def make_request(http, r, max_tries = 3)
  result = nil
  tries = 0
  while tries < max_tries
    begin
         result = http.request(r)
         tries = 4
       rescue StandardError
         tries += 1
       end
  end
  return result
end

#reopt_request(reopt_input, filename) ⇒ Object

Completes a REopt Lite optimization. From a formatted hash, an optimization task is submitted to the API. Results are polled at 5 second interval until they are ready or an error is returned from the API. Results are written to disk.

parameters:
  • reopt_input - Hash - REopt Lite formatted post containing at least required parameters.

  • filename - String - Path to file that will be created containing the full REopt Lite response.

return:

Bool - Returns true if the post succeeeds. Otherwise returns false.



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/urbanopt/reopt/reopt_lite_api.rb', line 241

def reopt_request(reopt_input, filename)
  description = reopt_input[:Scenario][:description]

  @@logger.info("Submitting #{description} to REopt Lite API")

  # Format the request
  header = { 'Content-Type' => 'application/json' }
  http = Net::HTTP.new(@uri_submit.host, @uri_submit.port)
  if !@use_localhost
    http.use_ssl = true
  end
  request = Net::HTTP::Post.new(@uri_submit, header)
  request.body = ::JSON.generate(reopt_input, allow_nan: true)

  # Send the request
  response = make_request(http, request)

  # Get UUID
  run_uuid = JSON.parse(response.body, allow_nan:true)['run_uuid']

  if File.directory? filename
    if run_uuid.nil?
      run_uuid = 'error'
    end
    if run_uuid.downcase.include? 'error'
      run_uuid = "error#{SecureRandom.uuid}"
    end
    filename = File.join(filename, "#{description}_#{run_uuid}.json")
    @@logger.info("REopt results saved to #{filename}")
  end

  text = ::JSON.generate(response.body, allow_nan: true)
  if response.code != '201'
    File.open(filename, 'w+') do |f|
      f.puts(text)
    end
    @@logger.info("Cannot write - #{filename}")
    raise "Error in REopt optimization post - see #{filename}"
  end

  # Poll results until ready or error occurs
  status = 'Optimizing...'
  uri = uri_results(run_uuid)
  http = Net::HTTP.new(uri.host, uri.port)
  if !@use_localhost
    http.use_ssl = true
  end

  request = Net::HTTP::Get.new(uri.request_uri)

  while status == 'Optimizing...'
    response = make_request(http, request)
    
    data = JSON.parse(response.body, allow_nan:true)

    if data['outputs']['Scenario']['Site']['PV'].kind_of?(Array)
      pv_sizes = 0
      data['outputs']['Scenario']['Site']['PV'].each do |x|
        pv_sizes = pv_sizes + x['size_kw'].to_f
      end 
    else
      pv_sizes = data['outputs']['Scenario']['Site']['PV']['size_kw'] || 0
    end
    sizes = pv_sizes + (data['outputs']['Scenario']['Site']['Storage']['size_kw'] || 0) + (data['outputs']['Scenario']['Site']['Wind']['size_kw'] || 0) + (data['outputs']['Scenario']['Site']['Generator']['size_kw'] || 0)
    status = data['outputs']['Scenario']['status']

    sleep 5
  end

  _max_retry = 5
  _tries = 0
  (check_complete = sizes == 0) && ((data['outputs']['Scenario']['Site']['Financial']['npv_us_dollars'] || 0) > 0)
  while (_tries < _max_retry) && check_complete
    sleep 1
    response = make_request(http, request)
    data = JSON.parse(response.body, allow_nan:true)
    if data['outputs']['Scenario']['Site']['PV'].kind_of?(Array)
      pv_sizes = 0
      data['outputs']['Scenario']['Site']['PV'].each do |x|
        pv_sizes = pv_sizes + x['size_kw'].to_f
      end 
    else
      pv_sizes = data['outputs']['Scenario']['Site']['PV']['size_kw'] || 0
    end
    sizes = pv_sizes + (data['outputs']['Scenario']['Site']['Storage']['size_kw'] || 0) + (data['outputs']['Scenario']['Site']['Wind']['size_kw'] || 0) + (data['outputs']['Scenario']['Site']['Generator']['size_kw'] || 0)
    (check_complete = sizes == 0) && ((data['outputs']['Scenario']['Site']['Financial']['npv_us_dollars'] || 0) > 0)
    _tries += 1
  end

  data = JSON.parse(response.body)
  text = ::JSON.generate(data, allow_nan: true)
  begin
    File.open(filename, 'w+') do |f|
      f.puts(text)
    end
  rescue
    @@logger.info("Cannot write - #{filename}")
  end

  if status == 'optimal'
    return data
  end

  error_message = data['messages']['error']
  raise "Error from REopt API - #{error_message}"
end

#resilience_request(run_uuid, filename) ⇒ Object

Completes a REopt Lite optimization. From a formatted hash, an optimization task is submitted to the API. Results are polled at 5 second interval until they are ready or an error is returned from the API. Results are written to disk.

parameters:
  • reopt_input - Hash - REopt Lite formatted post containing at least required parameters.

  • filename - String - Path to file that will be created containing the full REopt Lite response.

return:

Bool - Returns true if the post succeeeds. Otherwise returns false.



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/urbanopt/reopt/reopt_lite_api.rb', line 167

def resilience_request(run_uuid, filename)
  
  if File.directory? filename
    if run_uuid.nil?
      run_uuid = 'error'
    end
    if run_uuid.downcase.include? 'error'
      run_uuid = "error#{SecureRandom.uuid}"
    end
    filename = File.join(filename, "#{run_uuid}_resilience.json")
    @@logger.info("REopt results saved to #{filename}")
  end
  
  #Submit Job
  @@logger.info("Submitting Resilience Statistics job for #{run_uuid}")
  header = { 'Content-Type' => 'application/json' }
  http = Net::HTTP.new(@uri_submit_outagesimjob.host, @uri_submit_outagesimjob.port)
  if !@use_localhost
    http.use_ssl = true
  end
  request = Net::HTTP::Post.new(@uri_submit_outagesimjob, header)
  request.body = ::JSON.generate({"run_uuid" => run_uuid, "bau" => false }, allow_nan: true)
  submit_response = make_request(http, request)
  @@logger.info(submit_response.body)

  #Fetch Results
  uri = uri_resilience(run_uuid)
  http = Net::HTTP.new(uri.host, uri.port)
  if !@use_localhost
    http.use_ssl = true
  end

  elapsed_time = 0
  max_elapsed_time = 60
  
  request = Net::HTTP::Get.new(uri.request_uri)
  response = make_request(http, request)
  
  while (elapsed_time < max_elapsed_time) & (response.code == "404")
    response = make_request(http, request)
    elapsed_time += 5 
    sleep 5
  end
  
  data = JSON.parse(response.body)
  text = ::JSON.generate(data, allow_nan: true)
  begin
    File.open(filename, 'w+') do |f|
      f.puts(text)
    end
  rescue
    @@logger.info("Cannot write - #{filename}")
  end

  if response.code == "200"
    return data
  end

  raise "Error from REopt API - #{data['Error']}"
end

#uri_resilience(run_uuid) ⇒ Object

URL of the resilience statistics end point for a specific optimization task

parameters:
  • run_uuid - String - Resilience statistics for a unique run_uuid obtained from the REopt Lite job submittal URL for a specific optimization task.

return:

URI - Returns URI object for use in calling the REopt Lite resilience statistics endpoint for a specifc optimization task.



103
104
105
106
107
108
# File 'lib/urbanopt/reopt/reopt_lite_api.rb', line 103

def uri_resilience(run_uuid) # :nodoc:
  if @use_localhost
    return URI.parse("http://127.0.0.1:8000/v1/job/#{run_uuid}/resilience_stats")
  end
  return URI.parse("https://developer.nrel.gov/api/reopt/v1/job/#{run_uuid}/resilience_stats?api_key=#{@nrel_developer_key}")
end

#uri_results(run_uuid) ⇒ Object

URL of the results end point for a specific optimization task

parameters:
  • run_uuid - String - Unique run_uuid obtained from the REopt Lite job submittal URL for a specific optimization task.

return:

URI - Returns URI object for use in calling the REopt Lite results endpoint for a specifc optimization task.



86
87
88
89
90
91
# File 'lib/urbanopt/reopt/reopt_lite_api.rb', line 86

def uri_results(run_uuid) # :nodoc:
  if @use_localhost
    return URI.parse("http://127.0.0.1:8000/v1/job/#{run_uuid}/results")
  end
  return URI.parse("https://developer.nrel.gov/api/reopt/v1/job/#{run_uuid}/results?api_key=#{@nrel_developer_key}")
end