From c81d59b973690f784526b461adcb2b0494918997 Mon Sep 17 00:00:00 2001 From: Nathan Moore Date: Mon, 27 Jan 2025 11:47:49 -0700 Subject: [PATCH] Updates to handle breaking changes from REopt v3.11 (#157) * use released versions of gems * bump to v1.0.0 * add year to district input for ElectricLoad * add year to ElectricLoad for GHP assumptions * add year to ElectricLoad test assumption files * use `v3` in ghp test api url instead of `stable` * rubocop formatting * remove unused variable * less action & more assertion in 15-minute-interval test --- Gemfile | 24 ++++++++-------- lib/urbanopt/reopt/feature_report_adapter.rb | 9 +++--- lib/urbanopt/reopt/reopt_ghp_adapter.rb | 5 ++-- .../reopt_ghp_files/reopt_ghp_assumption.json | 9 +++--- .../reopt/reopt_ghp_post_processor.rb | 28 ++++++++----------- lib/urbanopt/reopt/reopt_post_processor.rb | 1 - .../reopt/reopt_schema/REopt-GHP-input.json | 5 ++-- lib/urbanopt/reopt/scenario_report_adapter.rb | 16 ++++------- lib/urbanopt/reopt/version.rb | 2 +- .../reopt_assumptions_4tsperhour_v3.json | 3 +- .../files/reopt_assumptions_with_wind_v3.json | 3 +- spec/tests/urbanopt_reopt_ghp_spec.rb | 3 +- spec/tests/urbanopt_reopt_spec.rb | 10 +++++-- urbanopt-reopt.gemspec | 4 +-- 14 files changed, 59 insertions(+), 63 deletions(-) diff --git a/Gemfile b/Gemfile index 691b03a9..f6c925e6 100755 --- a/Gemfile +++ b/Gemfile @@ -17,7 +17,7 @@ allow_local = ENV['FAVOR_LOCAL_GEMS'] # if allow_local && File.exist?('../OpenStudio-extension-gem') # gem 'openstudio-extension', path: '../OpenStudio-extension-gem' # elsif allow_local -# gem 'openstudio-extension', github: 'NREL/OpenStudio-extension-gem', branch: 'bundler-hack' +# gem 'openstudio-extension', github: 'NREL/OpenStudio-extension-gem', branch: 'develop' # else # gem 'openstudio-extension', '~> 0.8.1' # end @@ -43,17 +43,17 @@ allow_local = ENV['FAVOR_LOCAL_GEMS'] # gem 'openstudio-model-articulation', '0.1.0' # end -# if allow_local && File.exist?('../urbanopt-scenario-gem') -# gem 'urbanopt-scenario', path: '../urbanopt-scenario-gem' -# elsif allow_local -gem 'urbanopt-scenario', github: 'URBANopt/urbanopt-scenario-gem', branch: 'os39' -# end +if allow_local && File.exist?('../urbanopt-scenario-gem') + gem 'urbanopt-scenario', path: '../urbanopt-scenario-gem' +elsif allow_local + gem 'urbanopt-scenario', github: 'URBANopt/urbanopt-scenario-gem', branch: 'develop' +end # Temporary! Remove this once reporting-gem is merged/released -gem 'urbanopt-reporting', github: 'URBANopt/urbanopt-reporting-gem', branch: 'os39' +# gem 'urbanopt-reporting', github: 'URBANopt/urbanopt-reporting-gem', branch: 'develop' -# if allow_local && File.exists?('../urbanopt-geojson-gem') -# gem 'urbanopt-geojson', path: '../urbanopt-geojson-gem' -# elsif allow_local -# gem 'urbanopt-geojson', github: 'URBANopt/urbanopt-geojson-gem', branch: 'develop' -# end +if allow_local && File.exist?('../urbanopt-geojson-gem') + gem 'urbanopt-geojson', path: '../urbanopt-geojson-gem' +elsif allow_local + gem 'urbanopt-geojson', github: 'URBANopt/urbanopt-geojson-gem', branch: 'develop' +end diff --git a/lib/urbanopt/reopt/feature_report_adapter.rb b/lib/urbanopt/reopt/feature_report_adapter.rb index babadfbd..b9ec8a21 100755 --- a/lib/urbanopt/reopt/feature_report_adapter.rb +++ b/lib/urbanopt/reopt/feature_report_adapter.rb @@ -41,9 +41,9 @@ def reopt_json_from_feature_report(feature_report, reopt_assumptions_hash = nil, else @@logger.info('Using default REopt assumptions') reopt_inputs = { - Settings:{}, + Settings: {}, Site: {}, - Financial:{}, + Financial: {}, ElectricTariff: { monthly_demand_rates: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], monthly_energy_rates: [0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13] @@ -158,7 +158,6 @@ def reopt_json_from_feature_report(feature_report, reopt_assumptions_hash = nil, # [*return:*] _URBANopt::Reporting::DefaultReports::FeatureReport_ - Returns an updated FeatureReport. ## def update_feature_report(feature_report, reopt_output, timeseries_csv_path = nil, resilience_stats = nil) - # Check if the \REopt response is valid if reopt_output['status'] != 'optimal' @@logger.error("ERROR cannot update Feature Report #{feature_report.name} #{feature_report.id} - REopt optimization was non-optimal") @@ -439,7 +438,7 @@ def modrow(x, i) # :nodoc: x[$storage_to_grid_col] = $storage_to_grid[i] || 0 if defined?(storage) x[$storage_soc_col] = $storage_soc[i] || 0 if defined?(storage) x[$generator_total_col] = $generator_total[i] || 0 if defined?(generator) - x[$generator_to_battery_col] = $generator_to_battery[i] || 0 if (defined?(generator) && defined?(storage)) + x[$generator_to_battery_col] = $generator_to_battery[i] || 0 if defined?(generator) && defined?(storage) x[$generator_to_load_col] = $generator_to_load[i] || 0 if defined?(generator) x[$generator_to_grid_col] = $generator_to_grid[i] || 0 if defined?(generator) x[$pv_total_col] = $pv_total[i] || 0 @@ -447,7 +446,7 @@ def modrow(x, i) # :nodoc: x[$pv_to_load_col] = $pv_to_load[i] || 0 x[$pv_to_grid_col] = $pv_to_grid[i] || 0 x[$wind_total_col] = $wind_total[i] || 0 if defined?(wind) - x[$wind_to_battery_col] = $wind_to_battery[i] || 0 if (defined?(wind) && defined?(storage)) + x[$wind_to_battery_col] = $wind_to_battery[i] || 0 if defined?(wind) && defined?(storage) x[$wind_to_load_col] = $wind_to_load[i] || 0 if defined?(wind) x[$wind_to_grid_col] = $wind_to_grid[i] || 0 if defined?(wind) return x diff --git a/lib/urbanopt/reopt/reopt_ghp_adapter.rb b/lib/urbanopt/reopt/reopt_ghp_adapter.rb index d1630b7e..0300eb0a 100644 --- a/lib/urbanopt/reopt/reopt_ghp_adapter.rb +++ b/lib/urbanopt/reopt/reopt_ghp_adapter.rb @@ -10,7 +10,7 @@ class REoptGHPAdapter def initialize # initialize @@logger @@logger ||= URBANopt::REopt.reopt_logger - # Define class variable + # Define class variable @@hours_in_year = 8760 end @@ -247,7 +247,8 @@ def create_reopt_input_district(run_dir, system_parameter_hash, reopt_ghp_assump # This is not used in REopt calculation but required for formatting. reopt_inputs_district[:DomesticHotWaterLoad][:fuel_loads_mmbtu_per_hour] = [0.0000001]*@@hours_in_year - reopt_inputs_district[:ElectricLoad] = {} + # Adding year for ElectricLoad so district electric load can be calculated with REopt API v3.11 + reopt_inputs_district[:ElectricLoad] = {:year => 2017} #required for reopt formatting reopt_inputs_district[:ElectricLoad][:loads_kw] = [0.00001]*@@hours_in_year diff --git a/lib/urbanopt/reopt/reopt_ghp_files/reopt_ghp_assumption.json b/lib/urbanopt/reopt/reopt_ghp_files/reopt_ghp_assumption.json index a2640c64..0b90b021 100644 --- a/lib/urbanopt/reopt/reopt_ghp_files/reopt_ghp_assumption.json +++ b/lib/urbanopt/reopt/reopt_ghp_files/reopt_ghp_assumption.json @@ -5,19 +5,20 @@ }, "SpaceHeatingLoad": { }, - "DomesticHotWaterLoad": { + "DomesticHotWaterLoad": { }, - "ElectricLoad": { + "ElectricLoad": { + "year": 2017 }, "ElectricTariff": { - "urdb_label": "594976725457a37b1175d089" + "urdb_label": "594976725457a37b1175d089" }, "GHP":{ "installed_cost_heatpump_per_ton": 1075, "installed_cost_ghx_per_ft": 14, "installed_cost_building_hydronic_loop_per_sqft": 1.7, "om_cost_per_sqft_year": 0, - "macrs_bonus_fraction": 0.6, + "macrs_bonus_fraction": 0.6, "macrs_itc_reduction": 0.5, "federal_itc_fraction": 0.3 }, diff --git a/lib/urbanopt/reopt/reopt_ghp_post_processor.rb b/lib/urbanopt/reopt/reopt_ghp_post_processor.rb index 7572e81a..08e16cbc 100644 --- a/lib/urbanopt/reopt/reopt_ghp_post_processor.rb +++ b/lib/urbanopt/reopt/reopt_ghp_post_processor.rb @@ -13,7 +13,6 @@ module URBANopt # :nodoc: module REopt # :nodoc: class REoptGHPPostProcessor - def initialize(run_dir, system_parameter, modelica_result, reopt_ghp_assumptions = nil, nrel_developer_key = nil, localhost) # initialize @@logger @@logger ||= URBANopt::REopt.reopt_logger @@ -42,11 +41,11 @@ def initialize(run_dir, system_parameter, modelica_result, reopt_ghp_assumptions File.open(system_parameter, 'r') do |file| @system_parameter_input_hash = JSON.parse(file.read, symbolize_names: true) end - #Determine loop order + # Determine loop order loop_order = File.join(File.dirname(system_parameter), '_loop_order.json') if File.exist?(loop_order) File.open(loop_order, 'r') do |file| - loop_order_input= JSON.parse(file.read, symbolize_names: true) + loop_order_input = JSON.parse(file.read, symbolize_names: true) # Check the type of the parsed data if loop_order_input.is_a?(Array) @loop_order_input_hash = loop_order_input @@ -61,7 +60,7 @@ def initialize(run_dir, system_parameter, modelica_result, reopt_ghp_assumptions puts "GHE IDs in group: #{item[:list_ghe_ids_in_group].inspect}" end else - puts "Unexpected JSON structure" + puts 'Unexpected JSON structure' end end end @@ -77,7 +76,6 @@ def initialize(run_dir, system_parameter, modelica_result, reopt_ghp_assumptions # # Create REopt input and output building report def run_reopt_lcca(system_parameter_hash: nil, reopt_ghp_assumptions_hash: nil, modelica_result: nil) - adapter = URBANopt::REopt::REoptGHPAdapter.new # if these arguments are specified, use them @@ -89,14 +87,13 @@ def run_reopt_lcca(system_parameter_hash: nil, reopt_ghp_assumptions_hash: nil, @reopt_ghp_assumptions_input_hash = reopt_ghp_assumptions_hash end - if !modelica_result.nil? @modelica_result_input = modelica_result end # Create folder for REopt input files only if they dont exist - reopt_ghp_dir = File.join(@run_dir, "reopt_ghp") - reopt_ghp_input = File.join(reopt_ghp_dir, "reopt_ghp_inputs") + reopt_ghp_dir = File.join(@run_dir, 'reopt_ghp') + reopt_ghp_input = File.join(reopt_ghp_dir, 'reopt_ghp_inputs') unless Dir.exist?(reopt_ghp_dir) FileUtils.mkdir_p(reopt_ghp_dir) end @@ -104,7 +101,7 @@ def run_reopt_lcca(system_parameter_hash: nil, reopt_ghp_assumptions_hash: nil, FileUtils.mkdir_p(reopt_ghp_input) end - reopt_ghp_output = File.join(reopt_ghp_dir, "reopt_ghp_outputs") + reopt_ghp_output = File.join(reopt_ghp_dir, 'reopt_ghp_outputs') unless Dir.exist?(reopt_ghp_output) FileUtils.mkdir_p(reopt_ghp_output) end @@ -142,14 +139,11 @@ def run_reopt_lcca(system_parameter_hash: nil, reopt_ghp_assumptions_hash: nil, # reopt_ghp_output_file reopt_output_file = File.join(reopt_ghp_output, "#{base_name}_output.json") - #call the REopt API + # call the REopt API api = URBANopt::REopt::REoptLiteGHPAPI.new(reopt_input_data, DEVELOPER_NREL_KEY, reopt_output_file, @localhost) - api.get_api_results() - + api.get_api_results end - end - - end #REoptGHPPostProcessor - end #REopt -end #URBANopt + end # REoptGHPPostProcessor + end # REopt +end # URBANopt diff --git a/lib/urbanopt/reopt/reopt_post_processor.rb b/lib/urbanopt/reopt/reopt_post_processor.rb index 5ff93a7e..e646934a 100644 --- a/lib/urbanopt/reopt/reopt_post_processor.rb +++ b/lib/urbanopt/reopt/reopt_post_processor.rb @@ -33,7 +33,6 @@ def initialize(scenario_report, scenario_reopt_assumptions_file = nil, reopt_fea end @nrel_developer_key = nrel_developer_key @localhost = localhost - @reopt_base_post = { ElectricTariff: {}, ElectricLoad: {}, Wind: { max_kw: 0 } } @scenario_reopt_default_output_file = nil @scenario_timeseries_default_output_file = nil diff --git a/lib/urbanopt/reopt/reopt_schema/REopt-GHP-input.json b/lib/urbanopt/reopt/reopt_schema/REopt-GHP-input.json index 73795571..741153bf 100644 --- a/lib/urbanopt/reopt/reopt_schema/REopt-GHP-input.json +++ b/lib/urbanopt/reopt/reopt_schema/REopt-GHP-input.json @@ -44,7 +44,7 @@ }, "building_sqft": { "type": "float", - "requierd": true, + "required": true, "description": "to calculate cost of hydronic loop", "note": "for the GHX iteration, set building_sqft to a number close to 0 but not exactly 0" }, @@ -69,7 +69,7 @@ "number_of_boreholes": { "type": "int", "required": true, - "note": "in the GHP iteration, set this value to 0" + "note": "in the GHP iteration, set this value to 0" }, "length_boreholes_ft": { "type": "float", @@ -146,4 +146,3 @@ } } } - \ No newline at end of file diff --git a/lib/urbanopt/reopt/scenario_report_adapter.rb b/lib/urbanopt/reopt/scenario_report_adapter.rb index bea02820..57b212b7 100755 --- a/lib/urbanopt/reopt/scenario_report_adapter.rb +++ b/lib/urbanopt/reopt/scenario_report_adapter.rb @@ -43,9 +43,9 @@ def reopt_json_from_scenario_report(scenario_report, reopt_assumptions_json = ni else @@logger.info('Using default REopt assumptions') reopt_inputs = { - Settings:{}, + Settings: {}, Site: {}, - Financial:{}, + Financial: {}, ElectricTariff: { monthly_demand_rates: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], monthly_energy_rates: [0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13] @@ -277,12 +277,8 @@ def update_scenario_report(scenario_report, reopt_output, timeseries_csv_path = wind = reopt_output['outputs']['Wind'] # find size_class size_class = nil - if reopt_output['inputs']['Wind']['size_class'] - size_class = reopt_output['inputs']['Wind']['size_class'] - else - size_class = 'commercial' # default - end - scenario_report.distributed_generation.add_tech 'wind', URBANopt::Reporting::DefaultReports::Wind.new({ size_kw: (wind['size_kw'] || 0), size_class: size_class, average_yearly_energy_produced_kwh: (wind['average_yearly_energy_produced_kwh'] || 0) }) + size_class = reopt_output['inputs']['Wind']['size_class'] || 'commercial' + scenario_report.distributed_generation.add_tech 'wind', URBANopt::Reporting::DefaultReports::Wind.new({ size_kw: (wind['size_kw'] || 0), size_class:, average_yearly_energy_produced_kwh: (wind['average_yearly_energy_produced_kwh'] || 0) }) end if reopt_output['outputs'].key?('Generator') @@ -491,7 +487,7 @@ def modrow(data, idx) # :nodoc: data[$storage_to_grid_col] = $storage_to_grid[idx] || 0 if defined?(storage) data[$storage_soc_col] = $storage_soc[idx] || 0 if defined?(storage) data[$generator_total_col] = $generator_total[idx] || 0 if defined?(generator) - data[$generator_to_battery_col] = $generator_to_battery[idx] || 0 if (defined?(generator) && defined?(storage)) + data[$generator_to_battery_col] = $generator_to_battery[idx] || 0 if defined?(generator) && defined?(storage) data[$generator_to_load_col] = $generator_to_load[idx] || 0 if defined?(generator) data[$generator_to_grid_col] = $generator_to_grid[idx] || 0 if defined?(generator) data[$pv_total_col] = $pv_total[idx] || 0 @@ -499,7 +495,7 @@ def modrow(data, idx) # :nodoc: data[$pv_to_load_col] = $pv_to_load[idx] || 0 data[$pv_to_grid_col] = $pv_to_grid[idx] || 0 data[$wind_total_col] = $wind_total[idx] || 0 if defined?(wind) - data[$wind_to_battery_col] = $wind_to_battery[idx] || 0 if (defined?(wind) && defined?(storage)) + data[$wind_to_battery_col] = $wind_to_battery[idx] || 0 if defined?(wind) && defined?(storage) data[$wind_to_load_col] = $wind_to_load[idx] || 0 if defined?(wind) data[$wind_to_grid_col] = $wind_to_grid[idx] || 0 if defined?(wind) return data diff --git a/lib/urbanopt/reopt/version.rb b/lib/urbanopt/reopt/version.rb index 86f00869..a226ccfe 100755 --- a/lib/urbanopt/reopt/version.rb +++ b/lib/urbanopt/reopt/version.rb @@ -5,6 +5,6 @@ module URBANopt # :nodoc: module REopt # :nodoc: - VERSION = '0.13.0'.freeze + VERSION = '1.0.0'.freeze end end diff --git a/spec/files/reopt_assumptions_4tsperhour_v3.json b/spec/files/reopt_assumptions_4tsperhour_v3.json index 960d9f5c..730881cd 100644 --- a/spec/files/reopt_assumptions_4tsperhour_v3.json +++ b/spec/files/reopt_assumptions_4tsperhour_v3.json @@ -30,7 +30,8 @@ "ElectricLoad": { "critical_loads_kw_is_net": false, "critical_load_fraction": 0, - "loads_kw_is_net": true + "loads_kw_is_net": true, + "year": 2017 }, "PV": { "name": "Roof - South Face", diff --git a/spec/files/reopt_assumptions_with_wind_v3.json b/spec/files/reopt_assumptions_with_wind_v3.json index 18f98276..04201372 100644 --- a/spec/files/reopt_assumptions_with_wind_v3.json +++ b/spec/files/reopt_assumptions_with_wind_v3.json @@ -29,7 +29,8 @@ "ElectricLoad": { "critical_loads_kw_is_net": false, "critical_load_fraction": 0, - "loads_kw_is_net": true + "loads_kw_is_net": true, + "year": 2017 }, "PV": { "name": "Roof - South Face", diff --git a/spec/tests/urbanopt_reopt_ghp_spec.rb b/spec/tests/urbanopt_reopt_ghp_spec.rb index 91652663..205d80c0 100644 --- a/spec/tests/urbanopt_reopt_ghp_spec.rb +++ b/spec/tests/urbanopt_reopt_ghp_spec.rb @@ -67,6 +67,7 @@ building_4_data = JSON.parse(File.read(@building_4_path), symbolize_names: true) expect(building_4_data[:Site][:latitude]).to_not be_nil + expect(building_4_data[:ElectricLoad][:year]).to_not be_nil expect(building_4_data[:ElectricLoad][:loads_kw]).to_not be_empty expect(building_4_data[:ElectricLoad][:loads_kw].size).to eq(8760) expect(building_4_data[:ElectricTariff][:urdb_label]).to_not be_nil @@ -83,7 +84,7 @@ File.open(reopt_input_file_path, 'r') do |f| reopt_input_data = JSON.parse(f.read) end - post_url = "https://developer.nrel.gov/api/reopt/stable/job/?api_key=#{DEVELOPER_NREL_KEY}" + post_url = "https://developer.nrel.gov/api/reopt/v3/job/?api_key=#{DEVELOPER_NREL_KEY}" # Parse the URL and prepare the HTTP request uri = URI.parse(post_url) diff --git a/spec/tests/urbanopt_reopt_spec.rb b/spec/tests/urbanopt_reopt_spec.rb index 90c0304e..8e81d659 100644 --- a/spec/tests/urbanopt_reopt_spec.rb +++ b/spec/tests/urbanopt_reopt_spec.rb @@ -146,11 +146,15 @@ reopt_post_processor = URBANopt::REopt::REoptPostProcessor.new(nil, nil, nil, DEVELOPER_NREL_KEY) # Act - feature_report = reopt_post_processor.run_feature_report(feature_report: feature_report, reopt_assumptions_hash: reopt_assumptions, reopt_output_file: reopt_output_file, timeseries_csv_path: timeseries_output_file, save_name: 'feature_report_reopt') - feature_report = reopt_post_processor.run_feature_report(feature_report: feature_report, reopt_output_file: reopt_output_file, timeseries_csv_path: timeseries_output_file, save_name: 'feature_report_reopt1') + feature_report = reopt_post_processor.run_feature_report(feature_report: feature_report, reopt_assumptions_hash: reopt_assumptions, reopt_output_file: reopt_output_file, timeseries_csv_path: timeseries_output_file, save_name: 'feature_report_reopt1') feature_report = reopt_post_processor.run_feature_report(feature_report: feature_report, reopt_assumptions_hash: reopt_assumptions, timeseries_csv_path: timeseries_output_file, save_name: 'feature_report_reopt2') feature_report = reopt_post_processor.run_feature_report(feature_report: feature_report, reopt_assumptions_hash: reopt_assumptions, reopt_output_file: reopt_output_file, save_name: 'feature_report_reopt3') - feature_report = reopt_post_processor.run_feature_report(feature_report: feature_report, save_name: 'feature_report_reopt4') + + # Assert + # Assume that file size over 7kb means data was written correctly. Test file is expected to be about 10kb + expect((File.size(feature_report_dir / 'feature_reports' / 'feature_report_reopt1.json').to_f / 1024) > 7) + expect((File.size(feature_report_dir / 'feature_reports' / 'feature_report_reopt2.json').to_f / 1024) > 7) + expect((File.size(feature_report_dir / 'feature_reports' / 'feature_report_reopt3.json').to_f / 1024) > 7) # Cleanup FileUtils.rm_rf(scenario_dir / '1' / 'reopt') diff --git a/urbanopt-reopt.gemspec b/urbanopt-reopt.gemspec index 26e7edfa..bb27ee74 100755 --- a/urbanopt-reopt.gemspec +++ b/urbanopt-reopt.gemspec @@ -25,10 +25,10 @@ Gem::Specification.new do |spec| # It would be nice to be able to use newer patches of Ruby 3.2, which would require os-extension to relax its dependency on bundler. spec.required_ruby_version = '3.2.2' - # spec.add_dependency 'openstudio-extension', '~> 0.8.2' + spec.add_dependency 'openstudio-extension', '~> 0.8.3' # Matrix is in stdlib, but needs to be specifically added here for compatibility with Ruby 3.2 spec.add_dependency 'matrix', '~> 0.4.2' - # spec.add_dependency 'urbanopt-scenario', '~> 0.12.0' + spec.add_dependency 'urbanopt-scenario', '~> 1.0.0' spec.add_development_dependency 'rspec', '~> 3.13' spec.add_development_dependency 'simplecov', '~> 0.22.0'