class URBANopt::REopt::REoptLiteAPI
Public Class Methods
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/
# File lib/urbanopt/reopt/reopt_lite_api.rb, line 29 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/v2/job/') @uri_submit_outagesimjob = URI.parse('http//:127.0.0.1:8000/v2/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/v2/job?api_key=#{@nrel_developer_key}") @uri_submit_outagesimjob = URI.parse("https://developer.nrel.gov/api/reopt/v2/outagesimjob?api_key=#{@nrel_developer_key}") # initialize @@logger @@logger ||= URBANopt::REopt.reopt_logger end end
Public Instance Methods
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.
# File lib/urbanopt/reopt/reopt_lite_api.rb, line 138 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 post_request = Net::HTTP::Post.new(@uri_submit, header) post_request.body = ::JSON.generate(data, allow_nan: true) # Send the request response = make_request(http, post_request) if !response.is_a?(Net::HTTPSuccess) @@logger.error('Check_connection Failed') raise 'Check_connection Failed' end return true end
# File lib/urbanopt/reopt/reopt_lite_api.rb, line 86 def make_request(http, req, max_tries = 3) result = nil tries = 0 while tries < max_tries begin result = http.request(req) # Result codes sourced from https://developer.nrel.gov/docs/errors/ if result.code == '429' @@logger.fatal('Exceeded the REopt-Lite API limit of 300 requests per hour') puts 'Using the URBANopt CLI to submit a Scenario optimization counts as one request per scenario' puts 'Using the URBANopt CLI to submit a Feature optimization counts as one request per feature' abort('Please wait and try again once the time period has elapsed. The URBANopt CLI flag --reopt-keep-existing can be used to resume the optimization') elsif result.code == '404' @@logger.info("REOpt is still calculating. We'll give it a moment and check again") sleep 15 tries += 1 next elsif (result.code != '201') && (result.code != '200') # Anything in the 200s is success @@logger.warn("REopt-Lite has returned a '#{result.code}' status code. Visit https://developer.nrel.gov/docs/errors/ for more status code information") # display error messages json_res = JSON.parse(result.body, allow_nan: true) json_res['messages'].delete('warnings') if json_res['messages']['warnings'] json_res['messages'].delete('Deprecations') if json_res['messages']['Deprecations'] if json_res['messages'] @@logger.error("MESSAGES: #{json_res['messages']}") end end tries = max_tries rescue StandardError => e @@logger.debug("error from REopt lite API: #{e}") if tries + 1 < max_tries @@logger.debug('trying again...') else @@logger.debug('max tries reached!') return result end tries += 1 end end return result end
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.
# File lib/urbanopt/reopt/reopt_lite_api.rb, line 251 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 post_request = Net::HTTP::Post.new(@uri_submit, header) post_request.body = ::JSON.generate(reopt_input, allow_nan: true) # Send the request response = make_request(http, post_request) if !response.is_a?(Net::HTTPSuccess) @@logger.error('make_request Failed') raise 'Check_connection Failed' end # 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.parse(response.body, allow_nan: true) if response.code != '201' File.open(filename, 'w+') do |f| f.puts(JSON.pretty_generate(text)) end 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 get_request = Net::HTTP::Get.new(uri.request_uri) while status == 'Optimizing...' response = make_request(http, get_request) data = JSON.parse(response.body, allow_nan: true) if data['outputs']['Scenario']['Site']['PV'].is_a?(Array) pv_sizes = 0 data['outputs']['Scenario']['Site']['PV'].each do |x| 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 3 response = make_request(http, get_request) data = JSON.parse(response.body, allow_nan: true) if data['outputs']['Scenario']['Site']['PV'].is_a?(Array) pv_sizes = 0 data['outputs']['Scenario']['Site']['PV'].each do |x| 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, allow_nan: true) text = JSON.pretty_generate(data) begin File.open(filename, 'w+') do |f| f.puts(text) end rescue StandardError @@logger.error("Cannot write - #{filename}") end if status == 'optimal' return data end error_message = data['messages']['error'] raise "Error from REopt API - #{error_message}" end
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.
# File lib/urbanopt/reopt/reopt_lite_api.rb, line 171 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 post_request = Net::HTTP::Post.new(@uri_submit_outagesimjob, header) post_request.body = ::JSON.generate({ 'run_uuid' => run_uuid, 'bau' => false }, allow_nan: true) submit_response = make_request(http, post_request) @@logger.debug(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 # Wait a few seconds for the REopt database to update before GETing results sleep 5 get_request = Net::HTTP::Get.new(uri.request_uri) response = make_request(http, get_request, 8) # Set a limit on retries when 404s are returned from REopt API elapsed_time = 0 max_elapsed_time = 60 * 5 # If database still hasn't updated, wait a little longer and try again while (elapsed_time < max_elapsed_time) && (response && response.code == '404') response = make_request(http, get_request) @@logger.warn('GET request was too fast for REOpt-Lite API. Retrying...') elapsed_time += 5 sleep 5 end data = JSON.parse(response.body, allow_nan: true) text = JSON.pretty_generate(data) begin File.open(filename, 'w+') do |f| f.puts(text) end rescue StandardError => e @@logger.error("Cannot write - #{filename}") @@logger.error("ERROR: #{e}") end if response.code == '200' return data end @@logger.error("Error from REopt API - #{data['Error']}") return {} end