urbanopt-rnm-us 0.1.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,52 @@
1
+ # *********************************************************************************
2
+ # URBANopt (tm), Copyright (c) 2019-2021, Alliance for Sustainable Energy, LLC, and other
3
+ # contributors. All rights reserved.
4
+
5
+ # Redistribution and use in source and binary forms, with or without modification,
6
+ # are permitted provided that the following conditions are met:
7
+
8
+ # Redistributions of source code must retain the above copyright notice, this list
9
+ # of conditions and the following disclaimer.
10
+
11
+ # Redistributions in binary form must reproduce the above copyright notice, this
12
+ # list of conditions and the following disclaimer in the documentation and/or other
13
+ # materials provided with the distribution.
14
+
15
+ # Neither the name of the copyright holder nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without specific
17
+ # prior written permission.
18
+
19
+ # Redistribution of this software, without modification, must refer to the software
20
+ # by the same designation. Redistribution of a modified version of this software
21
+ # (i) may not refer to the modified version by the same designation, or by any
22
+ # confusingly similar designation, and (ii) must refer to the underlying software
23
+ # originally provided by Alliance as "URBANopt". Except to comply with the foregoing,
24
+ # the term "URBANopt", or any confusingly similar designation may not be used to
25
+ # refer to any modified version of this software or any modified version of the
26
+ # underlying software originally provided by Alliance without the prior written
27
+ # consent of Alliance.
28
+
29
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
30
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
31
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
32
+ # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33
+ # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
34
+ # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
36
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
37
+ # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
38
+ # OF THE POSSIBILITY OF SUCH DAMAGE.
39
+ # *********************************************************************************
40
+
41
+ require 'logger'
42
+
43
+ module URBANopt
44
+ module RNM
45
+ @@logger = Logger.new($stdout)
46
+
47
+ # Definining class variable "@@logger" to log errors, info and warning messages.
48
+ def self.logger
49
+ @@logger
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,129 @@
1
+ # *********************************************************************************
2
+ # URBANopt (tm), Copyright (c) 2019-2021, Alliance for Sustainable Energy, LLC, and other
3
+ # contributors. All rights reserved.
4
+
5
+ # Redistribution and use in source and binary forms, with or without modification,
6
+ # are permitted provided that the following conditions are met:
7
+
8
+ # Redistributions of source code must retain the above copyright notice, this list
9
+ # of conditions and the following disclaimer.
10
+
11
+ # Redistributions in binary form must reproduce the above copyright notice, this
12
+ # list of conditions and the following disclaimer in the documentation and/or other
13
+ # materials provided with the distribution.
14
+
15
+ # Neither the name of the copyright holder nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without specific
17
+ # prior written permission.
18
+
19
+ # Redistribution of this software, without modification, must refer to the software
20
+ # by the same designation. Redistribution of a modified version of this software
21
+ # (i) may not refer to the modified version by the same designation, or by any
22
+ # confusingly similar designation, and (ii) must refer to the underlying software
23
+ # originally provided by Alliance as "URBANopt". Except to comply with the foregoing,
24
+ # the term "URBANopt", or any confusingly similar designation may not be used to
25
+ # refer to any modified version of this software or any modified version of the
26
+ # underlying software originally provided by Alliance without the prior written
27
+ # consent of Alliance.
28
+
29
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
30
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
31
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
32
+ # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33
+ # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
34
+ # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
36
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
37
+ # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
38
+ # OF THE POSSIBILITY OF SUCH DAMAGE.
39
+ # *********************************************************************************
40
+
41
+ # find when the power lines in a street have to be considered OH or UG
42
+ # according to a threshold height obtained from the %UG defined by the user
43
+ module URBANopt
44
+ module RNM
45
+ class OhUgRate
46
+ attr_accessor :av_height, :type, :n_buildings_street
47
+
48
+ def initialize
49
+ @av_height = av_height
50
+ @type = type
51
+ @n_buildings_street = n_buildings_street
52
+ end
53
+
54
+ # for each street calculate the average height given the buildings in the streets
55
+ # and it calculates the number of buildings in each street
56
+ def height_building(coordinates_building, coordinates_street, floors)
57
+ height = []
58
+ id_building = []
59
+ height_sum = 0
60
+ n_buildings_street = 0
61
+ for i in 0..coordinates_street.length - 1
62
+ dist_min = 5000
63
+ for j in 0..floors.length - 1
64
+ for k in 0..coordinates_building[j].length - 1
65
+ y = coordinates_building[j][k][1] - coordinates_street[i][1]
66
+ x = coordinates_building[j][k][0] - coordinates_street[i][0]
67
+ distance = (x + y) / 2 # finding the distance of each building node with each street node
68
+ if distance < dist_min
69
+ distance = (x**2 + y**2)**0.5
70
+ if distance < dist_min
71
+ dist_min = distance
72
+ height[i] = floors[j]
73
+ id_building[i] = coordinates_building[j][k][3].split('_')[0] # for future implementations set as attributes :id, :coordinates
74
+ end
75
+ end
76
+ end
77
+ end
78
+ ii = 0
79
+ while ii != i && id_building[i] != id_building[ii] # id_building[ii] != id_bui
80
+ ii += 1
81
+ if ii == i
82
+ n_buildings_street += 1
83
+ end
84
+ end
85
+ height_sum += height[i]
86
+ end
87
+ @n_buildings_street = n_buildings_street
88
+ @av_height = (height_sum / (i + 1)).to_f.round(2)
89
+ end
90
+
91
+ # defining a method which defines the "threshold height", in an iterative way and adding 0.1m of height until the threhold limit is reached
92
+ # when the % of streets in the district above the threshold is equal to the UG rate defined by the user
93
+ def threshold_height(street_type, ug_ratio)
94
+ h_threshold = 0
95
+ tot_build_in_streets = 0
96
+ n_street_oh = []
97
+ for i in 0..street_type.length - 1
98
+ tot_build_in_streets += street_type[i].n_buildings_street
99
+ end
100
+ # puts tot_build_in_streets
101
+ street_set_oh = (tot_build_in_streets * (1 - ug_ratio)).round
102
+ ii = 0
103
+ while ii == 0 || n_street_oh[ii - 1] < street_set_oh
104
+ n_street_oh[ii] = 0
105
+ for i in 0..street_type.length - 1
106
+ if street_type[i].av_height < h_threshold
107
+ n_street_oh[ii] += street_type[i].n_buildings_street
108
+ end
109
+ end
110
+ ii += 1
111
+ h_threshold += 0.10
112
+ end
113
+ h_threshold -= 0.10
114
+ return h_threshold
115
+ end
116
+
117
+ # defining a method that decides to place each street either OH or UG, according to its average height and the threshold height,
118
+ # if the street has a height below the threshold height it has to modelled has OH, otherwise UG
119
+ def classify_street_type(street_type, ug_ratio)
120
+ h = threshold_height(street_type, ug_ratio)
121
+ if @av_height < h
122
+ @type = 'A' # 'OH'
123
+ else
124
+ @type = 'S' # 'UG'
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,220 @@
1
+ # *********************************************************************************
2
+ # URBANopt (tm), Copyright (c) 2019-2021, Alliance for Sustainable Energy, LLC, and other
3
+ # contributors. All rights reserved.
4
+
5
+ # Redistribution and use in source and binary forms, with or without modification,
6
+ # are permitted provided that the following conditions are met:
7
+
8
+ # Redistributions of source code must retain the above copyright notice, this list
9
+ # of conditions and the following disclaimer.
10
+
11
+ # Redistributions in binary form must reproduce the above copyright notice, this
12
+ # list of conditions and the following disclaimer in the documentation and/or other
13
+ # materials provided with the distribution.
14
+
15
+ # Neither the name of the copyright holder nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without specific
17
+ # prior written permission.
18
+
19
+ # Redistribution of this software, without modification, must refer to the software
20
+ # by the same designation. Redistribution of a modified version of this software
21
+ # (i) may not refer to the modified version by the same designation, or by any
22
+ # confusingly similar designation, and (ii) must refer to the underlying software
23
+ # originally provided by Alliance as "URBANopt". Except to comply with the foregoing,
24
+ # the term "URBANopt", or any confusingly similar designation may not be used to
25
+ # refer to any modified version of this software or any modified version of the
26
+ # underlying software originally provided by Alliance without the prior written
27
+ # consent of Alliance.
28
+
29
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
30
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
31
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
32
+ # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33
+ # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
34
+ # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
36
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
37
+ # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
38
+ # OF THE POSSIBILITY OF SUCH DAMAGE.
39
+ # *********************************************************************************
40
+
41
+ require 'urbanopt/rnm/logger'
42
+
43
+ module URBANopt
44
+ module RNM
45
+ class PostProcessor
46
+ ##
47
+ # Initialize Post-Processor
48
+ ##
49
+ # [parameters:]
50
+ # * +results+ - _Hash_ - Hash of RNM-US results returned from the API
51
+ # * +scenario+ - _String_ - Path to scenario_dir
52
+ def initialize(results, scenario_dir, feature_file, reopt: false)
53
+ @results = results
54
+ @scenario_dir = scenario_dir
55
+ @results_dir = File.join(@scenario_dir, 'rnm-us', 'results')
56
+ @report_filename = 'scenario_report_rnm.json'
57
+ @geojson_filename = 'feature_file_rnm.json'
58
+ @feature_file = feature_file
59
+ @reopt = reopt
60
+ end
61
+
62
+ ##
63
+ # Post Process report and feature file
64
+ ##
65
+ def post_process
66
+ generate_report
67
+ generate_feature_file
68
+ puts "RNM results were added to scenario report and feature file. New files can be found in #{@results_dir}"
69
+ end
70
+
71
+ ##
72
+ # Generate Scenario report
73
+ ##
74
+ def generate_report
75
+
76
+ # calculate rnm statistics
77
+ rnm_stats = calculate_stats
78
+
79
+ # get scenario
80
+ scenario = get_scenario
81
+
82
+ # merge stats with scenario report (before feature_reports section)
83
+
84
+ scenario['scenario_report']['rnm_results'] = rnm_stats
85
+
86
+ # save back to scenario directory as scenario_report_rnm.json
87
+ File.open(File.join(@scenario_dir, @report_filename), "w") do |f|
88
+ f.write(JSON.pretty_generate(scenario))
89
+ end
90
+
91
+ end
92
+
93
+ ##
94
+ # Load Scenario Report
95
+ ##
96
+ def get_scenario
97
+ if @reopt
98
+ # get reopt scenario report
99
+ return JSON.parse(File.read(File.join(@scenario_dir, 'feature_optimization.json')))
100
+ else
101
+ # get default scenario report
102
+ return JSON.parse(File.read(File.join(@scenario_dir, 'default_scenario_report.json')))
103
+ end
104
+ end
105
+
106
+ ##
107
+ # Generate new GeoJSON file
108
+ ##
109
+ def generate_feature_file
110
+
111
+ # get results GeoJSON file and read in
112
+ results = JSON.parse(File.read(File.join(@results_dir, 'GeoJSON', 'Distribution_system.json')))
113
+
114
+ # merge the two files
115
+ results['features'].each do |feature|
116
+ @feature_file['features'].append(feature)
117
+ end
118
+
119
+ # save back to scenario directory as features_and_rnm.json
120
+ File.open(File.join(@scenario_dir, @geojson_filename), "w") do |f|
121
+ f.write(JSON.pretty_generate(@feature_file))
122
+ end
123
+ end
124
+
125
+ ##
126
+ # Calculate report statistics from raw results
127
+ ##
128
+ def calculate_stats
129
+ # calculate statistics and append to scenario report
130
+ stats = {}
131
+ # demand generation planning
132
+ stats['demand_generation_planning'] = []
133
+ @results['Demand/generation and number of consumers/distributed generators'].each do |item|
134
+ rec = {}
135
+ case item['Voltage level']
136
+ when 'LV'
137
+ rec['type'] = "Low Voltage (LV) #{item['Type'].strip}"
138
+ when 'MV'
139
+ rec['type'] = "Medium Voltage (MV) #{item['Type'].strip}"
140
+ else
141
+ rec['type'] = item['Voltage level'] + item['Type'].strip
142
+ end
143
+ if item['Type'].strip == 'Consumers'
144
+ # consumers
145
+ rec['peak_demand_kw'] = item['Peak demand/generation (kW)']
146
+ elsif item['Type'].include? 'generators'
147
+ # generators
148
+ rec['max_generation_kw'] = item['Peak demand/generation (kW)']
149
+ else
150
+ rec['peak_demand_generation_kw'] = item['Peak demand/generation (kW)']
151
+ end
152
+ rec['number_of_nodes_in_network'] = item['Number']
153
+ stats['demand_generation_planning'] << rec
154
+ end
155
+
156
+ # lines LV and MV
157
+ stats['electrical_lines_length'] = {}
158
+ km_to_mi = 0.621371
159
+ @results['Length of overhead and underground electrical lines'].each do |item|
160
+ case item['Voltage level']
161
+ when 'Lines LV'
162
+ stats['electrical_lines_length']['low_voltage'] = {}
163
+ stats['electrical_lines_length']['low_voltage']['overhead_mi'] = (item['Overhead (km)'] * km_to_mi).round(4)
164
+ stats['electrical_lines_length']['low_voltage']['underground_mi'] = (item['Underground (km)'] * km_to_mi).round(4)
165
+ when 'Lines MV'
166
+ stats['electrical_lines_length']['medium_voltage'] = {}
167
+ stats['electrical_lines_length']['medium_voltage']['overhead_mi'] = (item['Overhead (km)'] * km_to_mi).round(4)
168
+ stats['electrical_lines_length']['medium_voltage']['underground_mi'] = (item['Underground (km)'] * km_to_mi).round(4)
169
+ end
170
+ end
171
+ transformer_capacity = 0
172
+ @results['Substations and distribution transformers'].each do |item|
173
+ transformer_capacity += item['Size (kVA)'] * item['Number']
174
+ end
175
+ stats['distribution_transformers_capacity_kva'] = transformer_capacity
176
+
177
+ # costs
178
+ stats['costs'] = {}
179
+ stats['costs']['investment'] = {}
180
+ stats['costs']['yearly_maintenance'] = {}
181
+ @results['Summary'].each do |item|
182
+ case item['Level']
183
+ when 'LV'
184
+ stats['costs']['investment']['low_voltage_network'] = item['Investment cost']
185
+ stats['costs']['yearly_maintenance']['low_voltage_network'] = item['Preventive maintenance (yearly)']
186
+ when 'MV'
187
+ stats['costs']['investment']['medium_voltage_network'] = item['Investment cost']
188
+ stats['costs']['yearly_maintenance']['medium_voltage_network'] = item['Preventive maintenance (yearly)']
189
+ when 'Dist.Transf.'
190
+ stats['costs']['investment']['distribution_transformers'] = item['Investment cost']
191
+ stats['costs']['yearly_maintenance']['distribution_transformers'] = item['Preventive maintenance (yearly)']
192
+ when 'HV/MV Subest.'
193
+ stats['costs']['investment']['primary_substations'] = item['Investment cost']
194
+ stats['costs']['yearly_maintenance']['primary_substations'] = item['Preventive maintenance (yearly)']
195
+ end
196
+ end
197
+ # cost totals
198
+ inv_tot = 0
199
+ stats['costs']['investment'].each do |key, val|
200
+ inv_tot += val
201
+ end
202
+ stats['costs']['investment']['total'] = inv_tot
203
+ maint_tot = 0
204
+ stats['costs']['yearly_maintenance'].each do |key, val|
205
+ maint_tot += val
206
+ end
207
+ stats['costs']['yearly_maintenance']['total'] = maint_tot
208
+
209
+ # reliability indexes
210
+ stats['reliability_indexes'] = {}
211
+ # sum of interruptions duration / num customers.  6 would be too high
212
+ stats['reliability_indexes']['SAIDI'] = @results['Reliability indexes'][0]['ASIDI']
213
+ # num interruptions / num customers.  should be < 1
214
+ stats['reliability_indexes']['SAIFI'] = @results['Reliability indexes'][0]['ASIFI']
215
+
216
+ return stats
217
+ end
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,73 @@
1
+ # *********************************************************************************
2
+ # URBANopt (tm), Copyright (c) 2019-2021, Alliance for Sustainable Energy, LLC, and other
3
+ # contributors. All rights reserved.
4
+
5
+ # Redistribution and use in source and binary forms, with or without modification,
6
+ # are permitted provided that the following conditions are met:
7
+
8
+ # Redistributions of source code must retain the above copyright notice, this list
9
+ # of conditions and the following disclaimer.
10
+
11
+ # Redistributions in binary form must reproduce the above copyright notice, this
12
+ # list of conditions and the following disclaimer in the documentation and/or other
13
+ # materials provided with the distribution.
14
+
15
+ # Neither the name of the copyright holder nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without specific
17
+ # prior written permission.
18
+
19
+ # Redistribution of this software, without modification, must refer to the software
20
+ # by the same designation. Redistribution of a modified version of this software
21
+ # (i) may not refer to the modified version by the same designation, or by any
22
+ # confusingly similar designation, and (ii) must refer to the underlying software
23
+ # originally provided by Alliance as "URBANopt". Except to comply with the foregoing,
24
+ # the term "URBANopt", or any confusingly similar designation may not be used to
25
+ # refer to any modified version of this software or any modified version of the
26
+ # underlying software originally provided by Alliance without the prior written
27
+ # consent of Alliance.
28
+
29
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
30
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
31
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
32
+ # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33
+ # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
34
+ # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
36
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
37
+ # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
38
+ # OF THE POSSIBILITY OF SUCH DAMAGE.
39
+ # *********************************************************************************
40
+ module URBANopt
41
+ module RNM
42
+ # class created to verify that if a component is repeated more than once in the extended catalog
43
+ # than it is parsed only one time for the OpenDSS catalog
44
+ class ProcessorOpendss
45
+ attr_accessor :cont, :list
46
+
47
+ def initialize
48
+ @list = list
49
+ @cont = cont
50
+ end
51
+
52
+ def process_data(catalog_data)
53
+ for kk in 0..catalog_data.length - 1 # inside each component
54
+ zz = 0
55
+ if @list.nil?
56
+ @cont = 0
57
+ @list = []
58
+ else
59
+ if catalog_data[kk].include? 'Probability' # referring to transformers
60
+ zz += 1 while zz < @cont && @list[zz]['Name'] != catalog_data[kk]['Name']
61
+ else
62
+ zz += 1 while zz < @cont && @list[zz] != catalog_data[kk]
63
+ end
64
+ end
65
+ if zz == @cont
66
+ @list[@cont] = catalog_data[kk] # associating conductores values in this list
67
+ @cont += 1
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end