urbanopt-reopt 0.12.0 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ebc2f3c6f40a363e9f234917e55bd165eab74734c2ae477dd393893fb6210a3d
4
- data.tar.gz: a2502bf5a657e7d256e39be74426f8eb91d7774f64851274a98c67347a33de12
3
+ metadata.gz: d7585e601e08ef8ae4b677124c3f6790129b44dbfe7da4c701168698437df94c
4
+ data.tar.gz: fb375bcd95645f6821ee8f06fce5d27eb682f1253db82d71ada98b9f043c921f
5
5
  SHA512:
6
- metadata.gz: dfad70c6283e27679164c2b6c14dcbb7a009643a838614a4d31f04aca864f548324cea4dfa0978e02eba29bd7705ea32a31867763aca16120a90205cf10ad241
7
- data.tar.gz: 243aca41f2c5f45970f346cb7d430a7443fd8f0a5db08b59e43db23ef8a6063d9aefd56e4a5b45a54cb89d708c147531e34424f8172c23276943faa987169b74
6
+ metadata.gz: a8d65764ca0eb6ec58a63f188882c2e70ba5457299f7ddbac75c7a98cc5d2762f62a82f1ecb7cb5d61c330d9ad29d6e7a38d1b24ee429c5d30ef910199f1919c
7
+ data.tar.gz: c91ec78859161c11e381771b0a5f231e7ee168dc758f49f9e80035e15483ca8c1b95c42a32334dca830f60c67d167c5016cc6ea776be2cff39180bde42091852
@@ -2,13 +2,11 @@ name: REopt-gem CI
2
2
 
3
3
  on:
4
4
  workflow_dispatch:
5
- # push:
5
+ push:
6
6
  schedule:
7
7
  # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule
8
8
  # 5:23 am UTC (11:23pm MDT the day before) every weekday night in MDT
9
9
  - cron: '23 5 * * 2-6'
10
- pull_request:
11
- types: [review_requested]
12
10
 
13
11
  env:
14
12
  # This env var should enforce develop branch of all dependencies
@@ -21,17 +19,16 @@ jobs:
21
19
  # https://github.com/rbenv/ruby-build/discussions/1940
22
20
  runs-on: ubuntu-latest
23
21
  container:
24
- image: docker://nrel/openstudio:3.7.0
22
+ image: docker://nrel/openstudio:3.9.0
25
23
  steps:
26
24
  - uses: actions/checkout@v4
27
25
  - name: set git config options
28
26
  shell: bash
29
- run: |
30
- git config --global --add safe.directory '*'
27
+ run: git config --global --add safe.directory '*'
31
28
  - name: Update gems
32
29
  run: |
30
+ bundle install
33
31
  bundle update
34
- bundle exec certified-update
35
32
  - name: Run Rspec
36
33
  run: bundle exec rspec
37
34
  - name: Coveralls
data/.gitignore CHANGED
@@ -16,7 +16,7 @@
16
16
  /lib/measures/test_results
17
17
  /lib/measures/.rubocop*
18
18
  # test files
19
- spec/run/
19
+ spec/files/run/baseline_scenario_ghe/reopt_ghp/
20
20
 
21
21
  developer_nrel_key.rb
22
22
  # rspec failure tracking
data/CHANGELOG.md CHANGED
@@ -1,12 +1,29 @@
1
1
  # URBANopt REopt Gem
2
2
 
3
+ ## Version 1.0.0
4
+
5
+ ## What's Changed
6
+
7
+ * Upgrade to OpenStudio 3.9 and Ruby 3.2.2 by @vtnate in https://github.com/urbanopt/urbanopt-reopt-gem/pull/155
8
+ * Updates for REopt v3.11 by @vtnate in https://github.com/urbanopt/urbanopt-reopt-gem/pull/157
9
+
10
+ ### Other Changes
11
+
12
+ * expose error messages for certificate verify failed by @kflemin in https://github.com/urbanopt/urbanopt-reopt-gem/pull/151
13
+ * REopt GHP LCCA Analysis by @tanushree04 in https://github.com/urbanopt/urbanopt-reopt-gem/pull/153
14
+
15
+ **Full Changelog**: https://github.com/urbanopt/urbanopt-reopt-gem/compare/v0.12.0...v1.0.0
16
+
3
17
  ## Version 0.12.0
18
+
4
19
  Date Range: 01/11/24 - 05/06/24
20
+
5
21
  * Use Reopt v3 by @vtnate in https://github.com/urbanopt/urbanopt-reopt-gem/pull/149
6
22
 
7
23
  **Full Changelog**: https://github.com/urbanopt/urbanopt-reopt-gem/compare/v0.11.0...v0.12.0
8
24
 
9
25
  ## Version 0.11.0
26
+
10
27
  Date Range: 07/06/23 - 01/11/24
11
28
 
12
29
  * Use different error messages in different places by @vtnate in https://github.com/urbanopt/urbanopt-reopt-gem/pull/145
@@ -17,6 +34,7 @@ Date Range: 07/06/23 - 01/11/24
17
34
  **Full Changelog**: https://github.com/urbanopt/urbanopt-reopt-gem/compare/v0.10.0...v0.11.0
18
35
 
19
36
  ## Version 0.10.0
37
+
20
38
  Date Range: 12/13/22 - 7/6/23
21
39
 
22
40
  - Update dependencies for OpenStudio 3.6.1
data/Gemfile CHANGED
@@ -17,7 +17,9 @@ allow_local = ENV['FAVOR_LOCAL_GEMS']
17
17
  # if allow_local && File.exist?('../OpenStudio-extension-gem')
18
18
  # gem 'openstudio-extension', path: '../OpenStudio-extension-gem'
19
19
  # elsif allow_local
20
- # gem 'openstudio-extension', github: 'NREL/OpenStudio-extension-gem', branch: 'develop'
20
+ # gem 'openstudio-extension', github: 'NREL/OpenStudio-extension-gem', branch: 'develop'
21
+ # else
22
+ # gem 'openstudio-extension', '~> 0.8.1'
21
23
  # end
22
24
  #
23
25
  # if allow_local && File.exist?('../openstudio-common-measures-gem')
@@ -41,14 +43,17 @@ allow_local = ENV['FAVOR_LOCAL_GEMS']
41
43
  # gem 'openstudio-model-articulation', '0.1.0'
42
44
  # end
43
45
 
44
- # if allow_local && File.exist?('../urbanopt-scenario-gem')
45
- # gem 'urbanopt-scenario', path: '../urbanopt-scenario-gem'
46
- # elsif allow_local
47
- # gem 'urbanopt-scenario', github: 'URBANopt/urbanopt-scenario-gem', branch: 'reopt-v3'
48
- # end
46
+ if allow_local && File.exist?('../urbanopt-scenario-gem')
47
+ gem 'urbanopt-scenario', path: '../urbanopt-scenario-gem'
48
+ elsif allow_local
49
+ gem 'urbanopt-scenario', github: 'URBANopt/urbanopt-scenario-gem', branch: 'develop'
50
+ end
49
51
 
50
- # if allow_local && File.exists?('../urbanopt-geojson-gem')
51
- # gem 'urbanopt-geojson', path: '../urbanopt-geojson-gem'
52
- # elsif allow_local
53
- # gem 'urbanopt-geojson', github: 'URBANopt/urbanopt-geojson-gem', branch: 'develop'
54
- # end
52
+ # Temporary! Remove this once reporting-gem is merged/released
53
+ # gem 'urbanopt-reporting', github: 'URBANopt/urbanopt-reporting-gem', branch: 'develop'
54
+
55
+ if allow_local && File.exist?('../urbanopt-geojson-gem')
56
+ gem 'urbanopt-geojson', path: '../urbanopt-geojson-gem'
57
+ elsif allow_local
58
+ gem 'urbanopt-geojson', github: 'URBANopt/urbanopt-geojson-gem', branch: 'develop'
59
+ end
data/LICENSE.md CHANGED
@@ -1,4 +1,4 @@
1
- URBANopt (tm), Copyright (c) 2019-2024, Alliance for Sustainable Energy, LLC, and other
1
+ URBANopt (tm), Copyright (c) 2019-2025, Alliance for Sustainable Energy, LLC, and other
2
2
  contributors. All rights reserved.
3
3
 
4
4
  Redistribution and use in source and binary forms, with or without modification,
@@ -1,4 +1,4 @@
1
- URBANopt (tm), Copyright (c) 2019-2024, Alliance for Sustainable Energy, LLC, and other
1
+ URBANopt (tm), Copyright (c) 2019-2025, Alliance for Sustainable Energy, LLC, and other
2
2
  contributors. All rights reserved.
3
3
 
4
4
  Redistribution and use in source and binary forms, with or without modification,
@@ -1,6 +1,6 @@
1
1
  <%
2
2
  # *********************************************************************************
3
- # URBANopt (tm), Copyright (c) 2019-2024, Alliance for Sustainable Energy, LLC, and other
3
+ # URBANopt (tm), Copyright (c) 2019-2025, Alliance for Sustainable Energy, LLC, and other
4
4
  # contributors. All rights reserved.
5
5
 
6
6
  # Redistribution and use in source and binary forms, with or without modification,
@@ -1,6 +1,6 @@
1
1
  # REopt Lite Outputs Schema
2
2
 
3
- When the gem calls the REopt Lite APUI it recieves the following complete set of results described in the data dictionary below. Only those needed to update a Feature or Scenario Report's distributed_generation attibute set and timeseries CSV are pulled from the reponse and transferred to the Feature or Scenario Report. You may choose to modify the code to include more or less of the full REopt Lite response.
3
+ When the gem calls the REopt Lite APUI it receives the following complete set of results described in the data dictionary below. Only those needed to update a Feature or Scenario Report's distributed_generation attribute set and timeseries CSV are pulled from the response and transferred to the Feature or Scenario Report. You may choose to modify the code to include more or less of the full REopt Lite response.
4
4
 
5
5
  ## Data Dictionary
6
6
  <ReoptOutputSchema />
@@ -41,9 +41,9 @@ module URBANopt # :nodoc:
41
41
  else
42
42
  @@logger.info('Using default REopt assumptions')
43
43
  reopt_inputs = {
44
- Settings:{},
44
+ Settings: {},
45
45
  Site: {},
46
- Financial:{},
46
+ Financial: {},
47
47
  ElectricTariff: {
48
48
  monthly_demand_rates: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
49
49
  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]
@@ -151,14 +151,13 @@ module URBANopt # :nodoc:
151
151
  #
152
152
  # [*parameters:*]
153
153
  #
154
- # * +feature_report+ - _URBANopt::Reporting::DefaultReports::FeatureReport_ - FeatureReport to update from a \REopt reponse hash.
155
- # * +reopt_output+ - _Hash_ - A reponse hash from the \REopt API to use in overwriting FeatureReport technology sizes, costs and dispatch strategies.
154
+ # * +feature_report+ - _URBANopt::Reporting::DefaultReports::FeatureReport_ - FeatureReport to update from a \REopt response hash.
155
+ # * +reopt_output+ - _Hash_ - A response hash from the \REopt API to use in overwriting FeatureReport technology sizes, costs and dispatch strategies.
156
156
  # * +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 optimization task.
157
157
  #
158
158
  # [*return:*] _URBANopt::Reporting::DefaultReports::FeatureReport_ - Returns an updated FeatureReport.
159
159
  ##
160
160
  def update_feature_report(feature_report, reopt_output, timeseries_csv_path = nil, resilience_stats = nil)
161
-
162
161
  # Check if the \REopt response is valid
163
162
  if reopt_output['status'] != 'optimal'
164
163
  @@logger.error("ERROR cannot update Feature Report #{feature_report.name} #{feature_report.id} - REopt optimization was non-optimal")
@@ -439,7 +438,7 @@ module URBANopt # :nodoc:
439
438
  x[$storage_to_grid_col] = $storage_to_grid[i] || 0 if defined?(storage)
440
439
  x[$storage_soc_col] = $storage_soc[i] || 0 if defined?(storage)
441
440
  x[$generator_total_col] = $generator_total[i] || 0 if defined?(generator)
442
- x[$generator_to_battery_col] = $generator_to_battery[i] || 0 if (defined?(generator) && defined?(storage))
441
+ x[$generator_to_battery_col] = $generator_to_battery[i] || 0 if defined?(generator) && defined?(storage)
443
442
  x[$generator_to_load_col] = $generator_to_load[i] || 0 if defined?(generator)
444
443
  x[$generator_to_grid_col] = $generator_to_grid[i] || 0 if defined?(generator)
445
444
  x[$pv_total_col] = $pv_total[i] || 0
@@ -447,7 +446,7 @@ module URBANopt # :nodoc:
447
446
  x[$pv_to_load_col] = $pv_to_load[i] || 0
448
447
  x[$pv_to_grid_col] = $pv_to_grid[i] || 0
449
448
  x[$wind_total_col] = $wind_total[i] || 0 if defined?(wind)
450
- x[$wind_to_battery_col] = $wind_to_battery[i] || 0 if (defined?(wind) && defined?(storage))
449
+ x[$wind_to_battery_col] = $wind_to_battery[i] || 0 if defined?(wind) && defined?(storage)
451
450
  x[$wind_to_load_col] = $wind_to_load[i] || 0 if defined?(wind)
452
451
  x[$wind_to_grid_col] = $wind_to_grid[i] || 0 if defined?(wind)
453
452
  return x
@@ -0,0 +1,337 @@
1
+ # *********************************************************************************
2
+ # URBANopt (tm), Copyright (c) Alliance for Sustainable Energy, LLC.
3
+ # See also https://github.com/urbanopt/urbanopt-reopt-gem/blob/develop/LICENSE.md
4
+ # *********************************************************************************
5
+
6
+ module URBANopt # :nodoc:
7
+ module REopt # :nodoc:
8
+ class REoptGHPAdapter
9
+
10
+ def initialize
11
+ # initialize @@logger
12
+ @@logger ||= URBANopt::REopt.reopt_logger
13
+ # Define class variable
14
+ @@hours_in_year = 8760
15
+ end
16
+
17
+ def create_reopt_input_building(run_dir, system_parameter_hash, reopt_ghp_assumptions_hash, building_id, modelica_result)
18
+
19
+ # Define variables
20
+ reopt_inputs_building = {}
21
+ if !reopt_ghp_assumptions_hash.nil?
22
+ reopt_inputs_building = reopt_ghp_assumptions_hash
23
+ else
24
+ @@logger.info('Using default REopt assumptions')
25
+ # create a dictionary for REopt Inputs
26
+ reopt_inputs_building = {
27
+ Site: {},
28
+ SpaceHeatingLoad: {},
29
+ DomesticHotWaterLoad: {},
30
+ ElectricLoad: {},
31
+ ElectricTariff: {
32
+ urdb_label: ""
33
+ },
34
+ GHP: {},
35
+ ExistingBoiler: {}
36
+ }
37
+ end
38
+
39
+ # The URDB label is required to be specified in the input assumption file
40
+ if reopt_inputs_building[:ElectricTariff][:urdb_label].nil? || reopt_inputs_building[:ElectricTariff][:urdb_label].empty?
41
+ raise "Missing value for urdb_label - this is a required input"
42
+ end
43
+
44
+ scenario_json_path = File.join(run_dir, "default_scenario_report.json")
45
+ if File.exist?(scenario_json_path)
46
+ File.open(scenario_json_path, 'r') do |file|
47
+ scenario_json_data = JSON.parse(file.read, symbolize_names: true)
48
+ # update site location
49
+ @latitude = scenario_json_data[:scenario_report][:location][:latitude_deg]
50
+ @longitude = scenario_json_data[:scenario_report][:location][:longitude_deg]
51
+ reopt_inputs_building[:Site][:latitude] = @latitude
52
+ reopt_inputs_building[:Site][:longitude] = @longitude
53
+
54
+ end
55
+ end
56
+
57
+ reopt_inputs_building[:SpaceHeatingLoad][:fuel_loads_mmbtu_per_hour] = []
58
+ # Read the default csv report
59
+ default_feature_report_path = File.join(run_dir, building_id.to_s, "feature_reports", "default_feature_report.csv")
60
+ if File.exist?(default_feature_report_path)
61
+ timeseries_data = CSV.read(default_feature_report_path, headers: true)
62
+
63
+ # Initialize the total kBtu sum
64
+ total_kbtu = 0.0
65
+
66
+ # Convert each value in "Heating:NaturalGas(kBtu)" to MMBtu and store in the array
67
+ timeseries_data.each do |row|
68
+ if row['Heating:NaturalGas(kBtu)'] # Ensure the value exists
69
+ kBtu_value = row['Heating:NaturalGas(kBtu)'].to_f # Convert to float
70
+ total_kbtu += kBtu_value # Sum kBtu values
71
+ end
72
+ end
73
+ # Check if the total kBtu is zero
74
+ if total_kbtu.zero?
75
+ # If zero, populate with hourly values meet reopts formatting requirements
76
+ reopt_inputs_building[:SpaceHeatingLoad][:fuel_loads_mmbtu_per_hour] = [0.000001] * @@hours_in_year
77
+ else
78
+ # If not zero, convert and append to the array
79
+ timeseries_data.each do |row|
80
+ if row['Heating:NaturalGas(kBtu)'] # Ensure the value exists
81
+ kBtu_value = row['Heating:NaturalGas(kBtu)'].to_f # Convert to float
82
+ mMBtu_value = kBtu_value / 1000 # Convert kBtu to MMBtu
83
+ reopt_inputs_building[:SpaceHeatingLoad][:fuel_loads_mmbtu_per_hour] << mMBtu_value # Append to the array
84
+ end
85
+ end
86
+ end
87
+
88
+ else
89
+ # Calculate space heating load
90
+ reopt_inputs_building[:SpaceHeatingLoad][:fuel_loads_mmbtu_per_hour] = [0.000001] * @@hours_in_year
91
+ puts "Existing heating fuel cost was not taken into consideration in result calculations."
92
+ end
93
+
94
+
95
+ # read_modelica_result
96
+ modelica_project = File.expand_path(modelica_result)
97
+ project_name = File.basename(modelica_project)
98
+ @modelica_csv = File.join(
99
+ modelica_project,
100
+ "#{project_name}.Districts.DistrictEnergySystem_results",
101
+ "#{project_name}.Districts.DistrictEnergySystem_result.csv"
102
+ )
103
+
104
+ if File.exist?(@modelica_csv)
105
+ modelica_data = CSV.read(@modelica_csv, headers: true)
106
+ heating_power = "heating_electric_power_#{building_id}"
107
+ cooling_power = "cooling_electric_power_#{building_id}"
108
+ pump_power = "pump_power_#{building_id}"
109
+ ets_pump_power = "ets_pump_power_#{building_id}"
110
+ heating_system_capacity = "heating_system_capacity_#{building_id}"
111
+ cooling_system_capacity = "cooling_system_capacity_#{building_id}"
112
+
113
+ heating_power_values = cooling_power_values = pump_power_values = ets_pump_power_values = []
114
+ total_electric_load_building = []
115
+ # Ensure the column exists
116
+ if modelica_data.headers.include?(heating_power)
117
+ heating_power_values = modelica_data[heating_power]
118
+ end
119
+ if modelica_data.headers.include?(cooling_power)
120
+ cooling_power_values = modelica_data[cooling_power]
121
+ end
122
+ if modelica_data.headers.include?(pump_power)
123
+ pump_power_values = modelica_data[pump_power]
124
+ end
125
+ if modelica_data.headers.include?(ets_pump_power)
126
+ ets_pump_power_values = modelica_data[ets_pump_power]
127
+ end
128
+
129
+ total_electric_load_building = heating_power_values.zip(cooling_power_values, pump_power_values, ets_pump_power_values).map do |elements|
130
+ # Convert watts to kilowatts
131
+ elements.map { |e| e.to_f / 1000 }.sum
132
+ end
133
+
134
+ peak_combined_heatpump_thermal_ton = 0
135
+
136
+ if modelica_data.headers.include?(heating_system_capacity)
137
+ heating_system_capacity_value = modelica_data[heating_system_capacity][0]
138
+ end
139
+ if modelica_data.headers.include?(cooling_system_capacity)
140
+ cooling_system_capacity_value = modelica_data[cooling_system_capacity][0]
141
+ end
142
+
143
+ watts_per_ton_cooling_capacity = 3517
144
+ peak_combined_heatpump_thermal_ton = ([heating_system_capacity_value.to_f.abs, cooling_system_capacity_value.to_f.abs].max) / watts_per_ton_cooling_capacity
145
+
146
+ # Store the result in reopt_inputs_building ElectricLoad
147
+ reopt_inputs_building[:ElectricLoad][:loads_kw] = total_electric_load_building
148
+
149
+
150
+ domestic_hot_water = total_electric_load_building.map do |load|
151
+ load * 0
152
+ end
153
+
154
+ # This is not used in REopt calculation but required for formatting.
155
+ reopt_inputs_building[:DomesticHotWaterLoad][:fuel_loads_mmbtu_per_hour] = domestic_hot_water
156
+
157
+ # Add GHP Fields
158
+ reopt_inputs_building[:GHP] = {}
159
+ # REopt default
160
+ reopt_inputs_building[:GHP][:require_ghp_purchase] = 1
161
+ reopt_inputs_building[:GHP][:om_cost_per_sqft_year] = 0
162
+ reopt_inputs_building[:GHP][:heatpump_capacity_sizing_factor_on_peak_load] = 1.0
163
+ # Add the floor area
164
+ building_json_path = File.join(run_dir, building_id.to_s, "feature_reports", "default_feature_report.json")
165
+ if File.exist?(building_json_path)
166
+ File.open(building_json_path, 'r') do |file|
167
+ building_json_data = JSON.parse(file.read, symbolize_names: true)
168
+ reopt_inputs_building[:GHP][:building_sqft] = building_json_data[:program][:floor_area_sqft]
169
+ end
170
+ else
171
+ puts "File not found: #{building_json_path}"
172
+ end
173
+
174
+ # Add existing boiler fuel cost
175
+ # TODO : Add this as optional user input
176
+ nat_gas_dollars_per_mmbtu = 13.5
177
+ reopt_inputs_building[:ExistingBoiler][:fuel_cost_per_mmbtu] = nat_gas_dollars_per_mmbtu
178
+
179
+ # Add ghpghx_responses
180
+ ghpghx_output = {}
181
+ ghpghx_output[:outputs] = {}
182
+ ghpghx_output[:inputs] = {}
183
+ ghpghx_output[:outputs][:heat_pump_configuration] = "WSHP"
184
+ # This is not used in REopt calculation but required for formatting.
185
+ ghpghx_output[:outputs][:yearly_ghx_pump_electric_consumption_series_kw] = [0] * @@hours_in_year
186
+ ghpghx_output[:outputs][:number_of_boreholes] = 0
187
+ ghpghx_output[:outputs][:length_boreholes_ft] = 0
188
+
189
+ ghpghx_output[:outputs][:peak_combined_heatpump_thermal_ton] = peak_combined_heatpump_thermal_ton
190
+ ghpghx_output[:outputs][:yearly_total_electric_consumption_kwh] = total_electric_load_building.sum
191
+ ghpghx_output[:outputs][:yearly_total_electric_consumption_series_kw] = total_electric_load_building
192
+ ghpghx_output[:outputs][:yearly_heating_heatpump_electric_consumption_series_kw] = total_electric_load_building
193
+ ghpghx_output[:outputs][:yearly_cooling_heatpump_electric_consumption_series_kw] = [0] * @@hours_in_year
194
+ # This is not used in REopt calculation but required for formatting.
195
+ ghpghx_output[:inputs][:heating_thermal_load_mmbtu_per_hr] = [0.0001] * @@hours_in_year
196
+ # This is not used in REopt calculation but required for formatting.
197
+ ghpghx_output[:inputs][:cooling_thermal_load_ton] = [0] * @@hours_in_year
198
+
199
+ ghpghx_output_all = [ghpghx_output]
200
+ reopt_inputs_building[:GHP][:ghpghx_responses] = {}
201
+ reopt_inputs_building[:GHP][:ghpghx_responses] = ghpghx_output_all
202
+
203
+ end
204
+
205
+ #save output report in reopt_ghp directory
206
+ reopt_ghp_dir = File.join(run_dir, "reopt_ghp", "reopt_ghp_inputs")
207
+ json_file_path = File.join(reopt_ghp_dir, "GHP_building_#{building_id}.json")
208
+ pretty_json = JSON.pretty_generate(reopt_inputs_building)
209
+ File.write(json_file_path, pretty_json)
210
+
211
+ end
212
+
213
+ def create_reopt_input_district(run_dir, system_parameter_hash, reopt_ghp_assumptions_hash, ghp_id, modelica_result)
214
+
215
+ reopt_inputs_district = {}
216
+
217
+ if !reopt_ghp_assumptions_hash.nil?
218
+ reopt_inputs_district = reopt_ghp_assumptions_hash
219
+ else
220
+ @@logger.info('Using default REopt assumptions')
221
+ # create a dictionary for REopt Inputs
222
+ reopt_inputs_district = {
223
+ Site: {},
224
+ SpaceHeatingLoad: {},
225
+ DomesticHotWaterLoad: {},
226
+ ElectricLoad: {},
227
+ ElectricTariff: {
228
+ "urdb_label": ""
229
+ },
230
+ GHP: {},
231
+ ExistingBoiler: {}
232
+ }
233
+ end
234
+
235
+ reopt_inputs_district[:Site] = {}
236
+ reopt_inputs_district[:Site][:latitude] = @latitude
237
+ reopt_inputs_district[:Site][:longitude] = @longitude
238
+ # The URDB label is required to be specified in the input assumption file
239
+ if reopt_inputs_district[:ElectricTariff][:urdb_label].nil? || reopt_inputs_district[:ElectricTariff][:urdb_label].empty?
240
+
241
+ raise "Missing value for urdb_label - this is a required input"
242
+
243
+ end
244
+
245
+ # This is not used in REopt calculation but required for formatting.
246
+ reopt_inputs_district[:SpaceHeatingLoad][:fuel_loads_mmbtu_per_hour] = [0.000001]*@@hours_in_year
247
+ # This is not used in REopt calculation but required for formatting.
248
+ reopt_inputs_district[:DomesticHotWaterLoad][:fuel_loads_mmbtu_per_hour] = [0.0000001]*@@hours_in_year
249
+
250
+ # Adding year for ElectricLoad so district electric load can be calculated with REopt API v3.11
251
+ reopt_inputs_district[:ElectricLoad] = {:year => 2017}
252
+ #required for reopt formatting
253
+ reopt_inputs_district[:ElectricLoad][:loads_kw] = [0.00001]*@@hours_in_year
254
+
255
+ reopt_inputs_district[:ExistingBoiler] = {}
256
+ reopt_inputs_district[:ExistingBoiler][:fuel_cost_per_mmbtu] = 13.5
257
+
258
+ # GHP inputs
259
+ reopt_inputs_district[:GHP] = {}
260
+ reopt_inputs_district[:GHP][:require_ghp_purchase] = 1
261
+ reopt_inputs_district[:GHP][:building_sqft] = 0.00001
262
+ reopt_inputs_district[:GHP][:om_cost_per_sqft_year] = 0
263
+ reopt_inputs_district[:GHP][:heatpump_capacity_sizing_factor_on_peak_load] = 1.0
264
+
265
+ # Add ghpghx outputs
266
+ ghpghx_output = {}
267
+ ghpghx_output[:outputs] = {}
268
+ ghpghx_output[:inputs] = {}
269
+
270
+ ghpghx_output[:inputs][:heating_thermal_load_mmbtu_per_hr] = [0]*@@hours_in_year
271
+ ghpghx_output[:inputs][:cooling_thermal_load_ton] = [0] * @@hours_in_year
272
+
273
+
274
+ # Read GHX sizes from system parameter hash
275
+ ghe_specific_params = system_parameter_hash[:district_system][:fifth_generation][:ghe_parameters][:ghe_specific_params]
276
+ ghe_specific_params.each do |ghe_specific_param|
277
+ if ghe_specific_param[:ghe_id] = ghp_id
278
+ number_of_boreholes = ghe_specific_param[:borehole][:number_of_boreholes]
279
+ length_of_boreholes = ghe_specific_param[:borehole][:length_of_boreholes]
280
+ ghpghx_output[:outputs][:number_of_boreholes] = number_of_boreholes
281
+ # convert meters to feet
282
+ ghpghx_output[:outputs][:length_boreholes_ft] = (length_of_boreholes)*3.28084
283
+ end
284
+ end
285
+
286
+ if File.exist?(@modelica_csv)
287
+
288
+ modelica_data = CSV.read(@modelica_csv, headers: true)
289
+
290
+ electrical_power_consumed = modelica_data["electrical_power_consumed"]
291
+ # Convert watts to kilowatts
292
+ electrical_power_consumed_kw = electrical_power_consumed.map { |e| e.to_f / 1000 }
293
+ # if ghp_id.include?('-')
294
+ # # Note: For some reason when reading columns, '-' from the column headers are removed, whereas ghp_id has -
295
+ # ghp_id_formatted = ghp_id.delete('-')
296
+ # ghp_column = "electrical_power_consumed_#{ghp_id_formatted}".to_sym
297
+
298
+ # else
299
+ # # Note: For some reason when reading columns, '-' from the column headers are removed, whereas ghp_id has -
300
+ # ghp_column = "electrical_power_consumed_#{ghp_id}".to_sym
301
+ # end
302
+
303
+ # # Ensure the column exists
304
+ # unless modelica_data.headers.include?(ghp_column)
305
+ # puts "Column #{ghp_column} does not exist in the CSV file."
306
+ # end
307
+
308
+ # # Access values from the column
309
+ # column_values = modelica_data.by_col[ghp_column]
310
+
311
+ ghpghx_output[:outputs][:yearly_ghx_pump_electric_consumption_series_kw] = electrical_power_consumed_kw
312
+ else
313
+ ghpghx_output[:outputs][:yearly_ghx_pump_electric_consumption_series_kw] = [0.000000001]*@@hours_in_year
314
+ end
315
+
316
+ # This is not used in REopt calculation but required for formatting.
317
+ ghpghx_output[:outputs][:peak_combined_heatpump_thermal_ton] = 0.000000001
318
+
319
+ ghpghx_output[:outputs][:heat_pump_configuration] = "WSHP"
320
+ # Required for REpot formatting
321
+ ghpghx_output[:outputs][:yearly_total_electric_consumption_series_kw] = [0.00001] * @@hours_in_year
322
+ ghpghx_output[:outputs][:yearly_heating_heatpump_electric_consumption_series_kw] = [0] * @@hours_in_year
323
+ ghpghx_output[:outputs][:yearly_cooling_heatpump_electric_consumption_series_kw] = [0] * @@hours_in_year
324
+
325
+ ghpghx_output_all = [ghpghx_output, ghpghx_output]
326
+ reopt_inputs_district[:GHP][:ghpghx_responses] = ghpghx_output_all
327
+
328
+ #save output report in reopt_ghp directory
329
+ reopt_ghp_dir = File.join(run_dir, "reopt_ghp", "reopt_ghp_inputs")
330
+ json_file_path = File.join(reopt_ghp_dir, "GHX_#{ghp_id}.json")
331
+ pretty_json = JSON.pretty_generate(reopt_inputs_district)
332
+ File.write(json_file_path, pretty_json)
333
+ end
334
+
335
+ end
336
+ end
337
+ end