urbanopt-reopt 0.11.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.
@@ -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
@@ -0,0 +1,156 @@
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
+ require 'net/https'
7
+ require 'openssl'
8
+ require 'uri'
9
+ require 'json'
10
+ require 'securerandom'
11
+ require_relative '../../../developer_nrel_key'
12
+ require 'urbanopt/reopt/reopt_logger'
13
+
14
+ module URBANopt # :nodoc:
15
+ module REopt # :nodoc:
16
+ class REoptLiteGHPAPI
17
+
18
+ def initialize(reopt_input_file, nrel_developer_key = nil, reopt_output_file, use_localhost)
19
+
20
+ # Store developer key
21
+ if [nil, '', '<insert your key here>'].include? nrel_developer_key
22
+ if [nil, '', '<insert your key here>'].include? DEVELOPER_NREL_KEY
23
+ 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'
24
+ else
25
+ #Store the NREL developer key
26
+ nrel_developer_key = DEVELOPER_NREL_KEY
27
+ end
28
+ end
29
+
30
+ @use_localhost = use_localhost
31
+ if @use_localhost
32
+ @root_url = "http://localhost:8000/v3"
33
+ else
34
+ @root_url = "https://developer.nrel.gov/api/reopt/v3"
35
+ end
36
+ # add REopt URL
37
+ @nrel_developer_key = nrel_developer_key
38
+ @reopt_input_file = reopt_input_file
39
+ @reopt_output_file = reopt_output_file
40
+ # initialize @@logger
41
+ @@logger ||= URBANopt::REopt.reopt_logger
42
+ @@logger.level = Logger::INFO
43
+ end
44
+
45
+
46
+ def get_api_results(run_id=nil)
47
+
48
+ reopt_input_file = @reopt_input_file
49
+ nrel_developer_key = @nrel_developer_key
50
+ root_url = @root_url
51
+ reopt_output_file = @reopt_output_file
52
+
53
+ if run_id.nil?
54
+ run_id = get_run_uuid(reopt_input_file, nrel_developer_key, reopt_output_file)
55
+ end
56
+ if !run_id.nil?
57
+ results_url = "#{@root_url}/job/#{run_id}/results/?api_key=#{nrel_developer_key}"
58
+ puts "This is results URL #{results_url}"
59
+ results = reopt_request(results_url)
60
+
61
+ File.open(reopt_output_file, 'w') do |f|
62
+ f.write(JSON.pretty_generate(results))
63
+ @@logger.info("Saved results to #{reopt_output_file}")
64
+ end
65
+ else
66
+ results = nil
67
+ @@logger.error("Unable to get results: no UUID returned.")
68
+ end
69
+ results
70
+ end
71
+
72
+ def get_run_uuid(reopt_input_file, nrel_developer_key, root_url)
73
+
74
+ reopt_input_file = @reopt_input_file
75
+ nrel_developer_key = @nrel_developer_key
76
+ root_url = @root_url
77
+ post_url = "#{root_url}/job/?api_key=#{nrel_developer_key}"
78
+ puts "This is URL: #{post_url}"
79
+ @@logger.info("Connecting to #{post_url}")
80
+
81
+ # Parse the URL and prepare the HTTP request
82
+ uri = URI.parse(post_url)
83
+ request = Net::HTTP::Post.new(uri)
84
+ request.content_type = 'application/json'
85
+
86
+ # Add the JSON payload (assuming 'post' is the body data)
87
+ request.body = reopt_input_file.to_json
88
+
89
+ # Send the HTTP request
90
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
91
+ http.request(request)
92
+ end
93
+
94
+ run_id = nil
95
+
96
+ if !response.is_a?(Net::HTTPSuccess)
97
+ @@logger.error("Status code #{response.code}. #{response.body}")
98
+ @@logger.error("Status code #{response.code}")
99
+ else
100
+ @@logger.info("Response OK from #{post_url}.")
101
+ run_id_dict = JSON.parse(response.body)
102
+
103
+ begin
104
+ run_id = run_id_dict['run_uuid']
105
+ rescue KeyError
106
+ msg = "Response from #{post_url} did not contain run_uuid."
107
+ @@logger.error(msg)
108
+ end
109
+ end
110
+ # Return run_id
111
+ run_id
112
+ end
113
+
114
+ def reopt_request(results_url, poll_interval = 5, max_timeout = 300)
115
+
116
+ key_error_count = 0
117
+ key_error_threshold = 3
118
+ status = "Optimizing..."
119
+ @@logger.info("Polling #{results_url} for results with interval of #{poll_interval}...")
120
+ resp_dict = {}
121
+ start_time = Time.now
122
+
123
+ loop do
124
+ uri = URI.parse(results_url)
125
+ response = Net::HTTP.get_response(uri)
126
+ resp_dict = JSON.parse(response.body)
127
+
128
+ begin
129
+ status = resp_dict['status']
130
+ rescue KeyError
131
+ key_error_count += 1
132
+ @@logger.info("KeyError count: #{key_error_count}")
133
+ if key_error_count > key_error_threshold
134
+ @@logger.info("Breaking polling loop due to KeyError count threshold of #{key_error_threshold} exceeded.")
135
+ break
136
+ end
137
+ end
138
+
139
+ if status != "Optimizing..."
140
+ break
141
+ end
142
+
143
+ if Time.now - start_time > max_timeout
144
+ @@logger.info("Breaking polling loop due to max timeout of #{max_timeout} seconds exceeded.")
145
+ break
146
+ end
147
+
148
+ sleep(poll_interval)
149
+
150
+ end
151
+ resp_dict
152
+ end
153
+
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,27 @@
1
+ {
2
+ "Site": {
3
+ "latitude": 42.81428490645775,
4
+ "longitude": -78.84701778930912
5
+ },
6
+ "SpaceHeatingLoad": {
7
+ },
8
+ "DomesticHotWaterLoad": {
9
+ },
10
+ "ElectricLoad": {
11
+ "year": 2017
12
+ },
13
+ "ElectricTariff": {
14
+ "urdb_label": "594976725457a37b1175d089"
15
+ },
16
+ "GHP":{
17
+ "installed_cost_heatpump_per_ton": 1075,
18
+ "installed_cost_ghx_per_ft": 14,
19
+ "installed_cost_building_hydronic_loop_per_sqft": 1.7,
20
+ "om_cost_per_sqft_year": 0,
21
+ "macrs_bonus_fraction": 0.6,
22
+ "macrs_itc_reduction": 0.5,
23
+ "federal_itc_fraction": 0.3
24
+ },
25
+ "ExistingBoiler": {
26
+ }
27
+ }
@@ -0,0 +1,149 @@
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
+ require 'bundler/setup'
7
+ require 'urbanopt/reopt/reopt_logger'
8
+ require 'urbanopt/reopt/reopt_ghp_api'
9
+ require 'csv'
10
+ require 'json'
11
+ require 'fileutils'
12
+
13
+ module URBANopt # :nodoc:
14
+ module REopt # :nodoc:
15
+ class REoptGHPPostProcessor
16
+ def initialize(run_dir, system_parameter, modelica_result, reopt_ghp_assumptions = nil, nrel_developer_key = nil, localhost)
17
+ # initialize @@logger
18
+ @@logger ||= URBANopt::REopt.reopt_logger
19
+
20
+ @nrel_developer_key = nrel_developer_key
21
+ @localhost = localhost
22
+ @reopt_ghp_output_district = nil
23
+ @reopt_ghp_output_building = []
24
+ @reopt_ghp_assumptions_hash = nil
25
+ @reopt_ghp_assumptions = nil
26
+ @system_parameter = nil
27
+ @system_parameter_hash = nil
28
+ @modelica_result = nil
29
+ @building_ids = nil
30
+ @run_dir = run_dir
31
+
32
+ if !reopt_ghp_assumptions.nil?
33
+ @reopt_ghp_assumptions = reopt_ghp_assumptions
34
+ File.open(reopt_ghp_assumptions, 'r') do |file|
35
+ @reopt_ghp_assumptions_input_hash = JSON.parse(file.read, symbolize_names: true)
36
+ end
37
+ end
38
+
39
+ if !system_parameter.nil?
40
+ @system_parameter = system_parameter
41
+ File.open(system_parameter, 'r') do |file|
42
+ @system_parameter_input_hash = JSON.parse(file.read, symbolize_names: true)
43
+ end
44
+ # Determine loop order
45
+ loop_order = File.join(File.dirname(system_parameter), '_loop_order.json')
46
+ if File.exist?(loop_order)
47
+ File.open(loop_order, 'r') do |file|
48
+ loop_order_input = JSON.parse(file.read, symbolize_names: true)
49
+ # Check the type of the parsed data
50
+ if loop_order_input.is_a?(Array)
51
+ @loop_order_input_hash = loop_order_input
52
+ @loop_order_input_hash.each do |item|
53
+ puts "Building IDs in group: #{item[:list_bldg_ids_in_group].inspect}"
54
+ puts "GHE IDs in group: #{item[:list_ghe_ids_in_group].inspect}"
55
+ end
56
+ elsif loop_order_input.is_a?(Hash)
57
+ @loop_order_input_hash = [loop_order_input] # Wrap in array if a single object
58
+ @loop_order_input_hash.each do |item|
59
+ puts "Building IDs in group: #{item[:list_bldg_ids_in_group].inspect}"
60
+ puts "GHE IDs in group: #{item[:list_ghe_ids_in_group].inspect}"
61
+ end
62
+ else
63
+ puts 'Unexpected JSON structure'
64
+ end
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+ if !modelica_result.nil?
71
+ @modelica_result_input = modelica_result
72
+ end
73
+ end
74
+
75
+ attr_accessor :run_dir, :system_parameter_input_hash, :reopt_ghp_assumptions_input_hash, :loop_order_input_hash, :modelica_result_input
76
+
77
+ # # Create REopt input and output building report
78
+ def run_reopt_lcca(system_parameter_hash: nil, reopt_ghp_assumptions_hash: nil, modelica_result: nil)
79
+ adapter = URBANopt::REopt::REoptGHPAdapter.new
80
+
81
+ # if these arguments are specified, use them
82
+ if !system_parameter_hash.nil?
83
+ @system_parameter_input_hash = system_parameter_hash
84
+ end
85
+
86
+ if !reopt_ghp_assumptions_hash.nil?
87
+ @reopt_ghp_assumptions_input_hash = reopt_ghp_assumptions_hash
88
+ end
89
+
90
+ if !modelica_result.nil?
91
+ @modelica_result_input = modelica_result
92
+ end
93
+
94
+ # Create folder for REopt input files only if they dont exist
95
+ reopt_ghp_dir = File.join(@run_dir, 'reopt_ghp')
96
+ reopt_ghp_input = File.join(reopt_ghp_dir, 'reopt_ghp_inputs')
97
+ unless Dir.exist?(reopt_ghp_dir)
98
+ FileUtils.mkdir_p(reopt_ghp_dir)
99
+ end
100
+ unless Dir.exist?(reopt_ghp_input)
101
+ FileUtils.mkdir_p(reopt_ghp_input)
102
+ end
103
+
104
+ reopt_ghp_output = File.join(reopt_ghp_dir, 'reopt_ghp_outputs')
105
+ unless Dir.exist?(reopt_ghp_output)
106
+ FileUtils.mkdir_p(reopt_ghp_output)
107
+ end
108
+
109
+ # get building IDs from _loop_order.json
110
+ building_ids = []
111
+ ghp_ids = []
112
+ @loop_order_input_hash.each do |loop|
113
+ building_ids.concat(loop[:list_bldg_ids_in_group].flatten)
114
+ ghp_ids.concat(loop[:list_ghe_ids_in_group].flatten)
115
+ end
116
+
117
+ building_ids.each do |building_id|
118
+ # create REopt building input file for all buildings in loop order list
119
+ reopt_input_building = adapter.create_reopt_input_building(@run_dir, @system_parameter_input_hash, @reopt_ghp_assumptions_input_hash, building_id, @modelica_result_input)
120
+ end
121
+ ghp_ids.each do |ghp_id|
122
+ # create REopt district input file
123
+ reopt_input_district = adapter.create_reopt_input_district(@run_dir, @system_parameter_input_hash, @reopt_ghp_assumptions_input_hash, ghp_id, @modelica_result_input)
124
+ end
125
+
126
+ Dir.foreach(reopt_ghp_input) do |input_file|
127
+ # Skip '.' and '..' (current and parent directory entries)
128
+ next if input_file == '.' || input_file == '..'
129
+
130
+ reopt_ghp_input_file_path = File.join(reopt_ghp_input, input_file)
131
+
132
+ reopt_input_data = nil
133
+
134
+ File.open(reopt_ghp_input_file_path, 'r') do |f|
135
+ reopt_input_data = JSON.parse(f.read)
136
+ end
137
+
138
+ base_name = File.basename(input_file, '.json')
139
+
140
+ # reopt_ghp_output_file
141
+ reopt_output_file = File.join(reopt_ghp_output, "#{base_name}_output.json")
142
+ # call the REopt API
143
+ api = URBANopt::REopt::REoptLiteGHPAPI.new(reopt_input_data, DEVELOPER_NREL_KEY, reopt_output_file, @localhost)
144
+ api.get_api_results
145
+ end
146
+ end
147
+ end # REoptGHPPostProcessor
148
+ end # REopt
149
+ end # URBANopt