urbanopt-reporting 0.3.2 → 0.3.3
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 +4 -4
- data/CHANGELOG.md +10 -0
- data/lib/measures/default_feature_reports/measure.rb +41 -4
- data/lib/measures/export_modelica_loads/LICENSE.md +27 -0
- data/lib/measures/export_modelica_loads/README.md +26 -0
- data/lib/measures/export_modelica_loads/README.md.erb +42 -0
- data/lib/measures/export_modelica_loads/docs/.gitkeep +0 -0
- data/lib/measures/export_modelica_loads/measure.rb +367 -0
- data/lib/measures/export_modelica_loads/measure.xml +92 -0
- data/lib/measures/export_modelica_loads/resources/report.html.in +13 -0
- data/lib/measures/export_time_series_modelica/LICENSE.md +1 -0
- data/lib/measures/export_time_series_modelica/README.md +59 -0
- data/lib/measures/export_time_series_modelica/README.md.erb +42 -0
- data/lib/measures/export_time_series_modelica/docs/.gitkeep +0 -0
- data/lib/measures/export_time_series_modelica/measure.rb +433 -0
- data/lib/measures/export_time_series_modelica/measure.xml +147 -0
- data/lib/measures/export_time_series_modelica/resources/os_lib_helper_methods.rb +399 -0
- data/lib/measures/export_time_series_modelica/resources/report.html.in +13 -0
- data/lib/urbanopt/reporting/default_reports/feature_report.rb +48 -0
- data/lib/urbanopt/reporting/default_reports/reporting_period.rb +14 -2
- data/lib/urbanopt/reporting/default_reports/scenario_report.rb +7 -4
- data/lib/urbanopt/reporting/default_reports/schema/scenario_schema.json +25 -13
- data/lib/urbanopt/reporting/version.rb +1 -1
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e65d30b14cf3916be1251dd1092dd74f222e737a0c9d6e6278123f4c6101b503
|
4
|
+
data.tar.gz: 4fa5c62490c7dcc883c5308f8f31547090f1eef01914b0e3e54093e218f09bf9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e0fdd09a89d58be74b9506fc9904253b0121f0d117041799baa500f3a0b1ab07f907609e0495486943751203c1f8683cc564ffcb038cb944721e1751e79c6f3
|
7
|
+
data.tar.gz: 067ab1a1d6506c22933a1e4e9e09dac96ecc925e4d3d931bfc51bd6853faf99e658c4b578f6689d9e34cc5ff5533c77dab1d8533ccef1384963ad7a85ac627e1
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# URBANopt Reporting Gem
|
2
2
|
|
3
|
+
## Version 0.3.3
|
4
|
+
|
5
|
+
Date Range: 12/09/20 - 01/13/21
|
6
|
+
|
7
|
+
- Fixed [#36]( https://github.com/urbanopt/urbanopt-reporting-gem/issues/36 ), Add reporting measure for district heating/cooling system mass flow rates
|
8
|
+
- Fixed [#37]( https://github.com/urbanopt/urbanopt-reporting-gem/issues/37 ), Add EUI to default report
|
9
|
+
- Fixed [#38]( https://github.com/urbanopt/urbanopt-reporting-gem/issues/38 ), Add better error handling around convert_units
|
10
|
+
- Fixed [#43]( https://github.com/urbanopt/urbanopt-reporting-gem/issues/43 ), Add available_roof_area calculation
|
11
|
+
- Fixed [#44]( https://github.com/urbanopt/urbanopt-reporting-gem/issues/44 ), Fix coordinates order
|
12
|
+
|
3
13
|
## Version 0.3.2
|
4
14
|
|
5
15
|
Date Range: 12/07/20 - 12/08/20
|
@@ -258,6 +258,11 @@ class DefaultFeatureReports < OpenStudio::Measure::ReportingMeasure
|
|
258
258
|
if value.nil?
|
259
259
|
return nil
|
260
260
|
end
|
261
|
+
if from_units.nil? || to_units.nil?
|
262
|
+
@runner.registerError("Cannot convert units...from_units: #{from_units} or to_units: #{to_units} left blank.")
|
263
|
+
return nil
|
264
|
+
end
|
265
|
+
|
261
266
|
# apply unit conversion
|
262
267
|
value_converted = OpenStudio.convert(value, from_units, to_units)
|
263
268
|
if value_converted.is_initialized
|
@@ -353,10 +358,10 @@ class DefaultFeatureReports < OpenStudio::Measure::ReportingMeasure
|
|
353
358
|
##
|
354
359
|
|
355
360
|
if feature_location.include? '['
|
356
|
-
# get latitude from feature_location
|
357
|
-
latitude = (feature_location.split(',')[0].delete! '[]').to_f
|
358
361
|
# get longitude from feature_location
|
359
|
-
longitude = (feature_location.split(',')[
|
362
|
+
longitude = (feature_location.split(',')[0].delete! '[]').to_f
|
363
|
+
# get latitude from feature_location
|
364
|
+
latitude = (feature_location.split(',')[1].delete! '[]').to_f
|
360
365
|
# latitude
|
361
366
|
feature_report.location.latitude_deg = latitude
|
362
367
|
# longitude
|
@@ -495,7 +500,23 @@ class DefaultFeatureReports < OpenStudio::Measure::ReportingMeasure
|
|
495
500
|
total_roof_area += surface.netArea
|
496
501
|
end
|
497
502
|
end
|
498
|
-
|
503
|
+
|
504
|
+
total_roof_area_sqft = convert_units(total_roof_area, 'm^2', 'ft^2')
|
505
|
+
feature_report.program.roof_area_sqft[:total_roof_area_sqft] = total_roof_area_sqft
|
506
|
+
|
507
|
+
# available_roof_area_sqft
|
508
|
+
# RK: a more robust method should be implemented to find the available_roof_area
|
509
|
+
# assign available roof area to be a percentage of the total roof area
|
510
|
+
|
511
|
+
if building_types[0][:building_type].include? 'Single-Family Detached'
|
512
|
+
feature_report.program.roof_area_sqft[:available_roof_area_sqft] = 0.45 * total_roof_area_sqft
|
513
|
+
else
|
514
|
+
feature_report.program.roof_area_sqft[:available_roof_area_sqft] = 0.75 * total_roof_area_sqft
|
515
|
+
end
|
516
|
+
|
517
|
+
# RK: Temporary solution: assign available roof area to be equal to total roof area
|
518
|
+
#feature_report.program.roof_area_sqft[:available_roof_area_sqft] = total_roof_area_sqft
|
519
|
+
|
499
520
|
|
500
521
|
# orientation
|
501
522
|
# RK: a more robust method should be implemented to find orientation(finding main axis of the building using aspect ratio)
|
@@ -581,6 +602,22 @@ class DefaultFeatureReports < OpenStudio::Measure::ReportingMeasure
|
|
581
602
|
total_source_energy = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Site and Source Energy' AND RowName='Total Source Energy' AND ColumnName='Total Energy'")
|
582
603
|
feature_report.reporting_periods[0].total_source_energy_kwh = convert_units(total_source_energy, 'GJ', 'kWh')
|
583
604
|
|
605
|
+
# EUI is only valid with a full year of energy data
|
606
|
+
if begin_month == 1 && begin_day_of_month == 1 && end_month == 12 && end_day_of_month == 31
|
607
|
+
# calculate site EUI
|
608
|
+
site_EUI_kwh_per_m2 = feature_report.reporting_periods[0].total_site_energy_kwh / floor_area
|
609
|
+
site_EUI_kbtu_per_ft2 = convert_units(total_site_energy, 'GJ', 'kBtu') / feature_report.program.floor_area_sqft
|
610
|
+
# add site EUI to feature report
|
611
|
+
feature_report.reporting_periods[0].site_EUI_kwh_per_m2 = site_EUI_kwh_per_m2
|
612
|
+
feature_report.reporting_periods[0].site_EUI_kbtu_per_ft2 = site_EUI_kbtu_per_ft2
|
613
|
+
# calculate source EUI
|
614
|
+
source_EUI_kwh_per_m2 = feature_report.reporting_periods[0].total_source_energy_kwh / floor_area
|
615
|
+
source_EUI_kbtu_per_ft2 = convert_units(total_source_energy, 'GJ', 'kBtu') / feature_report.program.floor_area_sqft
|
616
|
+
# add source EUI to feature report
|
617
|
+
feature_report.reporting_periods[0].source_EUI_kwh_per_m2 = source_EUI_kwh_per_m2
|
618
|
+
feature_report.reporting_periods[0].source_EUI_kbtu_per_ft2 = source_EUI_kbtu_per_ft2
|
619
|
+
end
|
620
|
+
|
584
621
|
# net_site_energy
|
585
622
|
net_site_energy = sql_query(runner, sql_file, 'AnnualBuildingUtilityPerformanceSummary', "TableName='Site and Source Energy' AND RowName='Net Site Energy' AND ColumnName='Total Energy'")
|
586
623
|
feature_report.reporting_periods[0].net_site_energy_kwh = convert_units(net_site_energy, 'GJ', 'kWh')
|
@@ -0,0 +1,27 @@
|
|
1
|
+
OpenStudio(R), Copyright (c) 2008-2020, Alliance for Sustainable Energy, LLC. All rights reserved.
|
2
|
+
|
3
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted
|
4
|
+
provided that the following conditions are met:
|
5
|
+
|
6
|
+
(1) Redistributions of source code must retain the above copyright notice, this list of conditions
|
7
|
+
and the following disclaimer.
|
8
|
+
|
9
|
+
(2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions
|
10
|
+
and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
11
|
+
|
12
|
+
(3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse
|
13
|
+
or promote products derived from this software without specific prior written permission from the
|
14
|
+
respective party.
|
15
|
+
|
16
|
+
(4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other
|
17
|
+
derivative works may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar
|
18
|
+
designation without specific prior written permission from Alliance for Sustainable Energy, LLC.
|
19
|
+
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
21
|
+
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
22
|
+
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER, THE UNITED STATES GOVERNMENT,
|
23
|
+
OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
25
|
+
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
26
|
+
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
27
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
###### (Automatically generated documentation)
|
4
|
+
|
5
|
+
# Export Modelica Loads
|
6
|
+
|
7
|
+
## Description
|
8
|
+
Use the results from the EnergyPlus simulation to generate a load file for use in Modelica. This will create a MOS and a CSV file of the heating, cooling, and hot water loads.
|
9
|
+
|
10
|
+
## Modeler Description
|
11
|
+
|
12
|
+
|
13
|
+
## Measure Type
|
14
|
+
ReportingMeasure
|
15
|
+
|
16
|
+
## Taxonomy
|
17
|
+
|
18
|
+
|
19
|
+
## Arguments
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
This measure does not have any user arguments
|
25
|
+
|
26
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
<%#= README.md.erb is used to auto-generate README.md. %>
|
2
|
+
<%#= To manually maintain README.md throw away README.md.erb and manually edit README.md %>
|
3
|
+
###### (Automatically generated documentation)
|
4
|
+
|
5
|
+
# <%= name %>
|
6
|
+
|
7
|
+
## Description
|
8
|
+
<%= description %>
|
9
|
+
|
10
|
+
## Modeler Description
|
11
|
+
<%= modelerDescription %>
|
12
|
+
|
13
|
+
## Measure Type
|
14
|
+
<%= measureType %>
|
15
|
+
|
16
|
+
## Taxonomy
|
17
|
+
<%= taxonomy %>
|
18
|
+
|
19
|
+
## Arguments
|
20
|
+
|
21
|
+
<% arguments.each do |argument| %>
|
22
|
+
### <%= argument[:display_name] %>
|
23
|
+
<%= argument[:description] %>
|
24
|
+
**Name:** <%= argument[:name] %>,
|
25
|
+
**Type:** <%= argument[:type] %>,
|
26
|
+
**Units:** <%= argument[:units] %>,
|
27
|
+
**Required:** <%= argument[:required] %>,
|
28
|
+
**Model Dependent:** <%= argument[:model_dependent] %>
|
29
|
+
<% end %>
|
30
|
+
|
31
|
+
<% if arguments.size == 0 %>
|
32
|
+
<%= "This measure does not have any user arguments" %>
|
33
|
+
<% end %>
|
34
|
+
|
35
|
+
<% if outputs.size > 0 %>
|
36
|
+
## Outputs
|
37
|
+
<% output_names = [] %>
|
38
|
+
<% outputs.each do |output| %>
|
39
|
+
<% output_names << output[:display_name] %>
|
40
|
+
<% end %>
|
41
|
+
<%= output_names.join(", ") %>
|
42
|
+
<% end %>
|
File without changes
|
@@ -0,0 +1,367 @@
|
|
1
|
+
# *******************************************************************************
|
2
|
+
# OpenStudio(R), Copyright (c) 2008-2020, Alliance for Sustainable Energy, LLC.
|
3
|
+
# All rights reserved.
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are met:
|
6
|
+
#
|
7
|
+
# (1) Redistributions of source code must retain the above copyright notice,
|
8
|
+
# this list of conditions and the following disclaimer.
|
9
|
+
#
|
10
|
+
# (2) Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
#
|
14
|
+
# (3) Neither the name of the copyright holder nor the names of any contributors
|
15
|
+
# may be used to endorse or promote products derived from this software without
|
16
|
+
# specific prior written permission from the respective party.
|
17
|
+
#
|
18
|
+
# (4) Other than as required in clauses (1) and (2), distributions in any form
|
19
|
+
# of modifications or other derivative works may not use the "OpenStudio"
|
20
|
+
# trademark, "OS", "os", or any other confusingly similar designation without
|
21
|
+
# specific prior written permission from Alliance for Sustainable Energy, LLC.
|
22
|
+
#
|
23
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
|
24
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
25
|
+
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
26
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
|
27
|
+
# UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
|
28
|
+
# THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
29
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
30
|
+
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
31
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
32
|
+
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
33
|
+
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
34
|
+
# *******************************************************************************
|
35
|
+
|
36
|
+
require 'erb'
|
37
|
+
|
38
|
+
# start the measure
|
39
|
+
class ExportModelicaLoads < OpenStudio::Measure::ReportingMeasure
|
40
|
+
# human readable name
|
41
|
+
def name
|
42
|
+
# Measure name should be the title case of the class name.
|
43
|
+
return 'Export Modelica Loads'
|
44
|
+
end
|
45
|
+
|
46
|
+
def description
|
47
|
+
return 'Use the results from the EnergyPlus simulation to generate a load file for use in Modelica. This will create a MOS and a CSV file of the heating, cooling, and hot water loads.'
|
48
|
+
end
|
49
|
+
|
50
|
+
def modeler_description
|
51
|
+
return ''
|
52
|
+
end
|
53
|
+
|
54
|
+
def log(str)
|
55
|
+
puts "#{Time.now}: #{str}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def arguments(_model)
|
59
|
+
args = OpenStudio::Measure::OSArgumentVector.new
|
60
|
+
|
61
|
+
# this measure does not require any user arguments, return an empty list
|
62
|
+
return args
|
63
|
+
end
|
64
|
+
|
65
|
+
# return a vector of IdfObject's to request EnergyPlus objects needed by the run method
|
66
|
+
def energyPlusOutputRequests(runner, user_arguments)
|
67
|
+
super(runner, user_arguments)
|
68
|
+
|
69
|
+
result = OpenStudio::IdfObjectVector.new
|
70
|
+
|
71
|
+
# To use the built-in error checking we need the model...
|
72
|
+
# get the last model and sql file
|
73
|
+
model = runner.lastOpenStudioModel
|
74
|
+
if model.empty?
|
75
|
+
runner.registerError('Cannot find last model.')
|
76
|
+
return false
|
77
|
+
end
|
78
|
+
model = model.get
|
79
|
+
|
80
|
+
# use the built-in error checking
|
81
|
+
if !runner.validateUserArguments(arguments(model), user_arguments)
|
82
|
+
return false
|
83
|
+
end
|
84
|
+
|
85
|
+
result << OpenStudio::IdfObject.load('Output:Variable,,Site Mains Water Temperature,hourly;').get
|
86
|
+
result << OpenStudio::IdfObject.load('Output:Variable,,Site Outdoor Air Drybulb Temperature,hourly;').get
|
87
|
+
result << OpenStudio::IdfObject.load('Output:Variable,,Site Outdoor Air Relative Humidity,hourly;').get
|
88
|
+
result << OpenStudio::IdfObject.load('Output:Meter,Cooling:Electricity,hourly;').get
|
89
|
+
result << OpenStudio::IdfObject.load('Output:Meter,Heating:Electricity,hourly;').get
|
90
|
+
result << OpenStudio::IdfObject.load('Output:Meter,Heating:Gas,hourly;').get
|
91
|
+
result << OpenStudio::IdfObject.load('Output:Meter,InteriorLights:Electricity,hourly;').get
|
92
|
+
result << OpenStudio::IdfObject.load('Output:Meter,Fans:Electricity,hourly;').get
|
93
|
+
result << OpenStudio::IdfObject.load('Output:Meter,InteriorEquipment:Electricity,hourly;').get # Joules
|
94
|
+
result << OpenStudio::IdfObject.load('Output:Meter,ExteriorLighting:Electricity,hourly;').get # Joules
|
95
|
+
result << OpenStudio::IdfObject.load('Output:Meter,Electricity:Facility,hourly;').get # Joules
|
96
|
+
result << OpenStudio::IdfObject.load('Output:Meter,Gas:Facility,hourly;').get # Joules
|
97
|
+
result << OpenStudio::IdfObject.load('Output:Meter,Heating:EnergyTransfer,hourly;').get # Joules
|
98
|
+
result << OpenStudio::IdfObject.load('Output:Meter,WaterSystems:EnergyTransfer,hourly;').get # Joules
|
99
|
+
# these variables are used for the modelica export.
|
100
|
+
result << OpenStudio::IdfObject.load('Output:Variable,*,Zone Predicted Sensible Load to Setpoint Heat Transfer Rate,hourly;').get # watts according to e+
|
101
|
+
result << OpenStudio::IdfObject.load('Output:Variable,*,Water Heater Total Demand Heat Transfer Rate,hourly;').get # Watts
|
102
|
+
|
103
|
+
return result
|
104
|
+
end
|
105
|
+
|
106
|
+
def extract_timeseries_into_matrix(sqlfile, data, variable_name, key_value = nil, default_if_empty=0)
|
107
|
+
log "Executing query for #{variable_name}"
|
108
|
+
column_name = variable_name
|
109
|
+
if key_value
|
110
|
+
ts = sqlfile.timeSeries('RUN PERIOD 1', 'Hourly', variable_name, key_value)
|
111
|
+
# ts = sqlfile.timeSeries('RUN PERIOD 1', 'Zone Timestep', variable_name, key_value)
|
112
|
+
column_name += "_#{key_value}"
|
113
|
+
else
|
114
|
+
ts = sqlfile.timeSeries('RUN PERIOD 1', 'Hourly', variable_name)
|
115
|
+
# ts = sqlfile.timeSeries('RUN PERIOD 1', 'Zone Timestep', variable_name)
|
116
|
+
end
|
117
|
+
log 'Iterating over timeseries'
|
118
|
+
column = [column_name.delete(':').delete(' ')] # Set the header of the data to the variable name, removing : and spaces
|
119
|
+
|
120
|
+
if ts.empty?
|
121
|
+
log "No time series for #{variable_name}:#{key_value}... defaulting to #{default_if_empty}"
|
122
|
+
# needs to be data.size-1 since the column name is already stored above (+=)
|
123
|
+
column += [default_if_empty] * (data.size-1)
|
124
|
+
else
|
125
|
+
ts = ts.get if ts.respond_to?(:get)
|
126
|
+
ts = ts.first if ts.respond_to?(:first)
|
127
|
+
|
128
|
+
start = Time.now
|
129
|
+
# Iterating in OpenStudio can take up to 60 seconds with 10min data. The quick_proc takes 0.03 seconds.
|
130
|
+
# for i in 0..ts.values.size - 1
|
131
|
+
# log "... at #{i}" if i % 10000 == 0
|
132
|
+
# column << ts.values[i]
|
133
|
+
# end
|
134
|
+
|
135
|
+
quick_proc = ts.values.to_s.split(',')
|
136
|
+
|
137
|
+
# the first and last have some cleanup items because of the Vector method
|
138
|
+
quick_proc[0] = quick_proc[0].gsub(/^.*\(/, '')
|
139
|
+
quick_proc[-1] = quick_proc[-1].delete(')')
|
140
|
+
column += quick_proc
|
141
|
+
|
142
|
+
log "Took #{Time.now - start} to iterate"
|
143
|
+
end
|
144
|
+
|
145
|
+
log 'Appending column to data'
|
146
|
+
|
147
|
+
# append the data to the end of the rows
|
148
|
+
if column.size == data.size
|
149
|
+
data.each_index do |index|
|
150
|
+
data[index] << column[index]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
log "Finished extracting #{variable_name}"
|
155
|
+
end
|
156
|
+
|
157
|
+
def create_new_variable_sum(data, new_var_name, include_str, options=nil)
|
158
|
+
var_info = {
|
159
|
+
name: new_var_name,
|
160
|
+
var_indexes: []
|
161
|
+
}
|
162
|
+
data.each_with_index do |row, index|
|
163
|
+
if index.zero?
|
164
|
+
# Get the index of the columns to add
|
165
|
+
row.each do |c|
|
166
|
+
if c.include? include_str
|
167
|
+
var_info[:var_indexes] << row.index(c)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# add the new var to the header row
|
172
|
+
data[0] << var_info[:name]
|
173
|
+
else
|
174
|
+
# sum the values
|
175
|
+
sum = 0
|
176
|
+
var_info[:var_indexes].each do |var|
|
177
|
+
temp_v = row[var].to_f
|
178
|
+
if options.nil?
|
179
|
+
sum += temp_v
|
180
|
+
elsif options[:positive_only] && temp_v > 0
|
181
|
+
sum += temp_v
|
182
|
+
elsif options[:negative_only] && temp_v < 0
|
183
|
+
sum += temp_v
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# Also round the data here, because we don't need 10 decimals
|
188
|
+
data[index] << sum.round(1)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def run(runner, user_arguments)
|
194
|
+
super(runner, user_arguments)
|
195
|
+
|
196
|
+
# get the last model and sql file
|
197
|
+
model = runner.lastOpenStudioModel
|
198
|
+
if model.empty?
|
199
|
+
runner.registerError('Cannot find last model.')
|
200
|
+
return false
|
201
|
+
end
|
202
|
+
model = model.get
|
203
|
+
|
204
|
+
# use the built-in error checking
|
205
|
+
return false unless runner.validateUserArguments(arguments(model), user_arguments)
|
206
|
+
|
207
|
+
# get the last model and sql file
|
208
|
+
model = runner.lastOpenStudioModel
|
209
|
+
if model.empty?
|
210
|
+
runner.registerError('Cannot find last model.')
|
211
|
+
return false
|
212
|
+
end
|
213
|
+
model = model.get
|
214
|
+
|
215
|
+
sqlFile = runner.lastEnergyPlusSqlFile
|
216
|
+
if sqlFile.empty?
|
217
|
+
runner.registerError('Cannot find last sql file.')
|
218
|
+
return false
|
219
|
+
end
|
220
|
+
sqlFile = sqlFile.get
|
221
|
+
model.setSqlFile(sqlFile)
|
222
|
+
|
223
|
+
# create a new csv with the values and save to the reports directory.
|
224
|
+
# assumptions:
|
225
|
+
# - all the variables exist
|
226
|
+
# - data are the same length
|
227
|
+
|
228
|
+
# initialize the rows with the header
|
229
|
+
log 'Starting to process Timeseries data'
|
230
|
+
# Initial header row
|
231
|
+
rows = [
|
232
|
+
['Date Time', 'Month', 'Day', 'Day of Week', 'Hour', 'Minute', 'SecondsFromStart']
|
233
|
+
]
|
234
|
+
|
235
|
+
# just grab one of the variables to get the date/time stamps
|
236
|
+
# ts = sqlFile.timeSeries('RUN PERIOD 1', 'Zone Timestep', 'Cooling:Electricity')
|
237
|
+
ts = sqlFile.timeSeries('RUN PERIOD 1', 'Hourly', 'Cooling:Electricity')
|
238
|
+
unless ts.empty?
|
239
|
+
ts = ts.first
|
240
|
+
dt_base = nil
|
241
|
+
# Save off the date time values
|
242
|
+
ts.dateTimes.each_with_index do |dt, index|
|
243
|
+
runner.registerInfo("My index is #{index}")
|
244
|
+
dt_base = DateTime.parse(dt.to_s) if dt_base.nil?
|
245
|
+
dt_current = DateTime.parse(dt.to_s)
|
246
|
+
rows << [
|
247
|
+
DateTime.parse(dt.to_s).strftime('%m/%d/%Y %H:%M'),
|
248
|
+
dt.date.monthOfYear.value,
|
249
|
+
dt.date.dayOfMonth,
|
250
|
+
dt.date.dayOfWeek.value,
|
251
|
+
dt.time.hours,
|
252
|
+
dt.time.minutes,
|
253
|
+
dt_current.to_time.to_i - dt_base.to_time.to_i
|
254
|
+
]
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# add in the other variables by columns -- should really pull this from the report variables defined above
|
259
|
+
extract_timeseries_into_matrix(sqlFile, rows, 'Site Outdoor Air Drybulb Temperature', 'Environment', 0)
|
260
|
+
extract_timeseries_into_matrix(sqlFile, rows, 'Site Outdoor Air Relative Humidity', 'Environment', 0)
|
261
|
+
extract_timeseries_into_matrix(sqlFile, rows, 'Heating:Electricity', nil, 0)
|
262
|
+
extract_timeseries_into_matrix(sqlFile, rows, 'Heating:Gas', nil, 0)
|
263
|
+
extract_timeseries_into_matrix(sqlFile, rows, 'Cooling:Electricity', nil, 0)
|
264
|
+
extract_timeseries_into_matrix(sqlFile, rows, 'Electricity:Facility', nil, 0)
|
265
|
+
extract_timeseries_into_matrix(sqlFile, rows, 'Gas:Facility', nil, 0)
|
266
|
+
extract_timeseries_into_matrix(sqlFile, rows, 'Heating:EnergyTransfer', nil, 0)
|
267
|
+
extract_timeseries_into_matrix(sqlFile, rows, 'WaterSystems:EnergyTransfer', nil, 0)
|
268
|
+
|
269
|
+
# get all zones and save the names for later use in aggregation.
|
270
|
+
tz_names = []
|
271
|
+
model.getThermalZones.each do |tz|
|
272
|
+
tz_names << tz.name.get if tz.name.is_initialized
|
273
|
+
extract_timeseries_into_matrix(sqlFile, rows, 'Zone Predicted Sensible Load to Setpoint Heat Transfer Rate', tz_names.last, 0)
|
274
|
+
extract_timeseries_into_matrix(sqlFile, rows, 'Water Heater Heating Rate', tz_names.last, 0)
|
275
|
+
end
|
276
|
+
|
277
|
+
# sum up a couple of the columns and create a new columns
|
278
|
+
create_new_variable_sum(rows, 'TotalSensibleLoad', 'ZonePredictedSensibleLoadtoSetpointHeatTransferRate')
|
279
|
+
create_new_variable_sum(rows, 'TotalCoolingSensibleLoad', 'ZonePredictedSensibleLoadtoSetpointHeatTransferRate', negative_only: true)
|
280
|
+
create_new_variable_sum(rows, 'TotalHeatingSensibleLoad', 'ZonePredictedSensibleLoadtoSetpointHeatTransferRate', positive_only: true)
|
281
|
+
create_new_variable_sum(rows, 'TotalWaterHeating', 'WaterHeaterHeatingRate')
|
282
|
+
|
283
|
+
# convert this to CSV object
|
284
|
+
File.open('./building_loads.csv', 'w') do |f|
|
285
|
+
rows.each do |row|
|
286
|
+
f << row.join(',') << "\n"
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# covert the row data into the format needed by modelica
|
291
|
+
modelica_data = [['seconds', 'cooling', 'heating', 'waterheating']]
|
292
|
+
seconds_index = nil
|
293
|
+
total_water_heating_index = nil
|
294
|
+
total_cooling_sensible_index = nil
|
295
|
+
total_heating_sensible_index = nil
|
296
|
+
peak_cooling = 0
|
297
|
+
peak_heating = 0
|
298
|
+
peak_water_heating = 0
|
299
|
+
rows.each_with_index do |row, index|
|
300
|
+
if index.zero?
|
301
|
+
seconds_index = row.index('SecondsFromStart')
|
302
|
+
total_cooling_sensible_index = row.index('TotalCoolingSensibleLoad')
|
303
|
+
total_heating_sensible_index = row.index('TotalHeatingSensibleLoad')
|
304
|
+
total_water_heating_index = row.index('TotalWaterHeating')
|
305
|
+
else
|
306
|
+
new_data = [
|
307
|
+
row[seconds_index],
|
308
|
+
row[total_cooling_sensible_index],
|
309
|
+
row[total_heating_sensible_index],
|
310
|
+
row[total_water_heating_index]
|
311
|
+
]
|
312
|
+
|
313
|
+
modelica_data << new_data
|
314
|
+
|
315
|
+
# store the peaks
|
316
|
+
peak_cooling = row[total_cooling_sensible_index] if row[total_cooling_sensible_index] < peak_cooling
|
317
|
+
peak_heating = row[total_heating_sensible_index] if row[total_heating_sensible_index] > peak_heating
|
318
|
+
peak_water_heating = row[total_water_heating_index] if row[total_water_heating_index] > peak_water_heating
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
File.open('./modelica.mos', 'w') do |f|
|
323
|
+
f << "#1\n"
|
324
|
+
f << "#Heating and Cooling Model loads from OpenStudio Prototype Buildings\n"
|
325
|
+
f << "# Building Type: {{BUILDINGTYPE}}\n"
|
326
|
+
f << "# Climate Zone: {{CLIMATEZONE}}\n"
|
327
|
+
f << "# Vintage: {{VINTAGE}}\n"
|
328
|
+
f << "# Simulation ID (for debugging): {{SIMID}}\n"
|
329
|
+
f << "# URL: https://github.com/urbanopt/openstudio-prototype-loads\n"
|
330
|
+
f << "\n"
|
331
|
+
f << "#First column: Seconds in the year (loads are hourly)\n"
|
332
|
+
f << "#Second column: cooling loads in Watts (as negative numbers).\n"
|
333
|
+
f << "#Third column: space heating loads in Watts\n"
|
334
|
+
f << "#Fourth column: water heating in Watts\n"
|
335
|
+
f << "\n"
|
336
|
+
f << "#Peak space cooling load = #{peak_cooling} Watts\n"
|
337
|
+
f << "#Peak space heating load = #{peak_heating} Watts\n"
|
338
|
+
f << "#Peak water heating load = #{peak_water_heating} Watts\n"
|
339
|
+
f << "double tab1(8760,4)\n"
|
340
|
+
modelica_data.each_with_index do |row, index|
|
341
|
+
next if index.zero?
|
342
|
+
f << row.join(';') << "\n"
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
# Find the total runtime for energyplus and save it into a registerValue
|
347
|
+
total_time = -999
|
348
|
+
location_of_file = ['../eplusout.end', './run/eplusout.end']
|
349
|
+
first_index = location_of_file.map {|f| File.exist?(f)}.index(true)
|
350
|
+
if first_index
|
351
|
+
match = File.read(location_of_file[first_index]).to_s.match(/Elapsed.Time=(.*)hr(.*)min(.*)sec/)
|
352
|
+
total_time = match[1].to_i * 3600 + match[2].to_i * 60 + match[3].to_f
|
353
|
+
end
|
354
|
+
|
355
|
+
runner.registerValue('energyplus_runtime', total_time, 'sec')
|
356
|
+
runner.registerValue('peak_cooling_load', peak_cooling, 'W')
|
357
|
+
runner.registerValue('peak_heating_load', peak_heating, 'W')
|
358
|
+
runner.registerValue('peak_water_heating', peak_water_heating, 'W')
|
359
|
+
|
360
|
+
return true
|
361
|
+
ensure
|
362
|
+
sqlFile.close if sqlFile
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
# register the measure to be used by the application
|
367
|
+
ExportModelicaLoads.new.registerWithApplication
|