urbanopt-rnm-us 0.4.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +46 -0
- data/.gitignore +3 -1
- data/CHANGELOG.md +10 -0
- data/Gemfile +6 -5
- data/LICENSE.md +1 -1
- data/README.md +20 -4
- data/Rakefile +31 -6
- data/lib/urbanopt/rnm/api_client.rb +8 -0
- data/lib/urbanopt/rnm/consumers.rb +14 -2
- data/lib/urbanopt/rnm/geojson_input.rb +5 -1
- data/lib/urbanopt/rnm/input_files.rb +18 -0
- data/lib/urbanopt/rnm/prosumers.rb +17 -1
- data/lib/urbanopt/rnm/validation/main_validation.py +135 -33
- data/lib/urbanopt/rnm/validation/opendss_interface.py +458 -79
- data/lib/urbanopt/rnm/validation/plot_lib.py +430 -124
- data/lib/urbanopt/rnm/validation/report.py +370 -0
- data/lib/urbanopt/rnm/validation.rb +8 -6
- data/lib/urbanopt/rnm/version.rb +1 -1
- data/requirements.txt +9 -0
- data/urbanopt-rnm-us-gem.gemspec +3 -1
- metadata +35 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 78e5fa9802676b79db0cfaf9c6184581dafc1d5499c51dcf6c32070974baa221
|
4
|
+
data.tar.gz: f77669a0f5fa76cff7cf76b58d0fc67bc7671d8cbf964b6b9faec90ba2343a65
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 16e51e2d9b1c0aa9fbcb15f9e946a0feb65ab66133d1c43ab0c91ae6cab4afd0ff0746ef142a5bec279aec48be8812c37e845322800dc846e09a294916823ec7
|
7
|
+
data.tar.gz: 282ddcc5091620663891c2bd1637dd77fdb19097af21049b0d1f8992ac8a45bd9d68058bd1f61c201596dbfad0ccbb6872d0b2e90a85df26938aa540e71502a8
|
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
name: ci
|
3
|
+
|
4
|
+
on:
|
5
|
+
# push:
|
6
|
+
schedule:
|
7
|
+
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule
|
8
|
+
# 5:23 am UTC (11:23pm MDT the day before) every weekday night in MDT
|
9
|
+
- cron: '23 5 * * 2-6'
|
10
|
+
|
11
|
+
env:
|
12
|
+
# This env var should enforce develop branch of all dependencies
|
13
|
+
FAVOR_LOCAL_GEMS: true
|
14
|
+
GEM_DEVELOPER_KEY: ${{ secrets.GEM_DEVELOPER_KEY }}
|
15
|
+
|
16
|
+
jobs:
|
17
|
+
weeknight-tests:
|
18
|
+
runs-on: ubuntu-latest
|
19
|
+
container:
|
20
|
+
image: docker://nrel/openstudio:3.5.1
|
21
|
+
steps:
|
22
|
+
- uses: actions/checkout@v3
|
23
|
+
- name: Update gems
|
24
|
+
run: |
|
25
|
+
ruby --version
|
26
|
+
bundle update
|
27
|
+
bundle exec certified-update
|
28
|
+
- name: Run Rspec
|
29
|
+
continue-on-error: true
|
30
|
+
# Continue to upload step even if a test fails, so we can troubleshoot
|
31
|
+
run: bundle exec rspec
|
32
|
+
- name: Upload artifacts
|
33
|
+
uses: actions/upload-artifact@v3
|
34
|
+
if: failure() # Only upload if rspec fails
|
35
|
+
with:
|
36
|
+
name: rspec_results
|
37
|
+
path: |
|
38
|
+
spec/test*/**
|
39
|
+
# coverage/
|
40
|
+
retention-days: 7 # save for 1 week before deleting
|
41
|
+
# coveralls action docs: https://github.com/marketplace/actions/coveralls-github-action
|
42
|
+
- name: Coveralls
|
43
|
+
uses: coverallsapp/github-action@master
|
44
|
+
with:
|
45
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
46
|
+
path-to-lcov: "./coverage/lcov/urbanopt-rnm-us-gem.lcov"
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## Version 0.5.1
|
4
|
+
Date Range 12/9/22 - 6/7/23
|
5
|
+
|
6
|
+
- Fix to handle buildings with courtyard without creating additional erroneous buildings
|
7
|
+
|
8
|
+
## Version 0.5.0
|
9
|
+
Date Range 9/30/22 - 12/8/22
|
10
|
+
|
11
|
+
- Update dependencies for Extension Gem 0.6.0 and OpenStudio 3.5.0
|
12
|
+
|
3
13
|
## Version 0.4.0
|
4
14
|
Date Range 05/10/22 - 9/30/22
|
5
15
|
|
data/Gemfile
CHANGED
@@ -9,8 +9,9 @@ allow_local = ENV['FAVOR_LOCAL_GEMS']
|
|
9
9
|
# Below is an example of how to configure the gemfile for developing with local gems
|
10
10
|
# modify as appropriate
|
11
11
|
|
12
|
-
if allow_local && File.exists?('../urbanopt-geojson-gem')
|
13
|
-
|
14
|
-
elsif allow_local
|
15
|
-
|
16
|
-
end
|
12
|
+
# if allow_local && File.exists?('../urbanopt-geojson-gem')
|
13
|
+
# gem 'urbanopt-geojson', path: '../urbanopt-geojson-gem'
|
14
|
+
# elsif allow_local
|
15
|
+
# gem 'urbanopt-geojson', github: 'URBANopt/urbanopt-geojson-gem', branch: 'develop'
|
16
|
+
# end
|
17
|
+
|
data/LICENSE.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
URBANopt (tm), Copyright (c) 2019-
|
1
|
+
URBANopt (tm), Copyright (c) 2019-2023, 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,
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
[![Coverage Status](https://coveralls.io/repos/github/urbanopt/urbanopt-rnm-us-gem/badge.svg?branch=develop)](https://coveralls.io/github/urbanopt/urbanopt-rnm-us-gem?branch=develop)
|
2
|
+
|
1
3
|
# URBANopt RNM-US Gem
|
2
4
|
|
3
|
-
The URBANopt<sup>™</sup> RNM-US Gem includes functionalities to interface the URBANopt SDK to the RNM-US Gem for the development of a synthetic optimum distribution network in the considered district, given data related to the buildings energy consumption/DER energy generation and location, modeled by the other URBANopt modules.
|
5
|
+
The URBANopt<sup>™</sup> RNM-US Gem includes functionalities to interface the URBANopt SDK to the RNM-US Gem for the development of a synthetic optimum distribution network in the considered district, given data related to the buildings energy consumption/DER energy generation and location, modeled by the other URBANopt modules.
|
4
6
|
The RNM-US Gem is used to collect required data for the execution of RNM-US, which has been modeled in the other URBANopt modules, translating information provided
|
5
7
|
in .json and .csv format into .txt files
|
6
8
|
|
@@ -15,7 +17,7 @@ The OpenDSS format presents results for power system analysis and simulations.
|
|
15
17
|
The current functionalities of the RNM-US Gem include the creation of a streetmap text file, the substation txt file and multiple txt files related to the consumers peak loads and profiles and DERs peak generation and profiles.
|
16
18
|
The streetmap text file is developed from coordinates information provided by geoJSON feature file input. The customers and generators text files, which define all the network consumers and DG included in the project, are created from their peak electricity demand/generation, and building location, provided by csv and json feature_report files modeled by the URBANopt Scenario Gem.
|
17
19
|
The profiles txt files are divided among the consumers hourly profiles of active and reactive power and the DG hourly profiles of active and reactive power for the 2 most "extreme" days of maximum net demand and maximum net generation for the district .
|
18
|
-
Finally, the extended profiles txt files provide the active and reactive profiles for each consumer/DG for the whole year.
|
20
|
+
Finally, the extended profiles txt files provide the active and reactive profiles for each consumer/DG for the whole year.
|
19
21
|
|
20
22
|
|
21
23
|
## Generate input files
|
@@ -47,10 +49,24 @@ bundle exec rake create_opendss_catalog[/desired/path/to/opendss_catalog.json]
|
|
47
49
|
| API Version | RNM-US Gem Version | RNM-US exe Version |
|
48
50
|
| ----------- | ----------- | ---------------- |
|
49
51
|
| v1 | 0.3.0 and earlier | RNM-US_20220819 |
|
50
|
-
| v2 | 0.4.0 | RNM-
|
52
|
+
| v2 | 0.4.0 | RNM-US_20221018 |
|
53
|
+
|
54
|
+
|
55
|
+
## Validation Functionality
|
56
|
+
|
57
|
+
The validation and results visualization functionality is written in python. Follow these steps if you would like to use it.
|
58
|
+
|
59
|
+
1. Install python (3.10) if you do not already have it installed
|
60
|
+
1. Clone the repo to your computer
|
61
|
+
1. cd into the repo directory
|
62
|
+
1. run `bundle install` to install the required ruby dependencies
|
63
|
+
1. run `pip install -r requirements.txt` to install the required python dependencies for the validation module
|
64
|
+
1. create input files and run the simulation as usual
|
65
|
+
1. run `bundle exec rake run_validation[/path/to/scenario/csv]` to run the validation
|
66
|
+
|
51
67
|
|
52
68
|
## Testing
|
53
69
|
|
54
70
|
```bash
|
55
71
|
bundle exec rspec
|
56
|
-
```
|
72
|
+
```
|
data/Rakefile
CHANGED
@@ -148,6 +148,29 @@ task :run_simulation, [:scenario_csv_path, :reopt, :use_localhost] do |t, args|
|
|
148
148
|
puts '...done!'
|
149
149
|
end
|
150
150
|
|
151
|
+
# Full Runner workflow (mimics UO CLI functionality)
|
152
|
+
# pass in the path to the scenario csv, geojson path, whether this is a reopt analysis (true/false), and whether to use localhost RNM API (true/false)
|
153
|
+
desc 'Full Runner workflow'
|
154
|
+
task :full_runner_workflow, [:scenario_csv_path, :geojson_path, :reopt, :use_localhost] do |t, args|
|
155
|
+
# todo: could allow passing in extended catalog, average peak catalog, and opendss_catalog flags too
|
156
|
+
# if no path passed in, use default:
|
157
|
+
scenario_csv = args[:scenario_csv_path] || 'spec/test/example_project/run/baseline_scenario'
|
158
|
+
geojson_path = args[:geojson_path] || 'spec/test/example_project/example_project_with_network_and_streets'
|
159
|
+
root_dir, scenario_file_name = File.split(File.expand_path(scenario_csv))
|
160
|
+
scenario_name = File.basename(scenario_file_name, File.extname(scenario_file_name))
|
161
|
+
run_dir = File.join(root_dir, 'run', scenario_name.downcase)
|
162
|
+
reopt = args[:reopt] || false
|
163
|
+
reopt = reopt == 'true'
|
164
|
+
use_local = args[:use_localhost] || false
|
165
|
+
|
166
|
+
runner = URBANopt::RNM::Runner.new(scenario_name, run_dir, scenario_csv, geojson_path, reopt: reopt)
|
167
|
+
runner.create_simulation_files
|
168
|
+
runner.run(use_local)
|
169
|
+
runner.post_process
|
170
|
+
|
171
|
+
puts '...done!'
|
172
|
+
end
|
173
|
+
|
151
174
|
# Create opendss catalog from extended catalog
|
152
175
|
# pass in the path and filename where the OpenDSS catalog should be saved
|
153
176
|
desc 'Create OpenDSS catalog'
|
@@ -169,20 +192,22 @@ end
|
|
169
192
|
# run validation
|
170
193
|
# pass in the path to the scenario csv
|
171
194
|
desc 'Run Validation'
|
172
|
-
task :run_validation, [:scenario_csv_path, :
|
195
|
+
task :run_validation, [:scenario_csv_path, :use_numeric_ids] do |t, args|
|
173
196
|
#Exammple to run validation
|
174
|
-
#bundle exec rake run_validation[D:/.../urbanopt-rnm-us-gem/spec/files/example_project/baseline_scenario.csv]
|
197
|
+
#bundle exec rake run_validation[D:/.../urbanopt-rnm-us-gem/spec/files/example_project/baseline_scenario.csv,true]
|
198
|
+
|
175
199
|
puts 'Running OpenDSS validation'
|
200
|
+
|
176
201
|
# if no path passed in, use default:
|
177
202
|
scenario_csv = args[:scenario_csv_path] || 'spec/test/example_project/run/baseline_scenario'
|
178
203
|
root_dir, scenario_file_name = File.split(File.expand_path(scenario_csv))
|
179
204
|
scenario_name = File.basename(scenario_file_name, File.extname(scenario_file_name))
|
180
205
|
run_dir = File.join(root_dir, 'run', scenario_name.downcase)
|
181
|
-
|
182
206
|
rnm_dir = File.join(run_dir, 'rnm-us')
|
183
207
|
|
184
|
-
|
185
|
-
|
208
|
+
#Use numeric ids (for the hierarchical plot of the network)
|
209
|
+
use_numeric_ids = args[:use_numeric_ids] || false
|
210
|
+
use_numeric_ids = use_numeric_ids == 'true'
|
186
211
|
|
187
212
|
if !File.exist?(rnm_dir)
|
188
213
|
puts rnm_dir
|
@@ -190,7 +215,7 @@ task :run_validation, [:scenario_csv_path, :reopt, :use_localhost] do |t, args|
|
|
190
215
|
end
|
191
216
|
|
192
217
|
puts "run dir path: #{run_dir}"
|
193
|
-
validation = URBANopt::RNM::Validation.new(rnm_dir)
|
218
|
+
validation = URBANopt::RNM::Validation.new(rnm_dir,use_numeric_ids)
|
194
219
|
validation.run_validation()
|
195
220
|
|
196
221
|
puts '...done!'
|
@@ -108,6 +108,7 @@ module URBANopt
|
|
108
108
|
end
|
109
109
|
|
110
110
|
if !missing_files.empty?
|
111
|
+
puts "RNM DIR: #{@rnm_dir}"
|
111
112
|
raise "Input Files missing in directory: #{missing_files.join(',')}"
|
112
113
|
end
|
113
114
|
|
@@ -281,6 +282,13 @@ module URBANopt
|
|
281
282
|
# delete zip
|
282
283
|
File.delete(file_path)
|
283
284
|
|
285
|
+
# check if zip is empty
|
286
|
+
if Dir.empty? File.join(@rnm_dir, 'results')
|
287
|
+
msg = "Error in simulation: Results.zip empty"
|
288
|
+
@@logger.error(msg)
|
289
|
+
raise msg
|
290
|
+
end
|
291
|
+
|
284
292
|
else
|
285
293
|
msg = "Error retrieving results for #{the_sim_id}. error code: #{resp.status}. #{resp.body}"
|
286
294
|
@@logger.error(msg)
|
@@ -45,7 +45,7 @@ require 'csv'
|
|
45
45
|
module URBANopt
|
46
46
|
module RNM
|
47
47
|
class Consumers
|
48
|
-
attr_accessor :customers, :customers_ext, :profile_customer_p, :profile_customer_q, :profile_customer_p_ext, :profile_customer_q_ext, :power_factor
|
48
|
+
attr_accessor :customers, :customers_ext, :profile_customer_p, :profile_customer_q, :profile_customer_p_ext, :profile_customer_q_ext, :power_factor, :profile_date_time, :profile_date_time_ext
|
49
49
|
|
50
50
|
# initializing all the attributes to build the inputs files required by the RNM-US model
|
51
51
|
def initialize(reopt, only_lv_consumers = false, max_num_lv_nodes, average_building_peak_catalog_path, lv_limit)
|
@@ -55,8 +55,10 @@ module URBANopt
|
|
55
55
|
@max_num_lv_nodes = max_num_lv_nodes
|
56
56
|
@customers = []
|
57
57
|
@customers_ext = []
|
58
|
+
@profile_date_time = []
|
58
59
|
@profile_customer_p = []
|
59
60
|
@profile_customer_q = []
|
61
|
+
@profile_date_time_ext = []
|
60
62
|
@profile_customer_p_ext = []
|
61
63
|
@profile_customer_q_ext = []
|
62
64
|
@power_factor = power_factor
|
@@ -73,8 +75,10 @@ module URBANopt
|
|
73
75
|
# while the 2nd option is run in case "only LV" set to false and the consumption for each building will be placed in a single node
|
74
76
|
def construct_consumer(profiles, single_values, building_map, building_nodes, height, users, folder)
|
75
77
|
if @only_lv_consumers
|
78
|
+
planning_date_time = []
|
76
79
|
planning_profile_node_active = []
|
77
80
|
planning_profile_node_reactive = []
|
81
|
+
yearly_date_time = []
|
78
82
|
yearly_profile_node_active = []
|
79
83
|
yearly_profile_node_reactive = []
|
80
84
|
nodes_per_bldg, area, medium_voltage = av_peak_cons_per_building_type(folder['building_types'])
|
@@ -106,19 +110,23 @@ module URBANopt
|
|
106
110
|
else
|
107
111
|
voltage_default, phases = voltage_values(peak_active_power_cons / @power_factor)
|
108
112
|
end
|
109
|
-
|
113
|
+
|
110
114
|
for k in 0..profiles[:planning_profile_cust_active].length - 1
|
115
|
+
planning_date_time[k]=profiles[:planning_date_time][k]
|
111
116
|
planning_profile_node_active[k] = ((profiles[:planning_profile_cust_active][k]) / nodes_per_bldg).round(2)
|
112
117
|
planning_profile_node_reactive[k] = ((profiles[:planning_profile_cust_reactive][k]) / nodes_per_bldg).round(2)
|
113
118
|
end
|
114
119
|
for k in 0..profiles[:yearly_profile_cust_active].length - 1
|
120
|
+
yearly_date_time[k]=profiles[:yearly_date_time][k]
|
115
121
|
yearly_profile_node_active[k] = ((profiles[:yearly_profile_cust_active][k]) / nodes_per_bldg).round(2)
|
116
122
|
yearly_profile_node_reactive[k] = ((profiles[:yearly_profile_cust_reactive][k]) / nodes_per_bldg).round(2)
|
117
123
|
end
|
118
124
|
@customers.push([coordinates, voltage_default, peak_active_power_cons, peak_reactive_power_cons, phases])
|
119
125
|
@customers_ext.push([coordinates, voltage_default, peak_active_power_cons, peak_reactive_power_cons, phases, area, height, (single_values[:energy] / nodes_per_bldg).round(2), peak_active_power_cons, peak_reactive_power_cons, users])
|
126
|
+
@profile_date_time.push([planning_date_time])
|
120
127
|
@profile_customer_q.push([id, 24, planning_profile_node_reactive])
|
121
128
|
@profile_customer_p.push([id, 24, planning_profile_node_active])
|
129
|
+
@profile_date_time_ext.push([yearly_date_time])
|
122
130
|
@profile_customer_p_ext.push([id, 8760, yearly_profile_node_active])
|
123
131
|
@profile_customer_q_ext.push([id, 8760, yearly_profile_node_reactive])
|
124
132
|
|
@@ -131,8 +139,10 @@ module URBANopt
|
|
131
139
|
voltage_default, phases = voltage_values(single_values[:peak_active_power_cons] / @power_factor * 0.9) # applying safety factor
|
132
140
|
@customers.push([building_map, voltage_default, single_values[:peak_active_power_cons], single_values[:peak_reactive_power_cons], phases])
|
133
141
|
@customers_ext.push([building_map, voltage_default, single_values[:peak_active_power_cons], single_values[:peak_reactive_power_cons], phases, area, height, (single_values[:energy]).round(2), single_values[:peak_active_power_cons], single_values[:peak_reactive_power_cons], users])
|
142
|
+
@profile_date_time.push([profiles[:planning_date_time]])
|
134
143
|
@profile_customer_q.push([id, 24, profiles[:planning_profile_cust_reactive]])
|
135
144
|
@profile_customer_p.push([id, 24, profiles[:planning_profile_cust_active]])
|
145
|
+
@profile_date_time_ext.push([profiles[:yearly_date_time]])
|
136
146
|
@profile_customer_p_ext.push([id, 8760, profiles[:yearly_profile_cust_active]])
|
137
147
|
@profile_customer_q_ext.push([id, 8760, profiles[:yearly_profile_cust_reactive]])
|
138
148
|
end
|
@@ -234,10 +244,12 @@ module URBANopt
|
|
234
244
|
# content = CSV.foreach(csv_feature_report, headers: true) do |power|
|
235
245
|
CSV.foreach(csv_feature_report, headers: true) do |power|
|
236
246
|
@power_factor = power['Electricity:Facility Power(kW)'].to_f / power['Electricity:Facility Apparent Power(kVA)'].to_f
|
247
|
+
profiles[:yearly_date_time].push(power['Datetime'])
|
237
248
|
profiles[:yearly_profile_cust_active].push(power['Electricity:Facility Power(kW)'].to_f)
|
238
249
|
profiles[:yearly_profile_cust_reactive].push(profiles[:yearly_profile_cust_active][k] * Math.tan(Math.acos(@power_factor)))
|
239
250
|
single_values[:energy] += power['REopt:Electricity:Load:Total(kw)'].to_f # calculating the yearly energy consumed by each feature
|
240
251
|
if k >= profile_start_max && k <= profile_start_max + hours
|
252
|
+
profiles[:planning_date_time].push(power['Datetime'])
|
241
253
|
profiles[:planning_profile_cust_active].push(power['Electricity:Facility Power(kW)'].to_f)
|
242
254
|
if power['Electricity:Facility Power(kW)'].to_f > single_values[:peak_active_power_cons]
|
243
255
|
single_values[:peak_active_power_cons] = power['Electricity:Facility Power(kW)'].to_f
|
@@ -210,7 +210,11 @@ module URBANopt
|
|
210
210
|
street_coordinates[street_number] = each_street
|
211
211
|
street_number += 1
|
212
212
|
elsif street['geometry']['type'] == 'Polygon' && street['properties']['type'] == 'Building' && scenario_features.include?(street['properties']['id'])
|
213
|
-
|
213
|
+
puts "------ processing Building #{ street['properties']['name']} --------"
|
214
|
+
# this loop goes through each polygon and assign it a building and is not correct for buildings with
|
215
|
+
# inner courtyards (it would create 2 buildings)
|
216
|
+
# This assumes the outward footprint is the first polygon -- we only process the first one here
|
217
|
+
for k in 0..0
|
214
218
|
h = 0 # index representing number of nodes for each single building
|
215
219
|
building = [] # array containing every building node coordinates and id of 1 building
|
216
220
|
for j in 0..street['geometry']['coordinates'][k].length - 1
|
@@ -258,6 +258,10 @@ module URBANopt
|
|
258
258
|
File.open(File.join(@run_dir, @rnm_dirname, 'customers_ext.txt'), 'w+') do |g|
|
259
259
|
g.puts(prosumers.customers_ext.map { |w| w.join(';') })
|
260
260
|
end
|
261
|
+
File.open(File.join(@run_dir, @rnm_dirname, 'timestamps.csv'), 'w+') do |g|
|
262
|
+
g.puts("Datetime\n")
|
263
|
+
g.puts(prosumers.profile_date_time[0].map { |w| w.join("\n") })
|
264
|
+
end
|
261
265
|
File.open(File.join(@run_dir, @rnm_dirname, 'cust_profile_p.txt'), 'w+') do |g|
|
262
266
|
g.puts(prosumers.profile_customer_p.map { |w| w.join(';') })
|
263
267
|
end
|
@@ -267,6 +271,10 @@ module URBANopt
|
|
267
271
|
# CSV.open(File.join(@run_dir, @rnm_dirname, "cust_profile_q_extendido.csv"), "w") do |csv|
|
268
272
|
# csv << [prosumers.profile_customer_q_ext]
|
269
273
|
# end
|
274
|
+
File.open(File.join(@run_dir, @rnm_dirname, 'timestamps_extendido.csv'), 'w+') do |g|
|
275
|
+
g.puts("Datetime\n")
|
276
|
+
g.puts(prosumers.profile_date_time_ext[0].map { |w| w.join("\n") })
|
277
|
+
end
|
270
278
|
File.open(File.join(@run_dir, @rnm_dirname, 'cust_profile_q_extendido.txt'), 'w+') do |g|
|
271
279
|
g.puts(prosumers.profile_customer_q_ext.map { |w| w.join(';') })
|
272
280
|
end
|
@@ -289,6 +297,7 @@ module URBANopt
|
|
289
297
|
File.open(File.join(@run_dir, @rnm_dirname, 'gen_profile_p_extendido.txt'), 'w+') do |g|
|
290
298
|
g.puts(prosumers.profile_dg_p_extended.map { |w| w.join(';') })
|
291
299
|
end
|
300
|
+
ficheros_entrada_inc.push('Timestamps;timestamps.csv;timestamps_extendido.csv')
|
292
301
|
ficheros_entrada_inc.push('CClienteGreenfield;customers_ext.txt;cust_profile_p.txt;cust_profile_q.txt;cust_profile_p_extendido.txt;cust_profile_q_extendido.txt')
|
293
302
|
ficheros_entrada_inc.push('CGeneradorGreenfield;generators.txt;gen_profile_p.txt;gen_profile_q.txt;gen_profile_p_extendido.txt;gen_profile_q_extendido.txt')
|
294
303
|
ficheros_entrada_inc.push('END')
|
@@ -302,18 +311,27 @@ module URBANopt
|
|
302
311
|
File.open(File.join(@run_dir, @rnm_dirname, 'customers_ext.txt'), 'w+') do |g|
|
303
312
|
g.puts(consumers.customers_ext.map { |w| w.join(';') })
|
304
313
|
end
|
314
|
+
File.open(File.join(@run_dir, @rnm_dirname, 'timestamps.csv'), 'w+') do |g|
|
315
|
+
g.puts("Datetime\n")
|
316
|
+
g.puts(consumers.profile_date_time[0].map { |w| w.join("\n") })
|
317
|
+
end
|
305
318
|
File.open(File.join(@run_dir, @rnm_dirname, 'cust_profile_p.txt'), 'w+') do |g|
|
306
319
|
g.puts(consumers.profile_customer_p.map { |w| w.join(';') })
|
307
320
|
end
|
308
321
|
File.open(File.join(@run_dir, @rnm_dirname, 'cust_profile_q.txt'), 'w+') do |g|
|
309
322
|
g.puts(consumers.profile_customer_q.map { |w| w.join(';') })
|
310
323
|
end
|
324
|
+
File.open(File.join(@run_dir, @rnm_dirname, 'timestamps_extendido.csv'), 'w+') do |g|
|
325
|
+
g.puts("Datetime\n")
|
326
|
+
g.puts(consumers.profile_date_time_ext[0].map { |w| w.join("\n") })
|
327
|
+
end
|
311
328
|
File.open(File.join(@run_dir, @rnm_dirname, 'cust_profile_q_extendido.txt'), 'w+') do |g|
|
312
329
|
g.puts(consumers.profile_customer_q_ext.map { |w| w.join(';') })
|
313
330
|
end
|
314
331
|
File.open(File.join(@run_dir, @rnm_dirname, 'cust_profile_p_extendido.txt'), 'w+') do |g|
|
315
332
|
g.puts(consumers.profile_customer_p_ext.map { |w| w.join(';') })
|
316
333
|
end
|
334
|
+
ficheros_entrada_inc.push('Timestamps;timestamps.csv;timestamps_extendido.csv')
|
317
335
|
ficheros_entrada_inc.push('CClienteGreenfield;customers_ext.txt;cust_profile_p.txt;cust_profile_q.txt;cust_profile_p_extendido.txt;cust_profile_q_extendido.txt')
|
318
336
|
ficheros_entrada_inc.push('END')
|
319
337
|
File.open(File.join(@run_dir, @rnm_dirname, 'ficheros_entrada_inc.txt'), 'w+') do |g|
|
@@ -45,7 +45,7 @@ module URBANopt
|
|
45
45
|
# creating a class that creates the consumers input required by the RNM-US model,
|
46
46
|
# according to their geographic location, energy consumption and peak demand, and power consumption profiles
|
47
47
|
class Prosumers
|
48
|
-
attr_accessor :customers, :customers_ext, :profile_customer_p, :profile_customer_q, :profile_customer_p_ext, :profile_customer_q_ext, :dg, :dg_profile_p, :dg_profile_q, :profile_dg_p_extended, :profile_dg_q_extended, :power_factor
|
48
|
+
attr_accessor :customers, :customers_ext, :profile_customer_p, :profile_customer_q, :profile_customer_p_ext, :profile_customer_q_ext, :dg, :dg_profile_p, :dg_profile_q, :profile_dg_p_extended, :profile_dg_q_extended, :power_factor, :profile_date_time, :profile_date_time_ext
|
49
49
|
|
50
50
|
# initializing all the attributes to build the inputs files required by the RNM-US model
|
51
51
|
def initialize(reopt, only_lv_consumers = false, max_num_lv_nodes, average_building_peak_catalog_path, lv_limit)
|
@@ -55,8 +55,10 @@ module URBANopt
|
|
55
55
|
@max_num_lv_nodes = max_num_lv_nodes
|
56
56
|
@customers = []
|
57
57
|
@customers_ext = []
|
58
|
+
@profile_date_time = []
|
58
59
|
@profile_customer_p = []
|
59
60
|
@profile_customer_q = []
|
61
|
+
@profile_date_time_ext = []
|
60
62
|
@profile_customer_p_ext = []
|
61
63
|
@profile_customer_q_ext = []
|
62
64
|
@dg = []
|
@@ -99,16 +101,20 @@ module URBANopt
|
|
99
101
|
end
|
100
102
|
@customers.push([building_map, id, voltage_default, single_values[:peak_active_power_cons], single_values[:peak_reactive_power_cons], phases])
|
101
103
|
@customers_ext.push([building_map, id, voltage_default, single_values[:peak_active_power_cons], single_values[:peak_reactive_power_cons], phases, area, height, (single_values[:energy]).round(2), single_values[:peak_active_power_cons], single_values[:peak_reactive_power_cons], users])
|
104
|
+
@profile_date_time.push([profiles_planning[:planning_date_time]])
|
102
105
|
@profile_customer_q.push([id, 48, profiles_planning[:planning_profile_cust_reactive]])
|
103
106
|
@profile_customer_p.push([id, 48, profiles_planning[:planning_profile_cust_active]])
|
107
|
+
@profile_date_time_ext.push([profiles[:yearly_date_time]])
|
104
108
|
@profile_customer_p_ext.push([id, 8760, profiles[:yearly_profile_cust_active]])
|
105
109
|
@profile_customer_q_ext.push([id, 8760, profiles[:yearly_profile_cust_reactive]])
|
106
110
|
|
107
111
|
if !der_capacity[:storage].nil? && der_capacity[:storage] > 0
|
108
112
|
@customers.push([building_map, id_batt, voltage_default, single_values[:peak_active_power_storage], single_values[:peak_reactive_power_storage], phases])
|
109
113
|
@customers_ext.push([building_map, id_batt, voltage_default, single_values[:peak_active_power_storage], single_values[:peak_reactive_power_storage], phases, area, height, (single_values[:energy]).round(2), single_values[:peak_active_power_storage], single_values[:peak_reactive_power_storage], users])
|
114
|
+
@profile_date_time.push([profiles_planning[:planning_date_time]])
|
110
115
|
@profile_customer_q.push([id_batt, 48, profiles_planning[:planning_profile_storage_reactive]])
|
111
116
|
@profile_customer_p.push([id_batt, 48, profiles_planning[:planning_profile_storage_active]])
|
117
|
+
@profile_date_time_ext.push([profiles[:yearly_date_time]])
|
112
118
|
@profile_customer_p_ext.push([id_batt, 8760, profiles[:yearly_profile_storage_active]])
|
113
119
|
@profile_customer_q_ext.push([id_batt, 8760, profiles[:yearly_profile_storage_reactive]])
|
114
120
|
end
|
@@ -129,8 +135,10 @@ module URBANopt
|
|
129
135
|
# among the nodes of each building
|
130
136
|
def construct_prosumer_lv(nodes_per_bldg = 0, profiles, profiles_planning, single_values, building_map, building_nodes, area, height, users, der_capacity)
|
131
137
|
# the default variables are defined (i.e. type and rurality type)
|
138
|
+
planning_date_time = []
|
132
139
|
planning_profile_node_active = []
|
133
140
|
planning_profile_node_reactive = []
|
141
|
+
yearly_date_time = []
|
134
142
|
yearly_profile_node_active = []
|
135
143
|
yearly_profile_node_reactive = []
|
136
144
|
closest_node = building_map[3].split('_')[1].to_i # refers to the closest node of the building in consideration to the street
|
@@ -159,17 +167,21 @@ module URBANopt
|
|
159
167
|
peak_reactive_power_cons = (single_values[:peak_reactive_power_cons] / nodes_consumers).round(2)
|
160
168
|
voltage_default, phases = voltage_values(peak_active_power_cons / @power_factor)
|
161
169
|
for k in 0..profiles_planning[:planning_profile_cust_active].length - 1
|
170
|
+
planning_date_time[k]=profiles_planning[:planning_date_time][k]
|
162
171
|
planning_profile_node_active[k] = (profiles_planning[:planning_profile_cust_active][k] / nodes_consumers).round(2)
|
163
172
|
planning_profile_node_reactive[k] = (profiles_planning[:planning_profile_cust_reactive][k] / nodes_consumers).round(2)
|
164
173
|
end
|
165
174
|
for k in 0..profiles[:yearly_profile_cust_active].length - 1
|
175
|
+
yearly_date_time[k]=profiles[:yearly_date_time][k]
|
166
176
|
yearly_profile_node_active[k] = (profiles[:yearly_profile_cust_active][k] / nodes_consumers).round(2)
|
167
177
|
yearly_profile_node_reactive[k] = (profiles[:yearly_profile_cust_reactive][k] / nodes_consumers).round(2)
|
168
178
|
end
|
169
179
|
@customers.push([coordinates, id, voltage_default, peak_active_power_cons, peak_reactive_power_cons, phases])
|
170
180
|
@customers_ext.push([coordinates, id, voltage_default, peak_active_power_cons, peak_reactive_power_cons, phases, area, height, (single_values[:energy] / nodes_consumers).round(2), peak_active_power_cons, peak_reactive_power_cons, users])
|
181
|
+
@profile_date_time.push([planning_date_time])
|
171
182
|
@profile_customer_q.push([id, 48, planning_profile_node_reactive])
|
172
183
|
@profile_customer_p.push([id, 48, planning_profile_node_active])
|
184
|
+
@profile_date_time_ext.push([yearly_date_time])
|
173
185
|
@profile_customer_p_ext.push([id, 8760, yearly_profile_node_active])
|
174
186
|
@profile_customer_q_ext.push([id, 8760, yearly_profile_node_reactive])
|
175
187
|
else
|
@@ -186,8 +198,10 @@ module URBANopt
|
|
186
198
|
if !der_capacity[:storage].nil? && der_capacity[:storage] > 0
|
187
199
|
@customers.push([coordinates, id_batt, voltage_default, single_values[:peak_active_power_storage], single_values[:peak_reactive_power_storage], phases])
|
188
200
|
@customers_ext.push([coordinates, id_batt, voltage_default, single_values[:peak_active_power_storage], single_values[:peak_reactive_power_storage], phases, area, height, (single_values[:energy]).round(2), single_values[:peak_active_power_storage], single_values[:peak_reactive_power_storage], users])
|
201
|
+
@profile_date_time.push([profiles[:planning_date_time]])
|
189
202
|
@profile_customer_q.push([id_batt, 48, profiles_planning[:planning_profile_storage_reactive]])
|
190
203
|
@profile_customer_p.push([id_batt, 48, profiles_planning[:planning_profile_storage_active]])
|
204
|
+
@profile_date_time_ext.push([profiles[:yearly_date_time]])
|
191
205
|
@profile_customer_p_ext.push([id_batt, 8760, profiles[:yearly_profile_storage_active]])
|
192
206
|
@profile_customer_q_ext.push([id_batt, 8760, profiles[:yearly_profile_storage_reactive]])
|
193
207
|
end
|
@@ -285,6 +299,7 @@ module URBANopt
|
|
285
299
|
|
286
300
|
# method to order profiles consistently
|
287
301
|
def profiles_planning_creation(profiles_planning, power, single_values, i, hours, power_factor)
|
302
|
+
profiles_planning[:planning_date_time][i]=power['Datetime']
|
288
303
|
profiles_planning[:planning_profile_cust_active][i] = power['REopt:Electricity:Load:Total(kw)'].to_f
|
289
304
|
profiles_planning[:planning_profile_storage_active][i] = power['REopt:Electricity:Grid:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:Generator:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:PV:ToBattery(kw)'].to_f + power['REopt:ElectricityProduced:Wind:ToBattery(kw)'].to_f - power['REopt:Electricity:Storage:ToLoad(kw)'].to_f - power['REopt:Electricity:Storage:ToGrid(kw)'].to_f
|
290
305
|
profiles_planning[:planning_profile_dg_active][i] = power['REopt:ElectricityProduced:Total(kw)'].to_f
|
@@ -341,6 +356,7 @@ module URBANopt
|
|
341
356
|
max_peak = 0
|
342
357
|
CSV.foreach(csv_feature_report, headers: true) do |power|
|
343
358
|
@power_factor = power['Electricity:Facility Power(kW)'].to_f / power['Electricity:Facility Apparent Power(kVA)'].to_f
|
359
|
+
profiles[:yearly_date_time].push(power['Datetime'])
|
344
360
|
profiles[:yearly_profile_cust_active].push(power['REopt:Electricity:Load:Total(kw)'].to_f)
|
345
361
|
profiles[:yearly_profile_cust_reactive].push(profiles[:yearly_profile_cust_active][k] * Math.tan(Math.acos(@power_factor)))
|
346
362
|
profiles[:yearly_profile_dg_active].push(power['REopt:ElectricityProduced:Total(kw)'].to_f)
|