openstudio-load-flexibility-measures 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.gitignore +36 -0
- data/.rubocop.yml +9 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +3 -0
- data/README.md +42 -0
- data/Rakefile +15 -0
- data/doc_templates/LICENSE.md +27 -0
- data/doc_templates/README.md.erb +42 -0
- data/doc_templates/copyright_erb.txt +36 -0
- data/doc_templates/copyright_js.txt +4 -0
- data/doc_templates/copyright_ruby.txt +34 -0
- data/lib/measures/add_central_hpwh_for_load_flexibility/LICENSE.md +1 -0
- data/lib/measures/add_central_hpwh_for_load_flexibility/README.md +186 -0
- data/lib/measures/add_central_hpwh_for_load_flexibility/README.md.erb +42 -0
- data/lib/measures/add_central_hpwh_for_load_flexibility/docs/Flexible Domestic Hot Water Implementation Guide.pdf +0 -0
- data/lib/measures/add_central_hpwh_for_load_flexibility/measure.rb +648 -0
- data/lib/measures/add_central_hpwh_for_load_flexibility/measure.xml +398 -0
- data/lib/measures/add_central_hpwh_for_load_flexibility/tests/SmallHotel-2A.osm +42893 -0
- data/lib/measures/add_central_hpwh_for_load_flexibility/tests/add_central_hpwh_for_load_flexibility.rb +98 -0
- data/lib/measures/add_distributed_ice_storage_to_air_loop_for_load_flexibility/LICENSE.md +13 -0
- data/lib/measures/add_distributed_ice_storage_to_air_loop_for_load_flexibility/README.md +189 -0
- data/lib/measures/add_distributed_ice_storage_to_air_loop_for_load_flexibility/measure.rb +689 -0
- data/lib/measures/add_distributed_ice_storage_to_air_loop_for_load_flexibility/measure.xml +253 -0
- data/lib/measures/add_distributed_ice_storage_to_air_loop_for_load_flexibility/resources/TESCurves.idf +1059 -0
- data/lib/measures/add_distributed_ice_storage_to_air_loop_for_load_flexibility/tests/MeasureTest.osm +9507 -0
- data/lib/measures/add_distributed_ice_storage_to_air_loop_for_load_flexibility/tests/add_distributed_ice_storage_to_air_loop_for_load_flexibility_test.rb +96 -0
- data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/LICENSE.md +13 -0
- data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/README.md +264 -0
- data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/README.md.erb +42 -0
- data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/docs/Ice Measure Implementation Guide.pdf +0 -0
- data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/measure.rb +1310 -0
- data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/measure.xml +506 -0
- data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/resources/OsLib_Schedules.rb +173 -0
- data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/tests/add_ice_storage_to_plant_loop_for_load_flexibility_test.rb +202 -0
- data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/tests/ice_test_model.osm +21523 -0
- data/lib/openstudio/load_flexibility_measures.rb +50 -0
- data/lib/openstudio/load_flexibility_measures/version.rb +40 -0
- data/openstudio-load-flexibility-measures.gemspec +32 -0
- metadata +172 -0
@@ -0,0 +1,96 @@
|
|
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 'openstudio'
|
37
|
+
require 'openstudio/measure/ShowRunnerOutput'
|
38
|
+
require 'minitest/autorun'
|
39
|
+
|
40
|
+
require_relative '../measure.rb'
|
41
|
+
|
42
|
+
class AddDistributedIceStorageToAirLoopForLoadFlexibilityTest < MiniTest::Test
|
43
|
+
# def setup
|
44
|
+
# end
|
45
|
+
|
46
|
+
# def teardown
|
47
|
+
# end
|
48
|
+
|
49
|
+
def test_good_argument_values
|
50
|
+
# create an instance of the measure
|
51
|
+
measure = AddDistributedIceStorageToAirLoopForLoadFlexibility.new
|
52
|
+
|
53
|
+
# create runner with empty OSW
|
54
|
+
osw = OpenStudio::WorkflowJSON.new
|
55
|
+
runner = OpenStudio::Measure::OSRunner.new(osw)
|
56
|
+
|
57
|
+
# load the test model
|
58
|
+
translator = OpenStudio::OSVersion::VersionTranslator.new
|
59
|
+
path = OpenStudio::Path.new(File.dirname(__FILE__) + '/MeasureTest.osm')
|
60
|
+
model = translator.loadModel(path)
|
61
|
+
assert(!model.empty?)
|
62
|
+
model = model.get
|
63
|
+
|
64
|
+
# forward translate OSM file to IDF file
|
65
|
+
ft = OpenStudio::EnergyPlus::ForwardTranslator.new
|
66
|
+
workspace = ft.translateModel(model)
|
67
|
+
|
68
|
+
# get arguments
|
69
|
+
arguments = measure.arguments(workspace)
|
70
|
+
argument_map = OpenStudio::Measure.convertOSArgumentVectorToMap(arguments)
|
71
|
+
|
72
|
+
# create hash of argument values
|
73
|
+
args_hash = {}
|
74
|
+
args_hash['ice_cap'] = '40,50'
|
75
|
+
|
76
|
+
# populate argument with specified has value if set
|
77
|
+
arguments.each do |arg|
|
78
|
+
temp_arg_var = arg.clone
|
79
|
+
assert(temp_arg_var.setValue(args_hash[arg.name])) if args_hash[arg.name]
|
80
|
+
argument_map[arg.name] = temp_arg_var
|
81
|
+
end
|
82
|
+
|
83
|
+
# run the measure
|
84
|
+
measure.run(workspace, runner, argument_map)
|
85
|
+
result = runner.result
|
86
|
+
assert_equal('Success', result.value.valueName)
|
87
|
+
|
88
|
+
show_output(result)
|
89
|
+
|
90
|
+
# save the workspace to output directory
|
91
|
+
output_file_path = OpenStudio::Path.new(File.dirname(__FILE__) + '/output/test_output.idf')
|
92
|
+
workspace.save(output_file_path, true)
|
93
|
+
|
94
|
+
true
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,13 @@
|
|
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 provided that the following conditions are met:
|
4
|
+
|
5
|
+
(1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
6
|
+
|
7
|
+
(2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
8
|
+
|
9
|
+
(3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission from the respective party.
|
10
|
+
|
11
|
+
(4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior written permission from Alliance for Sustainable Energy, LLC.
|
12
|
+
|
13
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER, THE UNITED STATES GOVERNMENT, OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@@ -0,0 +1,264 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
###### (Automatically generated documentation)
|
4
|
+
|
5
|
+
# Add Ice Storage Tank
|
6
|
+
|
7
|
+
## Description
|
8
|
+
This measure adds an ice storage tank to a chilled water loop for the purpose of thermal energy storage.
|
9
|
+
|
10
|
+
## Modeler Description
|
11
|
+
This measure adds the necessary components and performs required model articulations to add an ice thermal storage tank (ITS) to an existing chilled water loop. Special consideration is given to implementing configuration and control options. Refer to the ASHRAE CTES Design Guide or manufacturer applications guides for detailed implementation info. A user guide document is included in the docs folder of this measure to help translate design objectives into measure argument input values.
|
12
|
+
|
13
|
+
## Measure Type
|
14
|
+
ModelMeasure
|
15
|
+
|
16
|
+
## Taxonomy
|
17
|
+
|
18
|
+
|
19
|
+
## Arguments
|
20
|
+
|
21
|
+
|
22
|
+
### Select Energy Storage Objective:
|
23
|
+
|
24
|
+
**Name:** objective,
|
25
|
+
**Type:** Choice,
|
26
|
+
**Units:** ,
|
27
|
+
**Required:** true,
|
28
|
+
**Model Dependent:** false
|
29
|
+
|
30
|
+
### Select Upstream Device:
|
31
|
+
Partial Storage Only. See documentation for control implementation.
|
32
|
+
**Name:** upstream,
|
33
|
+
**Type:** Choice,
|
34
|
+
**Units:** ,
|
35
|
+
**Required:** true,
|
36
|
+
**Model Dependent:** false
|
37
|
+
|
38
|
+
### Enter Thermal Energy Storage Capacity for Ice Tank [ton-hours]:
|
39
|
+
|
40
|
+
**Name:** storage_capacity,
|
41
|
+
**Type:** Double,
|
42
|
+
**Units:** ,
|
43
|
+
**Required:** true,
|
44
|
+
**Model Dependent:** false
|
45
|
+
|
46
|
+
### Select Thaw Process Indicator for Ice Storage:
|
47
|
+
|
48
|
+
**Name:** melt_indicator,
|
49
|
+
**Type:** Choice,
|
50
|
+
**Units:** ,
|
51
|
+
**Required:** true,
|
52
|
+
**Model Dependent:** false
|
53
|
+
|
54
|
+
### Select Loop:
|
55
|
+
Error: No Cooling Loop Found
|
56
|
+
**Name:** selected_loop,
|
57
|
+
**Type:** Choice,
|
58
|
+
**Units:** ,
|
59
|
+
**Required:** true,
|
60
|
+
**Model Dependent:** false
|
61
|
+
|
62
|
+
### Select Chiller:
|
63
|
+
Error: No Chiller Found
|
64
|
+
**Name:** selected_chiller,
|
65
|
+
**Type:** Choice,
|
66
|
+
**Units:** ,
|
67
|
+
**Required:** true,
|
68
|
+
**Model Dependent:** false
|
69
|
+
|
70
|
+
### Enter Chiller Sizing Factor:
|
71
|
+
|
72
|
+
**Name:** chiller_resize_factor,
|
73
|
+
**Type:** Double,
|
74
|
+
**Units:** ,
|
75
|
+
**Required:** false,
|
76
|
+
**Model Dependent:** false
|
77
|
+
|
78
|
+
### Enter Chiller Max Capacity Limit During Ice Discharge:
|
79
|
+
Enter as a fraction of chiller capacity (0.0 - 1.0).
|
80
|
+
**Name:** chiller_limit,
|
81
|
+
**Type:** Double,
|
82
|
+
**Units:** ,
|
83
|
+
**Required:** false,
|
84
|
+
**Model Dependent:** false
|
85
|
+
|
86
|
+
### Use Existing (Pre-Defined) Temperature Control Schedules?
|
87
|
+
Use drop-down selections below.
|
88
|
+
**Name:** old,
|
89
|
+
**Type:** Boolean,
|
90
|
+
**Units:** ,
|
91
|
+
**Required:** false,
|
92
|
+
**Model Dependent:** false
|
93
|
+
|
94
|
+
### Select Pre-Defined Ice Availability Schedule
|
95
|
+
|
96
|
+
**Name:** ctes_av,
|
97
|
+
**Type:** Choice,
|
98
|
+
**Units:** ,
|
99
|
+
**Required:** false,
|
100
|
+
**Model Dependent:** false
|
101
|
+
|
102
|
+
### Select Pre-Defined Ice Tank Component Setpoint Schedule
|
103
|
+
|
104
|
+
**Name:** ctes_sch,
|
105
|
+
**Type:** Choice,
|
106
|
+
**Units:** ,
|
107
|
+
**Required:** false,
|
108
|
+
**Model Dependent:** false
|
109
|
+
|
110
|
+
### Select Pre-Defined Chiller Component Setpoint Schedule
|
111
|
+
|
112
|
+
**Name:** chill_sch,
|
113
|
+
**Type:** Choice,
|
114
|
+
**Units:** ,
|
115
|
+
**Required:** false,
|
116
|
+
**Model Dependent:** false
|
117
|
+
|
118
|
+
### Create New (Simple) Temperature Control Schedules?
|
119
|
+
Use entry fields below. If Pre-Defined is also selected, these new schedules will be created but not applied.
|
120
|
+
**Name:** new,
|
121
|
+
**Type:** Boolean,
|
122
|
+
**Units:** ,
|
123
|
+
**Required:** false,
|
124
|
+
**Model Dependent:** false
|
125
|
+
|
126
|
+
### Loop Setpoint Temperature F:
|
127
|
+
This value replaces the existing loop temperature setpoint manager; the old manager will be disconnected but not deleted from the model.
|
128
|
+
**Name:** loop_sp,
|
129
|
+
**Type:** Double,
|
130
|
+
**Units:** ,
|
131
|
+
**Required:** true,
|
132
|
+
**Model Dependent:** false
|
133
|
+
|
134
|
+
### Enter Intermediate Setpoint for Upstream Cooling Device During Ice Discharge F:
|
135
|
+
Partial storage only
|
136
|
+
**Name:** inter_sp,
|
137
|
+
**Type:** Double,
|
138
|
+
**Units:** ,
|
139
|
+
**Required:** false,
|
140
|
+
**Model Dependent:** false
|
141
|
+
|
142
|
+
### Ice Charging Setpoint Temperature F:
|
143
|
+
|
144
|
+
**Name:** chg_sp,
|
145
|
+
**Type:** Double,
|
146
|
+
**Units:** ,
|
147
|
+
**Required:** true,
|
148
|
+
**Model Dependent:** false
|
149
|
+
|
150
|
+
### Loop Design Temperature Difference F:
|
151
|
+
Enter numeric value to adjust selected loop settings.
|
152
|
+
**Name:** delta_t,
|
153
|
+
**Type:** String,
|
154
|
+
**Units:** ,
|
155
|
+
**Required:** true,
|
156
|
+
**Model Dependent:** false
|
157
|
+
|
158
|
+
### Enter Seasonal Availabity of Ice Storage:
|
159
|
+
Use MM/DD-MM/DD format
|
160
|
+
**Name:** ctes_season,
|
161
|
+
**Type:** String,
|
162
|
+
**Units:** ,
|
163
|
+
**Required:** true,
|
164
|
+
**Model Dependent:** false
|
165
|
+
|
166
|
+
### Enter Starting Time for Ice Discharge:
|
167
|
+
Use 24 hour format (HR:MM)
|
168
|
+
**Name:** discharge_start,
|
169
|
+
**Type:** String,
|
170
|
+
**Units:** ,
|
171
|
+
**Required:** true,
|
172
|
+
**Model Dependent:** false
|
173
|
+
|
174
|
+
### Enter End Time for Ice Discharge:
|
175
|
+
Use 24 hour format (HR:MM)
|
176
|
+
**Name:** discharge_end,
|
177
|
+
**Type:** String,
|
178
|
+
**Units:** ,
|
179
|
+
**Required:** true,
|
180
|
+
**Model Dependent:** false
|
181
|
+
|
182
|
+
### Enter Starting Time for Ice charge:
|
183
|
+
Use 24 hour format (HR:MM)
|
184
|
+
**Name:** charge_start,
|
185
|
+
**Type:** String,
|
186
|
+
**Units:** ,
|
187
|
+
**Required:** true,
|
188
|
+
**Model Dependent:** false
|
189
|
+
|
190
|
+
### Enter End Time for Ice charge:
|
191
|
+
Use 24 hour format (HR:MM)
|
192
|
+
**Name:** charge_end,
|
193
|
+
**Type:** String,
|
194
|
+
**Units:** ,
|
195
|
+
**Required:** true,
|
196
|
+
**Model Dependent:** false
|
197
|
+
|
198
|
+
### Allow Ice Discharge on Weekends?
|
199
|
+
|
200
|
+
**Name:** wknds,
|
201
|
+
**Type:** Boolean,
|
202
|
+
**Units:** ,
|
203
|
+
**Required:** true,
|
204
|
+
**Model Dependent:** false
|
205
|
+
|
206
|
+
### Select Reporting Frequency for New Output Variables
|
207
|
+
This will not change reporting frequency for existing output variables in the model.
|
208
|
+
**Name:** report_freq,
|
209
|
+
**Type:** Choice,
|
210
|
+
**Units:** ,
|
211
|
+
**Required:** false,
|
212
|
+
**Model Dependent:** false
|
213
|
+
|
214
|
+
### Test Demand Reponse Event?
|
215
|
+
|
216
|
+
**Name:** dr,
|
217
|
+
**Type:** Boolean,
|
218
|
+
**Units:** ,
|
219
|
+
**Required:** false,
|
220
|
+
**Model Dependent:** false
|
221
|
+
|
222
|
+
### Select if a Load Add or Load Shed Event
|
223
|
+
|
224
|
+
**Name:** dr_add_shed,
|
225
|
+
**Type:** Choice,
|
226
|
+
**Units:** ,
|
227
|
+
**Required:** false,
|
228
|
+
**Model Dependent:** false
|
229
|
+
|
230
|
+
### Enter date of demand response event:
|
231
|
+
Use MM/DD format.
|
232
|
+
**Name:** dr_date,
|
233
|
+
**Type:** String,
|
234
|
+
**Units:** ,
|
235
|
+
**Required:** false,
|
236
|
+
**Model Dependent:** false
|
237
|
+
|
238
|
+
### Enter start time of demand response event:
|
239
|
+
Use 24 hour format (HR:MM)
|
240
|
+
**Name:** dr_time,
|
241
|
+
**Type:** String,
|
242
|
+
**Units:** ,
|
243
|
+
**Required:** false,
|
244
|
+
**Model Dependent:** false
|
245
|
+
|
246
|
+
### Enter duration of demand response event [hr]:
|
247
|
+
|
248
|
+
**Name:** dr_dur,
|
249
|
+
**Type:** Double,
|
250
|
+
**Units:** ,
|
251
|
+
**Required:** false,
|
252
|
+
**Model Dependent:** false
|
253
|
+
|
254
|
+
### Allow chiller to back-up ice during DR event?
|
255
|
+
Unselection may result in unmet cooling hours
|
256
|
+
**Name:** dr_chill,
|
257
|
+
**Type:** Boolean,
|
258
|
+
**Units:** ,
|
259
|
+
**Required:** false,
|
260
|
+
**Model Dependent:** false
|
261
|
+
|
262
|
+
|
263
|
+
|
264
|
+
|
@@ -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 %>
|
Binary file
|
@@ -0,0 +1,1310 @@
|
|
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
|
+
# Measure distributed under NREL Copyright terms, see LICENSE.md file.
|
37
|
+
|
38
|
+
# Author: Karl Heine
|
39
|
+
# Date: June 2019 - July 2020
|
40
|
+
# Additional Code Added to Test Ice Performance for Demand Response Events: September 2019
|
41
|
+
# Revised February 2020
|
42
|
+
|
43
|
+
# References:
|
44
|
+
# => ASHRAE Handbook, HVAC Systems and Equipment, Chapter 51: Thermal storage, 2016
|
45
|
+
# => ASHRAE Design Guide for Cool Thermal Storage, 2nd Edition, January 2019
|
46
|
+
# => Manufacturer marketing materials, available online
|
47
|
+
|
48
|
+
# load OpenStudio measure libraries
|
49
|
+
require "#{File.dirname(__FILE__)}/resources/OsLib_Schedules"
|
50
|
+
|
51
|
+
# start the measure
|
52
|
+
class AddIceStorageToPlantLoopForLoadFlexibility < OpenStudio::Measure::ModelMeasure
|
53
|
+
# human readable name
|
54
|
+
def name
|
55
|
+
# Measure name should be the title case of the class name.
|
56
|
+
'Add Ice Storage Tank'
|
57
|
+
end
|
58
|
+
|
59
|
+
# human readable description
|
60
|
+
def description
|
61
|
+
'This measure adds an ice storage tank to a chilled water loop for the purpose of thermal energy storage.'
|
62
|
+
end
|
63
|
+
|
64
|
+
# human readable description of modeling approach
|
65
|
+
def modeler_description
|
66
|
+
'This measure adds the necessary components and performs required model articulations to add an ice ' \
|
67
|
+
'thermal storage tank (ITS) to an existing chilled water loop. Special consideration is given to ' \
|
68
|
+
'implementing configuration and control options. Refer to the ASHRAE CTES Design Guide or manufacturer ' \
|
69
|
+
'applications guides for detailed implementation info. A user guide document is included in the docs ' \
|
70
|
+
'folder of this measure to help translate design objectives into measure argument input values.'
|
71
|
+
end
|
72
|
+
|
73
|
+
# define the arguments that the user will input
|
74
|
+
def arguments(model)
|
75
|
+
args = OpenStudio::Measure::OSArgumentVector.new
|
76
|
+
|
77
|
+
# Make choice argument for energy storage objective
|
78
|
+
objective = OpenStudio::Measure::OSArgument.makeChoiceArgument('objective', ['Full Storage', 'Partial Storage'], true)
|
79
|
+
objective.setDisplayName('Select Energy Storage Objective:')
|
80
|
+
objective.setDefaultValue('Partial Storage')
|
81
|
+
args << objective
|
82
|
+
|
83
|
+
# Make choice argument for component layout
|
84
|
+
upstream = OpenStudio::Measure::OSArgument.makeChoiceArgument('upstream', ['Chiller', 'Storage'])
|
85
|
+
upstream.setDisplayName('Select Upstream Device:')
|
86
|
+
upstream.setDescription('Partial Storage Only. See documentation for control implementation.')
|
87
|
+
upstream.setDefaultValue('Chiller')
|
88
|
+
args << upstream
|
89
|
+
|
90
|
+
# Make double argument for thermal energy storage capacity
|
91
|
+
storage_capacity = OpenStudio::Measure::OSArgument.makeDoubleArgument('storage_capacity', true)
|
92
|
+
storage_capacity.setDisplayName('Enter Thermal Energy Storage Capacity for Ice Tank [ton-hours]:')
|
93
|
+
storage_capacity.setDefaultValue(2000)
|
94
|
+
args << storage_capacity
|
95
|
+
|
96
|
+
# Make choice argument for ice melt process indicator
|
97
|
+
melt_indicator = OpenStudio::Measure::OSArgument.makeChoiceArgument('melt_indicator', ['InsideMelt', 'OutsideMelt'], true)
|
98
|
+
melt_indicator.setDisplayName('Select Thaw Process Indicator for Ice Storage:')
|
99
|
+
melt_indicator.setDescription('')
|
100
|
+
melt_indicator.setDefaultValue('InsideMelt')
|
101
|
+
args << melt_indicator
|
102
|
+
|
103
|
+
# Make list of chilled water loop(s) from which user can select
|
104
|
+
plant_loops = model.getPlantLoops
|
105
|
+
loop_choices = OpenStudio::StringVector.new
|
106
|
+
plant_loops.each do |loop|
|
107
|
+
if loop.sizingPlant.loopType.to_s == 'Cooling'
|
108
|
+
loop_choices << loop.name.to_s
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Make choice argument for loop selection
|
113
|
+
selected_loop = OpenStudio::Measure::OSArgument.makeChoiceArgument('selected_loop', loop_choices, true)
|
114
|
+
selected_loop.setDisplayName('Select Loop:')
|
115
|
+
selected_loop.setDescription('This is the cooling loop on which the ice tank will be added.')
|
116
|
+
if loop_choices.include?('Chilled Water Loop')
|
117
|
+
selected_loop.setDefaultValue('Chilled Water Loop')
|
118
|
+
else
|
119
|
+
selected_loop.setDescription('Error: No Cooling Loop Found')
|
120
|
+
end
|
121
|
+
args << selected_loop
|
122
|
+
|
123
|
+
# Make list of available chillers from which the user can select
|
124
|
+
chillers = model.getChillerElectricEIRs
|
125
|
+
chillers += model.getChillerAbsorptions
|
126
|
+
chillers += model.getChillerAbsorptionIndirects
|
127
|
+
chiller_choices = OpenStudio::StringVector.new
|
128
|
+
chillers.each do |chill|
|
129
|
+
chiller_choices << chill.name.to_s
|
130
|
+
end
|
131
|
+
|
132
|
+
# Make choice argument for chiller selection
|
133
|
+
selected_chiller = OpenStudio::Measure::OSArgument.makeChoiceArgument('selected_chiller', chiller_choices, true)
|
134
|
+
selected_chiller.setDisplayName('Select Chiller:')
|
135
|
+
selected_chiller.setDescription('The ice tank will be placed in series with this chiller.')
|
136
|
+
if !chillers.empty?
|
137
|
+
selected_chiller.setDefaultValue(chiller_choices[0])
|
138
|
+
else
|
139
|
+
selected_chiller.setDescription('Error: No Chiller Found')
|
140
|
+
end
|
141
|
+
args << selected_chiller
|
142
|
+
|
143
|
+
# Make double argument for ice chiller resizing factor - relative to selected chiller capacity
|
144
|
+
chiller_resize_factor = OpenStudio::Measure::OSArgument.makeDoubleArgument('chiller_resize_factor', false)
|
145
|
+
chiller_resize_factor.setDisplayName('Enter Chiller Sizing Factor:')
|
146
|
+
chiller_resize_factor.setDefaultValue('0.7')
|
147
|
+
args << chiller_resize_factor
|
148
|
+
|
149
|
+
# Make double argument for chiller max capacity limit during ice discharge
|
150
|
+
chiller_limit = OpenStudio::Measure::OSArgument.makeDoubleArgument('chiller_limit', false)
|
151
|
+
chiller_limit.setDisplayName('Enter Chiller Max Capacity Limit During Ice Discharge:')
|
152
|
+
chiller_limit.setDescription('Enter as a fraction of chiller capacity (0.0 - 1.0).')
|
153
|
+
chiller_limit.setDefaultValue('0.75')
|
154
|
+
args << chiller_limit
|
155
|
+
|
156
|
+
# Make choice argument for schedule options
|
157
|
+
old = OpenStudio::Measure::OSArgument.makeBoolArgument('old', false)
|
158
|
+
old.setDisplayName('Use Existing (Pre-Defined) Temperature Control Schedules?')
|
159
|
+
old.setDescription('Use drop-down selections below.')
|
160
|
+
old.setDefaultValue(false)
|
161
|
+
args << old
|
162
|
+
|
163
|
+
# Find All Existing Schedules with Type Limits of "Temperature"
|
164
|
+
sched_options = []
|
165
|
+
sched_options2 = []
|
166
|
+
all_scheds = model.getSchedules
|
167
|
+
all_scheds.each do |sched|
|
168
|
+
sched.to_ScheduleBase.get
|
169
|
+
unless sched.scheduleTypeLimits.empty?
|
170
|
+
if sched.scheduleTypeLimits.get.unitType.to_s == 'Temperature'
|
171
|
+
sched_options << sched.name.to_s
|
172
|
+
elsif sched.scheduleTypeLimits.get.unitType.to_s == 'Availability' ||
|
173
|
+
sched.scheduleTypeLimits.get.unitType.to_s == 'OnOff'
|
174
|
+
sched_options2 << sched.name.to_s
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
sched_options = ['N/A'] + sched_options.sort
|
179
|
+
sched_options2 = ['N/A'] + sched_options2.sort
|
180
|
+
|
181
|
+
# Create choice argument for ice availability schedule (old = true)
|
182
|
+
ctes_av = OpenStudio::Measure::OSArgument.makeChoiceArgument('ctes_av', sched_options2, false)
|
183
|
+
ctes_av.setDisplayName('Select Pre-Defined Ice Availability Schedule')
|
184
|
+
if sched_options2.empty?
|
185
|
+
ctes_av.setDescription('Warning: No availability schedules found')
|
186
|
+
end
|
187
|
+
ctes_av.setDefaultValue('N/A')
|
188
|
+
args << ctes_av
|
189
|
+
|
190
|
+
# Create choice argument for ice tank component setpoint sched (old = true)
|
191
|
+
ctes_sch = OpenStudio::Measure::OSArgument.makeChoiceArgument('ctes_sch', sched_options, false)
|
192
|
+
ctes_sch.setDisplayName('Select Pre-Defined Ice Tank Component Setpoint Schedule')
|
193
|
+
if sched_options.empty?
|
194
|
+
ctes_sch.setDescription('Warning: No temperature setpoint schedules found')
|
195
|
+
end
|
196
|
+
ctes_sch.setDefaultValue('N/A')
|
197
|
+
args << ctes_sch
|
198
|
+
|
199
|
+
# Create choice argument for chiller component setpoint sched (old = true)
|
200
|
+
chill_sch = OpenStudio::Measure::OSArgument.makeChoiceArgument('chill_sch', sched_options, false)
|
201
|
+
chill_sch.setDisplayName('Select Pre-Defined Chiller Component Setpoint Schedule')
|
202
|
+
if sched_options.empty?
|
203
|
+
chill_sch.setDescription('Warning: No temperature setpoint schedules found')
|
204
|
+
end
|
205
|
+
chill_sch.setDefaultValue('N/A')
|
206
|
+
args << chill_sch
|
207
|
+
|
208
|
+
# Make bool argument for creating new schedules
|
209
|
+
new = OpenStudio::Measure::OSArgument.makeBoolArgument('new', false)
|
210
|
+
new.setDisplayName('Create New (Simple) Temperature Control Schedules?')
|
211
|
+
new.setDescription('Use entry fields below. If Pre-Defined is also selected, these new schedules will be created' \
|
212
|
+
' but not applied.')
|
213
|
+
new.setDefaultValue(true)
|
214
|
+
args << new
|
215
|
+
|
216
|
+
# Make double argument for loop setpoint temperature
|
217
|
+
loop_sp = OpenStudio::Measure::OSArgument.makeDoubleArgument('loop_sp', true)
|
218
|
+
loop_sp.setDisplayName('Loop Setpoint Temperature F:')
|
219
|
+
loop_sp.setDescription('This value replaces the existing loop temperature setpoint manager; the old manager will ' \
|
220
|
+
'be disconnected but not deleted from the model.')
|
221
|
+
loop_sp.setDefaultValue(44)
|
222
|
+
args << loop_sp
|
223
|
+
|
224
|
+
# Make double argument for ice chiller outlet temp during partial storage operation
|
225
|
+
inter_sp = OpenStudio::Measure::OSArgument.makeDoubleArgument('inter_sp', false)
|
226
|
+
inter_sp.setDisplayName('Enter Intermediate Setpoint for Upstream Cooling Device During Ice Discharge F:')
|
227
|
+
inter_sp.setDescription('Partial storage only')
|
228
|
+
inter_sp.setDefaultValue(47)
|
229
|
+
args << inter_sp
|
230
|
+
|
231
|
+
# Make double argument for loop temperature for ice charging
|
232
|
+
chg_sp = OpenStudio::Measure::OSArgument.makeDoubleArgument('chg_sp', true)
|
233
|
+
chg_sp.setDisplayName('Ice Charging Setpoint Temperature F:')
|
234
|
+
chg_sp.setDefaultValue('25')
|
235
|
+
args << chg_sp
|
236
|
+
|
237
|
+
# Make double argument for loop design delta T
|
238
|
+
delta_t = OpenStudio::Measure::OSArgument.makeStringArgument('delta_t', true)
|
239
|
+
delta_t.setDisplayName('Loop Design Temperature Difference F:')
|
240
|
+
delta_t.setDescription('Enter numeric value to adjust selected loop settings.')
|
241
|
+
delta_t.setDefaultValue('Use Existing Loop Value')
|
242
|
+
args << delta_t
|
243
|
+
|
244
|
+
# Make string argument for ctes seasonal availabilty
|
245
|
+
ctes_season = OpenStudio::Measure::OSArgument.makeStringArgument('ctes_season', true)
|
246
|
+
ctes_season.setDisplayName('Enter Seasonal Availabity of Ice Storage:')
|
247
|
+
ctes_season.setDescription('Use MM/DD-MM/DD format')
|
248
|
+
ctes_season.setDefaultValue('01/01-12/31')
|
249
|
+
args << ctes_season
|
250
|
+
|
251
|
+
# Make string arguments for ctes discharge times
|
252
|
+
discharge_start = OpenStudio::Measure::OSArgument.makeStringArgument('discharge_start', true)
|
253
|
+
discharge_start.setDisplayName('Enter Starting Time for Ice Discharge:')
|
254
|
+
discharge_start.setDescription('Use 24 hour format (HR:MM)')
|
255
|
+
discharge_start.setDefaultValue('08:00')
|
256
|
+
args << discharge_start
|
257
|
+
|
258
|
+
discharge_end = OpenStudio::Measure::OSArgument.makeStringArgument('discharge_end', true)
|
259
|
+
discharge_end.setDisplayName('Enter End Time for Ice Discharge:')
|
260
|
+
discharge_end.setDescription('Use 24 hour format (HR:MM)')
|
261
|
+
discharge_end.setDefaultValue('21:00')
|
262
|
+
args << discharge_end
|
263
|
+
|
264
|
+
# Make string arguments for ctes charge times
|
265
|
+
charge_start = OpenStudio::Measure::OSArgument.makeStringArgument('charge_start', true)
|
266
|
+
charge_start.setDisplayName('Enter Starting Time for Ice charge:')
|
267
|
+
charge_start.setDescription('Use 24 hour format (HR:MM)')
|
268
|
+
charge_start.setDefaultValue('23:00')
|
269
|
+
args << charge_start
|
270
|
+
|
271
|
+
charge_end = OpenStudio::Measure::OSArgument.makeStringArgument('charge_end', true)
|
272
|
+
charge_end.setDisplayName('Enter End Time for Ice charge:')
|
273
|
+
charge_end.setDescription('Use 24 hour format (HR:MM)')
|
274
|
+
charge_end.setDefaultValue('07:00')
|
275
|
+
args << charge_end
|
276
|
+
|
277
|
+
# Make boolean arguments for ctes dischage days
|
278
|
+
wknds = OpenStudio::Measure::OSArgument.makeBoolArgument('wknds', true)
|
279
|
+
wknds.setDisplayName('Allow Ice Discharge on Weekends?')
|
280
|
+
wknds.setDefaultValue(false)
|
281
|
+
args << wknds
|
282
|
+
|
283
|
+
# Make choice argument for output variable reporting frequency
|
284
|
+
report_choices = ['Detailed', 'Timestep', 'Hourly', 'Daily', 'Monthly', 'RunPeriod']
|
285
|
+
report_freq = OpenStudio::Measure::OSArgument.makeChoiceArgument('report_freq', report_choices, false)
|
286
|
+
report_freq.setDisplayName('Select Reporting Frequency for New Output Variables')
|
287
|
+
report_freq.setDescription('This will not change reporting frequency for existing output variables in the model.')
|
288
|
+
report_freq.setDefaultValue('Timestep')
|
289
|
+
args << report_freq
|
290
|
+
|
291
|
+
## DR TESTER INPUTS -----------------------------------------
|
292
|
+
# Make boolean argument for use of demand response event test
|
293
|
+
dr = OpenStudio::Measure::OSArgument.makeBoolArgument('dr', false)
|
294
|
+
dr.setDisplayName('Test Demand Reponse Event?')
|
295
|
+
dr.setDescription('')
|
296
|
+
dr.setDefaultValue(false)
|
297
|
+
args << dr
|
298
|
+
|
299
|
+
# Make choice argument for type of demand response event (add or shed)
|
300
|
+
dr_add_shed = OpenStudio::Measure::OSArgument.makeChoiceArgument('dr_add_shed', ['Add', 'Shed'], false)
|
301
|
+
dr_add_shed.setDisplayName('Select if a Load Add or Load Shed Event')
|
302
|
+
dr_add_shed.setDescription('')
|
303
|
+
dr_add_shed.setDefaultValue('Shed')
|
304
|
+
args << dr_add_shed
|
305
|
+
|
306
|
+
# Make string argument for DR event date
|
307
|
+
dr_date = OpenStudio::Measure::OSArgument.makeStringArgument('dr_date', false)
|
308
|
+
dr_date.setDisplayName('Enter date of demand response event:')
|
309
|
+
dr_date.setDescription('Use MM/DD format.')
|
310
|
+
dr_date.setDefaultValue('9/19')
|
311
|
+
args << dr_date
|
312
|
+
|
313
|
+
# Make string argument for DR Event time
|
314
|
+
dr_time = OpenStudio::Measure::OSArgument.makeStringArgument('dr_time', false)
|
315
|
+
dr_time.setDisplayName('Enter start time of demand response event:')
|
316
|
+
dr_time.setDescription('Use 24 hour format (HR:MM)')
|
317
|
+
dr_time.setDefaultValue('11:30')
|
318
|
+
args << dr_time
|
319
|
+
|
320
|
+
# Make double argument for DR event duration
|
321
|
+
dr_dur = OpenStudio::Measure::OSArgument.makeDoubleArgument('dr_dur', false)
|
322
|
+
dr_dur.setDisplayName('Enter duration of demand response event [hr]:')
|
323
|
+
dr_dur.setDescription('')
|
324
|
+
dr_dur.setDefaultValue('3')
|
325
|
+
args << dr_dur
|
326
|
+
|
327
|
+
# Make boolean argument for allowing chiller to back-up ice
|
328
|
+
dr_chill = OpenStudio::Measure::OSArgument.makeBoolArgument('dr_chill', false)
|
329
|
+
dr_chill.setDisplayName('Allow chiller to back-up ice during DR event?')
|
330
|
+
dr_chill.setDescription('Unselection may result in unmet cooling hours')
|
331
|
+
dr_chill.setDefaultValue('false')
|
332
|
+
args << dr_chill
|
333
|
+
## END DR TESTER INPUTS --------------------------------------
|
334
|
+
|
335
|
+
args
|
336
|
+
end
|
337
|
+
|
338
|
+
# define what happens when the measure is run
|
339
|
+
def run(model, runner, user_arguments)
|
340
|
+
super(model, runner, user_arguments)
|
341
|
+
|
342
|
+
# use the built-in error checking
|
343
|
+
unless runner.validateUserArguments(arguments(model), user_arguments)
|
344
|
+
return false
|
345
|
+
end
|
346
|
+
|
347
|
+
## Arguments and Declarations---------------------------------------------------------------------------------------
|
348
|
+
# Assign user arguments to variables
|
349
|
+
objective = runner.getStringArgumentValue('objective', user_arguments)
|
350
|
+
upstream = runner.getStringArgumentValue('upstream', user_arguments)
|
351
|
+
storage_capacity = runner.getDoubleArgumentValue('storage_capacity', user_arguments)
|
352
|
+
melt_indicator = runner.getStringArgumentValue('melt_indicator', user_arguments)
|
353
|
+
selected_loop = runner.getStringArgumentValue('selected_loop', user_arguments)
|
354
|
+
selected_chiller = runner.getStringArgumentValue('selected_chiller', user_arguments)
|
355
|
+
chiller_resize_factor = runner.getDoubleArgumentValue('chiller_resize_factor', user_arguments)
|
356
|
+
chiller_limit = runner.getDoubleArgumentValue('chiller_limit', user_arguments)
|
357
|
+
old = runner.getBoolArgumentValue('old', user_arguments)
|
358
|
+
ctes_av = runner.getStringArgumentValue('ctes_av', user_arguments)
|
359
|
+
ctes_sch = runner.getStringArgumentValue('ctes_sch', user_arguments)
|
360
|
+
chill_sch = runner.getStringArgumentValue('chill_sch', user_arguments)
|
361
|
+
new = runner.getBoolArgumentValue('new', user_arguments)
|
362
|
+
loop_sp = runner.getDoubleArgumentValue('loop_sp', user_arguments)
|
363
|
+
inter_sp = runner.getDoubleArgumentValue('inter_sp', user_arguments)
|
364
|
+
chg_sp = runner.getDoubleArgumentValue('chg_sp', user_arguments)
|
365
|
+
delta_t = runner.getStringArgumentValue('delta_t', user_arguments)
|
366
|
+
ctes_season = runner.getStringArgumentValue('ctes_season', user_arguments)
|
367
|
+
discharge_start = runner.getStringArgumentValue('discharge_start', user_arguments)
|
368
|
+
discharge_end = runner.getStringArgumentValue('discharge_end', user_arguments)
|
369
|
+
charge_start = runner.getStringArgumentValue('charge_start', user_arguments)
|
370
|
+
charge_end = runner.getStringArgumentValue('charge_end', user_arguments)
|
371
|
+
wknds = runner.getBoolArgumentValue('wknds', user_arguments)
|
372
|
+
report_freq = runner.getStringArgumentValue('report_freq', user_arguments)
|
373
|
+
|
374
|
+
## DR TESTER INPUTS ----------------------------------
|
375
|
+
dr = runner.getBoolArgumentValue('dr', user_arguments)
|
376
|
+
dr_add_shed = runner.getStringArgumentValue('dr_add_shed', user_arguments)
|
377
|
+
(dr_mon, dr_day) = runner.getStringArgumentValue('dr_date', user_arguments).split('/')
|
378
|
+
(dr_hr, dr_min) = runner.getStringArgumentValue('dr_time', user_arguments).split(':')
|
379
|
+
dr_dur = runner.getDoubleArgumentValue('dr_dur', user_arguments)
|
380
|
+
dr_chill = runner.getBoolArgumentValue('dr_chill', user_arguments)
|
381
|
+
dr_time = (dr_hr.to_f + (dr_min.to_f / 60)).round(2)
|
382
|
+
## END DR TESTER INPUTS ------------------------------
|
383
|
+
|
384
|
+
# Declare useful variables with values set within do-loops
|
385
|
+
cond_loop = ''
|
386
|
+
ctes_sp_sched = ''
|
387
|
+
ctes_setpoint = 99.0 # This is a flag value and should be overwritten later
|
388
|
+
demand_sp_mgr = ''
|
389
|
+
|
390
|
+
## Validate User Inputs---------------------------------------------------------------------------------------------
|
391
|
+
|
392
|
+
# Convert thermal storage capacity from ton-hours to GJ
|
393
|
+
storage_capacity = 12_660_670.23144e-9 * storage_capacity
|
394
|
+
|
395
|
+
# Check for existence of charging setpoint temperature, reset to default if left blank.
|
396
|
+
if chg_sp.nil? || chg_sp >= 32.0
|
397
|
+
runner.registerWarning('An invalid ice charging temperature was entered. Value reset to -3.88 C (25.0 F).')
|
398
|
+
chg_sp = 25.0
|
399
|
+
elsif chg_sp < 20.0
|
400
|
+
runner.registerWarning('The ice charging temperature is set below 20 F; this is atypically low. Verify input.')
|
401
|
+
end
|
402
|
+
|
403
|
+
# Convert setpoint temperature inputs from F to C
|
404
|
+
loop_sp = (loop_sp - 32.0) / 1.8
|
405
|
+
inter_sp = (inter_sp - 32.0) / 1.8
|
406
|
+
chg_sp = (chg_sp - 32.0) / 1.8
|
407
|
+
|
408
|
+
# Check if both old and new are false, set new = true and report error
|
409
|
+
if !old && !new
|
410
|
+
runner.registerError('No CTES schedule option was selected; either use old schedules or the create new option. ' \
|
411
|
+
'Measure aborted.')
|
412
|
+
return false
|
413
|
+
end
|
414
|
+
|
415
|
+
# Locate selected chiller and verify loop connection
|
416
|
+
if !model.getChillerElectricEIRByName(selected_chiller).empty?
|
417
|
+
ctes_chiller = model.getChillerElectricEIRByName(selected_chiller).get
|
418
|
+
elsif !model.getChillerAbsorptionByName(selected_chiller).empty?
|
419
|
+
ctes_chiller = model.getChillerAbsorptionByName(selected_chiller).get
|
420
|
+
elsif !model.getChillerAbsorptionIndirectByName(selected_chiller).empty?
|
421
|
+
ctes_chiller = model.getChillerAbsorptionIndirectByName(selected_chiller).get
|
422
|
+
end
|
423
|
+
|
424
|
+
ctes_loop = model.getModelObjectByName(selected_loop).get.to_PlantLoop.get
|
425
|
+
|
426
|
+
unless ctes_loop.components.include?(ctes_chiller)
|
427
|
+
runner.registerError('The selected chiller is not located on the selected chilled water loop. Measure aborted.')
|
428
|
+
return false
|
429
|
+
end
|
430
|
+
|
431
|
+
# Convert Delta T if needed from F to C (Overwrites string variables as floats)
|
432
|
+
if delta_t != 'Use Existing Loop Value' && delta_t.to_f != 0.0
|
433
|
+
delta_t = delta_t.to_f / 1.8
|
434
|
+
else
|
435
|
+
# Could add additional checks here for invalid (non-numerical) entries
|
436
|
+
delta_t = ctes_loop.sizingPlant.loopDesignTemperatureDifference
|
437
|
+
end
|
438
|
+
|
439
|
+
# Check chiller limit input values
|
440
|
+
if chiller_limit > 1.0
|
441
|
+
runner.registerWarning('Chiller limit must be a ratio less than 1. Limit set to 1.0.')
|
442
|
+
chiller_limit = 1.0
|
443
|
+
elsif chiller_limit < 0
|
444
|
+
runner.registerWarning('Chiller limit must be a ratio greater than or equal to 0. Limit set to 0.0' \
|
445
|
+
' (i.e. full storage).')
|
446
|
+
chiller_limit = 0.0
|
447
|
+
elsif chiller_limit < 0.15
|
448
|
+
runner.registerInfo('Chiller limit is below 15%; this may be outside the reasonable part load operating " \
|
449
|
+
"window for the device. Consider increasing or setting to 0.')
|
450
|
+
end
|
451
|
+
|
452
|
+
# Convert chiller limit to a temperature value based on delta_t variable if != 1. Otherwise, use as flag for EMS
|
453
|
+
if chiller_limit != 1.0
|
454
|
+
dt_max = chiller_limit * delta_t # degrees C
|
455
|
+
runner.registerInfo("Max chiller dT during ice discharge is set to: #{dt_max.round(2)} C " \
|
456
|
+
"(#{(dt_max * 1.8).round(2)} F).")
|
457
|
+
else
|
458
|
+
dt_max = delta_t
|
459
|
+
end
|
460
|
+
|
461
|
+
# Check limits of chiller performance curves and adjust if necessary - Notify user with WARNING
|
462
|
+
curve_output_check = false
|
463
|
+
cap_ft = ctes_chiller.coolingCapacityFunctionOfTemperature
|
464
|
+
min_x = cap_ft.minimumValueofx.to_f
|
465
|
+
if min_x > chg_sp
|
466
|
+
cap_ft.setMinimumValueofx(chg_sp)
|
467
|
+
runner.registerWarning("VERIFY CURVE VALIDITY: The input range for the '#{cap_ft.name}' curve is too " \
|
468
|
+
'restrictive for use with ice charging. The provided curve has been ' \
|
469
|
+
"extrapolated to a lower limit of #{chg_sp.round(2)} C for the 'x' variable.")
|
470
|
+
curve_output_check = true
|
471
|
+
end
|
472
|
+
|
473
|
+
eir_ft = ctes_chiller.electricInputToCoolingOutputRatioFunctionOfTemperature
|
474
|
+
min_x = eir_ft.minimumValueofx.to_f
|
475
|
+
if min_x > chg_sp
|
476
|
+
runner.registerWarning("VERIFY CURVE VALIDITY: The input range for the '#{eir_ft.name}' curve is too " \
|
477
|
+
'restrictive for use with ice charging. The provided curve has been ' \
|
478
|
+
"extrapolated to a lower limit of #{chg_sp.round(2)} C for the 'x' variable.")
|
479
|
+
end
|
480
|
+
|
481
|
+
# Report chiller performance derate at the ice-making conditions.
|
482
|
+
if curve_output_check == true
|
483
|
+
derate = cap_ft.evaluate(chg_sp, ctes_chiller.referenceEnteringCondenserFluidTemperature)
|
484
|
+
runner.registerInfo('A curve extrapolation warning was registered for the chiller capacity as a function ' \
|
485
|
+
'of temperature curve. At normal ice making temperatures, a chiller derate to 60-70% ' \
|
486
|
+
'of nominal capacity is expected. Using a condenser entering water temperature of ' \
|
487
|
+
"#{ctes_chiller.referenceEnteringCondenserFluidTemperature.round(1)} C and the ice " \
|
488
|
+
"charging temperature of #{chg_sp.round(1)} C, a derate to #{(derate * 100).round(1)}% is " \
|
489
|
+
'returned. This value will increase with lower condenser fluid return temperatures.')
|
490
|
+
end
|
491
|
+
|
492
|
+
# Check to ensure schedules are selected if old = true
|
493
|
+
if old
|
494
|
+
if ctes_av == 'N/A'
|
495
|
+
runner.registerError('Pre-Defined schedule option chosen, but no availabity schedule was selected.')
|
496
|
+
runner.registerWarning('Measure terminated early; no storage was applied.')
|
497
|
+
return false
|
498
|
+
end
|
499
|
+
if ctes_sch == 'N/A'
|
500
|
+
runner.registerError('Pre-Defined schedule option chosen, but no ice tank setpoint schedule was selected.')
|
501
|
+
runner.registerWarning('Measure terminated early; no storage was applied.')
|
502
|
+
return false
|
503
|
+
end
|
504
|
+
if chill_sch == 'N/A'
|
505
|
+
runner.registerError('Pre-Defined schedule option chosen, but no chiller setpoint schedule was selected.')
|
506
|
+
runner.registerWarning('Measure terminated early; no storage was applied.')
|
507
|
+
return false
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
# Parse and verify schedule inputs
|
512
|
+
# Remove potential spaces from date inputs
|
513
|
+
ctes_season = ctes_season.delete(' ')
|
514
|
+
|
515
|
+
# Convert HR:MM format into HR.fraction format
|
516
|
+
(d_start_hr, d_start_min) = discharge_start.split(':')
|
517
|
+
(d_end_hr, d_end_min) = discharge_end.split(':')
|
518
|
+
(c_start_hr, c_start_min) = charge_start.split(':')
|
519
|
+
(c_end_hr, c_end_min) = charge_end.split(':')
|
520
|
+
|
521
|
+
# Store re-formatted time values in shorthand variables for use in schedule
|
522
|
+
# building
|
523
|
+
ds = (d_start_hr.to_f + d_start_min.to_f / 60).round(2)
|
524
|
+
de = (d_end_hr.to_f + d_end_min.to_f / 60).round(2)
|
525
|
+
cs = (c_start_hr.to_f + c_start_min.to_f / 60).round(2)
|
526
|
+
ce = (c_end_hr.to_f + c_end_min.to_f / 60).round(2)
|
527
|
+
|
528
|
+
# Verify that input times make sense
|
529
|
+
if ds > de
|
530
|
+
runner.registerWarning('Dischage start time is later than discharge ' \
|
531
|
+
'end time (your ice will discharge overnight). ' \
|
532
|
+
'Verify schedule inputs.')
|
533
|
+
end
|
534
|
+
|
535
|
+
if cs.between?(ds - 0.01, de + 0.01) || ce.between?(ds - 0.01, de + 0.01)
|
536
|
+
runner.registerWarning('The tank charge and discharge periods overlap. ' \
|
537
|
+
'Examine results for unexpected operation; ' \
|
538
|
+
'verify schedule inputs.')
|
539
|
+
end
|
540
|
+
|
541
|
+
if [ds, de, cs, ce].any? { |i| i > 24 }
|
542
|
+
runner.registerError('One of you time enteries exceeds 24:00, ' \
|
543
|
+
'resulting in a schedule error. Measure aborted.')
|
544
|
+
return false
|
545
|
+
end
|
546
|
+
|
547
|
+
## Report Initial Condition of the Model----------------------------------------------------------------------------
|
548
|
+
total_storage = model.getThermalStorageIceDetaileds.size
|
549
|
+
runner.registerInitialCondition("The model started with #{total_storage} ice storage device(s).")
|
550
|
+
|
551
|
+
runner.registerInfo("Chiller '#{selected_chiller}' on Loop '#{selected_loop}' was selected for the addition " \
|
552
|
+
"of a #{storage_capacity.round(2)} GJ (#{(storage_capacity / 12_660_670.23144e-9).round(0)} " \
|
553
|
+
'ton-hours) ice thermal energy storage object.')
|
554
|
+
|
555
|
+
## Modify Chiller Settings------------------------------------------------------------------------------------------
|
556
|
+
|
557
|
+
# Adjust ctes chiller minimum outlet temperature
|
558
|
+
ctes_chiller.setLeavingChilledWaterLowerTemperatureLimit(chg_sp)
|
559
|
+
runner.registerInfo("Selected chiller minimum setpoint temperature was adjusted to #{chg_sp.round(2)} C " \
|
560
|
+
"(#{(chg_sp * 1.8) + 32} F).")
|
561
|
+
|
562
|
+
# Adjust ctes chiller sizing factor based on user input
|
563
|
+
if ctes_chiller.isReferenceCapacityAutosized
|
564
|
+
ctes_chiller.setSizingFactor(chiller_resize_factor)
|
565
|
+
runner.registerInfo("Selected chiller has been resized to #{chiller_resize_factor * 100}% of autosized " \
|
566
|
+
'capacity.')
|
567
|
+
else
|
568
|
+
ctes_chiller.setReferenceCapacity(
|
569
|
+
chiller_resize_factor * ctes_chiller.referenceCapacity.to_f
|
570
|
+
)
|
571
|
+
runner.registerInfo("Selected chiller has been resized to #{chiller_resize_factor * 100}% of original " \
|
572
|
+
'(hardsized) capacity.')
|
573
|
+
end
|
574
|
+
|
575
|
+
## Modify Loop Settings---------------------------------------------------------------------------------------------
|
576
|
+
|
577
|
+
# Adjust minimum loop temperature
|
578
|
+
ctes_loop.setMinimumLoopTemperature(chg_sp)
|
579
|
+
runner.registerInfo("Selected loop minimum temperature was adjusted to #{chg_sp.round(2)} C " \
|
580
|
+
"(#{(chg_sp * 1.8) + 32} F).")
|
581
|
+
|
582
|
+
# Adjust plant load distribution scheme
|
583
|
+
if ctes_loop.loadDistributionScheme != 'SequentialLoad'
|
584
|
+
ctes_loop.setLoadDistributionScheme('SequentialLoad')
|
585
|
+
runner.registerInfo("Selected loop load distribution scheme was set to 'SequentialLoad'.")
|
586
|
+
end
|
587
|
+
|
588
|
+
# Adjust loop design temperature difference
|
589
|
+
if ctes_loop.sizingPlant.loopDesignTemperatureDifference.to_f != delta_t
|
590
|
+
ctes_loop.sizingPlant.setLoopDesignTemperatureDifference(delta_t)
|
591
|
+
runner.registerInfo("Selected loop design temperature difference was adjusted to #{delta_t.round(2)} C " \
|
592
|
+
"(#{(delta_t * 1.8)} F).")
|
593
|
+
end
|
594
|
+
|
595
|
+
# Adjust loop gylcol solution percentage and set glycol - if necessary
|
596
|
+
if ctes_loop.fluidType == 'Water'
|
597
|
+
ctes_loop.setFluidType('EthyleneGlycol')
|
598
|
+
ctes_loop.setGlycolConcentration(25)
|
599
|
+
runner.registerInfo('Selected loop working fluid changed to ethylene glycol at a 25% concentration.')
|
600
|
+
elsif ctes_loop.glycolConcentration < 25
|
601
|
+
runner.registerInfo('Selected loop gylycol concentration is less than 25%. Consider increasing to 25-30%.')
|
602
|
+
end
|
603
|
+
|
604
|
+
# Adjust loop to two-way common pipe simulation - if necessary
|
605
|
+
if ctes_loop.commonPipeSimulation != 'TwoWayCommonPipe'
|
606
|
+
ctes_loop.setCommonPipeSimulation('TwoWayCommonPipe')
|
607
|
+
runner.registerInfo("Selected loop common pipe simulation changed to 'TwoWayCommonPipe'.")
|
608
|
+
|
609
|
+
# Add setpoint manager at inlet of demand loop (req'd for two-way common pipe sim.)
|
610
|
+
if old # Only applies if old curves are used, regardless of whether new curves are created (old takes precedence)
|
611
|
+
loop_sp_node = ctes_loop.loopTemperatureSetpointNode
|
612
|
+
loop_sp_mgrs = loop_sp_node.setpointManagers
|
613
|
+
loop_sp_mgrs.each do |spm|
|
614
|
+
if spm.controlVariable == 'Temperature'
|
615
|
+
demand_sp_mgr = spm.clone.to_SetpointManagerScheduled.get
|
616
|
+
end
|
617
|
+
end
|
618
|
+
demand_sp_mgr.addToNode(ctes_loop.demandInletNode)
|
619
|
+
runner.registerInfo('Original loop temperature setpoint manager duplicated and added to demand loop inlet node.')
|
620
|
+
end
|
621
|
+
|
622
|
+
end
|
623
|
+
|
624
|
+
## Create CTES Hardware---------------------------------------------------------------------------------------------
|
625
|
+
|
626
|
+
# Create ice tank (aka ctes)
|
627
|
+
ctes = OpenStudio::Model::ThermalStorageIceDetailed.new(model)
|
628
|
+
ctes.setCapacity(storage_capacity)
|
629
|
+
ctes.setThawProcessIndicator(melt_indicator)
|
630
|
+
|
631
|
+
# Add ice tank to loop based on user-selected objective option and upstream device
|
632
|
+
if objective == 'Full Storage'
|
633
|
+
# Full storage places the ice tank upstream of the chiller with no user option to change.
|
634
|
+
ctes.addToNode(ctes_chiller.supplyInletModelObject.get.to_Node.get)
|
635
|
+
elsif objective == 'Partial Storage' && upstream == 'Storage'
|
636
|
+
ctes.addToNode(ctes_chiller.supplyInletModelObject.get.to_Node.get)
|
637
|
+
elsif objective == 'Partial Storage' && upstream == 'Chiller'
|
638
|
+
ctes.addToNode(ctes_chiller.supplyOutletModelObject.get.to_Node.get)
|
639
|
+
end
|
640
|
+
|
641
|
+
## Create New Schedules if Necessary--------------------------------------------------------------------------------
|
642
|
+
#-------------------------------------------------------------------------------------------------------------------
|
643
|
+
if new
|
644
|
+
## Check for Schedule Type Limits and Create if Needed------------------------------------------------------------
|
645
|
+
if model.getModelObjectByName('OnOff').get.initialized
|
646
|
+
sched_limits_onoff = model.getModelObjectByName('OnOff').get.to_ScheduleTypeLimits.get
|
647
|
+
else
|
648
|
+
sched_limits_onoff = OpenStudio::Model::ScheduleTypeLimits.new(model)
|
649
|
+
sched_limits_onoff.setName('OnOff')
|
650
|
+
sched_limits_onoff.setNumericType('Discrete')
|
651
|
+
sched_limits_onoff.setUnitType('Availability')
|
652
|
+
sched_limits_onoff.setLowerLimitValue(0.0)
|
653
|
+
sched_limits_onoff.setUpperLimitValue(1.0)
|
654
|
+
end
|
655
|
+
|
656
|
+
if model.getModelObjectByName('Temperature').get.initialized
|
657
|
+
sched_limits_temp = model.getModelObjectByName('Temperature').get.to_ScheduleTypeLimits.get
|
658
|
+
if sched_limits_temp.lowerLimitValue.to_f > chg_sp
|
659
|
+
sched_limits_temp.setLowerLimitValue(chg_sp)
|
660
|
+
end
|
661
|
+
else
|
662
|
+
sched_limits_temp = OpenStudio::Model::ScheduleTypeLimits.new(model)
|
663
|
+
sched_limits_temp.setName('Temperature')
|
664
|
+
sched_limits_temp.setNumericType('Continuous')
|
665
|
+
sched_limits_temp.setUnitType('Temperature')
|
666
|
+
end
|
667
|
+
|
668
|
+
## Create Schedules-----------------------------------------------------------------------------------------------
|
669
|
+
|
670
|
+
# Create key-value sets based on user inputs for charge/discharge times
|
671
|
+
# cs = charge start, ce = charge end, ds = discharge start, de = discharge end
|
672
|
+
|
673
|
+
# Set chiller and ice discharge setpoints for partial storage configs
|
674
|
+
if objective == 'Full Storage'
|
675
|
+
chiller_setpoint = loop_sp
|
676
|
+
ctes_setpoint = loop_sp
|
677
|
+
elsif objective == 'Partial Storage'
|
678
|
+
if upstream == 'Chiller'
|
679
|
+
chiller_setpoint = inter_sp
|
680
|
+
ctes_setpoint = loop_sp
|
681
|
+
elsif upstream == 'Storage'
|
682
|
+
chiller_setpoint = loop_sp
|
683
|
+
ctes_setpoint = inter_sp
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
# Handle overnight charging and discharging
|
688
|
+
if ce < cs
|
689
|
+
midnight_av = [24, 1]
|
690
|
+
midnight_chiller = [24, chg_sp]
|
691
|
+
midnight_ctes = [24, loop_sp]
|
692
|
+
elsif de < ds
|
693
|
+
midnight_av = [24, 1]
|
694
|
+
midnight_chiller = [24, chiller_setpoint]
|
695
|
+
midnight_ctes = [24, ctes_setpoint]
|
696
|
+
else
|
697
|
+
midnight_av = [24, 0]
|
698
|
+
midnight_chiller = [24, loop_sp]
|
699
|
+
midnight_ctes = [24, 99]
|
700
|
+
end
|
701
|
+
|
702
|
+
# Availablity k-v sets for CTES
|
703
|
+
wk_av = [[cs, 0], [ce, 1], [ds, 0], [de, 1], midnight_av].sort
|
704
|
+
wknd_av = [[cs, 0], [ce, 1], midnight_av].sort
|
705
|
+
|
706
|
+
# Temperature k-v sets for CTES
|
707
|
+
wk_ctes = [[cs, 99], [ce, loop_sp], [ds, 99], [de, ctes_setpoint], midnight_ctes].sort
|
708
|
+
wknd_ctes = [[cs, 99], [ce, loop_sp], midnight_ctes].sort
|
709
|
+
|
710
|
+
# Temperature k-v set for Chiller
|
711
|
+
wk_chiller = [[cs, loop_sp], [ce, chg_sp], [ds, loop_sp], [de, chiller_setpoint], midnight_chiller].sort
|
712
|
+
wknd_chiller = [[cs, loop_sp], [ce, chg_sp], midnight_chiller].sort
|
713
|
+
|
714
|
+
# Apply weekends modifer if necessary
|
715
|
+
if wknds
|
716
|
+
wknd_av = wk_av
|
717
|
+
wknd_ctes = wk_ctes
|
718
|
+
wknd_chiller = wk_chiller
|
719
|
+
end
|
720
|
+
|
721
|
+
# Create ice availability schedule
|
722
|
+
ruleset_name = 'Ice Availability Schedule (New)'
|
723
|
+
winter_design_day = [[24, 0]]
|
724
|
+
summer_design_day = wk_av
|
725
|
+
default_day = ['AllDays'] + [[24, 0]]
|
726
|
+
rules = []
|
727
|
+
rules << ['Weekend', ctes_season, 'Sat/Sun'] + wknd_av
|
728
|
+
rules << ['Summer Weekday', ctes_season, 'Mon/Tue/Wed/Thu/Fri'] + wk_av
|
729
|
+
options_ctes = { 'name' => ruleset_name,
|
730
|
+
'winter_design_day' => winter_design_day,
|
731
|
+
'summer_design_day' => summer_design_day,
|
732
|
+
'default_day' => default_day,
|
733
|
+
'rules' => rules }
|
734
|
+
ctes_av_new = OsLib_Schedules.createComplexSchedule(model, options_ctes)
|
735
|
+
ctes_av_new.setScheduleTypeLimits(sched_limits_onoff)
|
736
|
+
|
737
|
+
# Create ctes setpoint temperature schedule
|
738
|
+
ruleset_name = "#{ctes.name} Setpoint Schedule (New)"
|
739
|
+
winter_design_day = [[24, 99]]
|
740
|
+
summer_design_day = wk_ctes
|
741
|
+
default_day = ['AllDays'] + [[24, 99]]
|
742
|
+
rules = []
|
743
|
+
rules << ['Weekend', ctes_season, 'Sat/Sun'] + wknd_ctes
|
744
|
+
rules << ['Summer Weekday', ctes_season, 'Mon/Tue/Wed/Thu/Fri'] + wk_ctes
|
745
|
+
options_ctes_ctes = { 'name' => ruleset_name,
|
746
|
+
'winter_design_day' => winter_design_day,
|
747
|
+
'summer_design_day' => summer_design_day,
|
748
|
+
'default_day' => default_day,
|
749
|
+
'rules' => rules }
|
750
|
+
ctes_sch_new = OsLib_Schedules.createComplexSchedule(model, options_ctes_ctes)
|
751
|
+
ctes_sch_new.setScheduleTypeLimits(sched_limits_temp)
|
752
|
+
|
753
|
+
# Create chiller setpoint temperature schedule
|
754
|
+
ruleset_name = "#{ctes_chiller.name} Setpoint Schedule (New)"
|
755
|
+
winter_design_day = [[24, loop_sp]]
|
756
|
+
summer_design_day = wk_chiller
|
757
|
+
default_day = ['AllDays'] + [[24, loop_sp]]
|
758
|
+
rules = []
|
759
|
+
rules << ['Weekend', ctes_season, 'Sat/Sun'] + wknd_chiller
|
760
|
+
rules << ['Summer Weekday', ctes_season, 'Mon/Tue/Wed/Thu/Fri'] + wk_chiller
|
761
|
+
options_ctes_chiller = { 'name' => ruleset_name,
|
762
|
+
'winter_design_day' => winter_design_day,
|
763
|
+
'summer_design_day' => summer_design_day,
|
764
|
+
'default_day' => default_day,
|
765
|
+
'rules' => rules }
|
766
|
+
chill_sch_new = OsLib_Schedules.createComplexSchedule(model, options_ctes_chiller)
|
767
|
+
chill_sch_new.setScheduleTypeLimits(sched_limits_temp)
|
768
|
+
|
769
|
+
# Create loop setpoint temperature schedule - if new = true
|
770
|
+
if new
|
771
|
+
ruleset_name = "#{ctes_loop.name} Setpoint Schedule (New)"
|
772
|
+
options_ctes_loop = { 'name' => ruleset_name,
|
773
|
+
'winterTimeValuePairs' => [[24, loop_sp]],
|
774
|
+
'summerTimeValuePairs' => [[24, loop_sp]],
|
775
|
+
'defaultTimeValuePairs' => [[24, loop_sp]] }
|
776
|
+
loop_sch_new = OsLib_Schedules.createSimpleSchedule(model, options_ctes_loop)
|
777
|
+
loop_sch_new.setScheduleTypeLimits(sched_limits_temp)
|
778
|
+
end
|
779
|
+
|
780
|
+
# Register info about new schedule objects
|
781
|
+
runner.registerInfo("The following schedules were added to the model:\n" \
|
782
|
+
" * #{ctes_av_new.name}\n" \
|
783
|
+
" * #{ctes_sch_new.name}\n" \
|
784
|
+
" * #{chill_sch_new.name}\n" \
|
785
|
+
" * #{loop_sch_new.name}")
|
786
|
+
|
787
|
+
if old
|
788
|
+
runner.registerInfo('However, these schedules are not used in favor of those pre-defined by the user.')
|
789
|
+
end
|
790
|
+
|
791
|
+
end
|
792
|
+
# end of new schedule build-----------------------------------------------------------------------------------------
|
793
|
+
#-------------------------------------------------------------------------------------------------------------------
|
794
|
+
|
795
|
+
## Create Component Setpoint Objects--------------------------------------------------------------------------------
|
796
|
+
|
797
|
+
if old
|
798
|
+
ctes_avail_sched = model.getScheduleRulesetByName(ctes_av).get
|
799
|
+
ctes_temp_sched = model.getScheduleRulesetByName(ctes_sch).get
|
800
|
+
chill_temp_sched = model.getScheduleRulesetByName(chill_sch).get
|
801
|
+
elsif new
|
802
|
+
ctes_avail_sched = ctes_av_new
|
803
|
+
ctes_temp_sched = ctes_sch_new
|
804
|
+
chill_temp_sched = chill_sch_new
|
805
|
+
end
|
806
|
+
|
807
|
+
# Apply ice availability schedule
|
808
|
+
ctes.setAvailabilitySchedule(ctes_avail_sched)
|
809
|
+
|
810
|
+
# Add component setpoint manager for ice tank
|
811
|
+
ctes_sp_mgr = OpenStudio::Model::SetpointManagerScheduled.new(model, ctes_temp_sched)
|
812
|
+
ctes_sp_mgr.addToNode(ctes.outletModelObject.get.to_Node.get)
|
813
|
+
ctes_sp_mgr.setName("#{ctes.name} Setpoint Manager")
|
814
|
+
|
815
|
+
# Add component setpoint manager for ctes chiller
|
816
|
+
chill_sp_mgr = OpenStudio::Model::SetpointManagerScheduled.new(model, chill_temp_sched)
|
817
|
+
chill_sp_mgr.addToNode(ctes_chiller.supplyOutletModelObject.get.to_Node.get)
|
818
|
+
chill_sp_mgr.setName("#{ctes_chiller.name} Setpoint Manager")
|
819
|
+
|
820
|
+
# Replace existing loop setpoint manager - if new = true and old = false
|
821
|
+
if new && !old
|
822
|
+
loop_sp_node = ctes_loop.loopTemperatureSetpointNode
|
823
|
+
loop_sp_mgrs = loop_sp_node.setpointManagers
|
824
|
+
loop_sp_mgrs.each do |spm|
|
825
|
+
next unless ['Temperature', 'MinimumTemperature', 'MaximumTemperature'].include?(spm.controlVariable)
|
826
|
+
spm.disconnect
|
827
|
+
runner.registerInfo("Selected loop temperature setpoint manager '#{spm.name}' " \
|
828
|
+
"with control variable '#{spm.controlVariable}' was disconnected.")
|
829
|
+
end
|
830
|
+
|
831
|
+
loop_sp_mgr = OpenStudio::Model::SetpointManagerScheduled.new(model, loop_sch_new)
|
832
|
+
loop_sp_mgr.addToNode(loop_sp_node)
|
833
|
+
loop_sp_mgr.setName("#{ctes_loop.name} Setpoint Manager (New)")
|
834
|
+
|
835
|
+
demand_sp_mgr = loop_sp_mgr.clone.to_SetpointManagerScheduled.get
|
836
|
+
demand_sp_mgr.addToNode(ctes_loop.demandInletNode)
|
837
|
+
demand_sp_mgr.setName("#{ctes_loop.name} Demand Side Setpoint Manager (New)")
|
838
|
+
end
|
839
|
+
|
840
|
+
# Register info about new schedule objects
|
841
|
+
runner.registerInfo('The following component temperature setpoint managers were added to the ' \
|
842
|
+
"model:\n" \
|
843
|
+
" * #{ctes_sp_mgr.name}\n" \
|
844
|
+
" * #{chill_sp_mgr.name}")
|
845
|
+
|
846
|
+
if old # Old Schedules always take precedence, even if new ones are also created
|
847
|
+
runner.registerInfo("The following schedules ared used in the model:\n" \
|
848
|
+
" * #{ctes_avail_sched.name}\n" \
|
849
|
+
" * #{ctes_temp_sched.name}\n" \
|
850
|
+
" * #{chill_temp_sched.name}")
|
851
|
+
runner.registerInfo('The following loop temperature setpoint manager was added to the ' \
|
852
|
+
"model:\n" \
|
853
|
+
" * #{demand_sp_mgr.name}")
|
854
|
+
elsif new && !old
|
855
|
+
runner.registerInfo('The following loop temperature setpoint managers were added to the ' \
|
856
|
+
"model:\n" \
|
857
|
+
" * #{loop_sp_mgr.name}\n" \
|
858
|
+
" * #{demand_sp_mgr.name}")
|
859
|
+
end
|
860
|
+
|
861
|
+
## Create EMS Components to Control Load on Upstream (Priority) Device----------------------------------------------
|
862
|
+
|
863
|
+
# Flag value indicating that a chiller limiter is required or DR Test is Activated
|
864
|
+
if chiller_limit != 1.0 || dr == true
|
865
|
+
|
866
|
+
# Set up EMS output
|
867
|
+
output_ems = model.getOutputEnergyManagementSystem
|
868
|
+
output_ems.setActuatorAvailabilityDictionaryReporting('Verbose')
|
869
|
+
output_ems.setInternalVariableAvailabilityDictionaryReporting('Verbose')
|
870
|
+
output_ems.setEMSRuntimeLanguageDebugOutputLevel('None')
|
871
|
+
|
872
|
+
runner.registerInfo("A #{(chiller_limit * 100).round(2)}% capacity limit has been placed on the chiller " \
|
873
|
+
'during ice discharge. EMS scripting is employed to actuate this control via chiller ' \
|
874
|
+
'outlet setpoint. ')
|
875
|
+
|
876
|
+
# Internal and Global Variable(s)
|
877
|
+
# Chiller Nominal Capacity
|
878
|
+
evar_chiller_cap = OpenStudio::Model::EnergyManagementSystemInternalVariable.new(model, 'Chiller Nominal Capacity')
|
879
|
+
evar_chiller_cap.setInternalDataIndexKeyName(ctes_chiller.name.to_s)
|
880
|
+
evar_chiller_cap.setName('CTES_Chiller_Capacity')
|
881
|
+
|
882
|
+
# Chiller Limited Capacity for Ice Discharge Period - Empty Global Variable
|
883
|
+
evar_chiller_limit = OpenStudio::Model::EnergyManagementSystemGlobalVariable.new(model, 'Chiller_Limited_Capacity')
|
884
|
+
|
885
|
+
# Instances of Chiller Limit Application - Empty Global Variable
|
886
|
+
evar_limit_counter = OpenStudio::Model::EnergyManagementSystemGlobalVariable.new(model, 'Limit_Counter')
|
887
|
+
|
888
|
+
# Ice Tank thermal storage capacity - Empty Global Variable
|
889
|
+
evar_tes_cap = OpenStudio::Model::EnergyManagementSystemGlobalVariable.new(model, 'TES_Cap')
|
890
|
+
|
891
|
+
# Max Delta-T for Chiller De-Rate - Empty Global Variable
|
892
|
+
dt_ems = OpenStudio::Model::EnergyManagementSystemGlobalVariable.new(model, 'DT_Max')
|
893
|
+
|
894
|
+
# DR In-Progress Flag - Empty Global Variable
|
895
|
+
dr_flag = OpenStudio::Model::EnergyManagementSystemGlobalVariable.new(model, 'DR_Flag')
|
896
|
+
|
897
|
+
# Sensor(s)
|
898
|
+
# Evaporator Entering Water Temperature
|
899
|
+
eewt = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Chiller Evaporator Inlet Temperature')
|
900
|
+
eewt.setName('EEWT')
|
901
|
+
eewt.setKeyName(ctes_chiller.name.to_s)
|
902
|
+
|
903
|
+
# Evaporator Leaving Water Temperature
|
904
|
+
elwt = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Chiller Evaporator Outlet Temperature')
|
905
|
+
elwt.setName('ELWT')
|
906
|
+
elwt.setKeyName(ctes_chiller.name.to_s)
|
907
|
+
|
908
|
+
# Evaporator Leave Water Temperature Setpoint
|
909
|
+
elwt_sp = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Schedule Value')
|
910
|
+
elwt_sp.setName('SP')
|
911
|
+
elwt_sp.setKeyName(chill_temp_sched.name.to_s)
|
912
|
+
|
913
|
+
# Supply Water Temperature Setpoint
|
914
|
+
swt_sp = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'System Node Setpoint Temperature')
|
915
|
+
swt_sp.setName('SWT_SP')
|
916
|
+
swt_sp.setKeyName(ctes_loop.supplyOutletNode.name.to_s)
|
917
|
+
|
918
|
+
# Ice Tank Availability Schedule
|
919
|
+
av_sp = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Schedule Value')
|
920
|
+
av_sp.setName('ICE_AV')
|
921
|
+
av_sp.setKeyName(ctes.availabilitySchedule.get.name.to_s)
|
922
|
+
|
923
|
+
# Ice Tank Leaving Water Temperature
|
924
|
+
ilwt = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'System Node Temperature')
|
925
|
+
ilwt.setName('ILWT')
|
926
|
+
ilwt.setKeyName(ctes.outletModelObject.get.name.to_s)
|
927
|
+
|
928
|
+
# Ice Tank State of Charge
|
929
|
+
soc = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Ice Thermal Storage End Fraction')
|
930
|
+
soc.setName('SOC')
|
931
|
+
soc.setKeyName(ctes.name.to_s)
|
932
|
+
|
933
|
+
# Actuator(s)
|
934
|
+
# Evaporator Leaving Water Temperature Septoint Node Actuator
|
935
|
+
elwt = OpenStudio::Model::EnergyManagementSystemActuator.new(ctes_chiller.supplyOutletModelObject.get,
|
936
|
+
'System Node Setpoint', 'Temperature Setpoint')
|
937
|
+
elwt.setName('ELWT_SP')
|
938
|
+
end
|
939
|
+
|
940
|
+
if chiller_limit != 1
|
941
|
+
# Program(s)
|
942
|
+
# Apply Chiller Capacity Limit During Ice Discharge
|
943
|
+
chiller_limit_program = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
|
944
|
+
chiller_limit_program.setName('Chiller_Limiter')
|
945
|
+
body = <<-EMS
|
946
|
+
IF ( ICE_AV == 1 ) && ( SP >= SWT_SP ) && ( DR_Flag <> 1 )
|
947
|
+
IF ( EEWT - SP ) > DT_Max
|
948
|
+
SET ELWT_SP = ( EEWT - DT_Max )
|
949
|
+
SET Limit_Counter = ( Limit_Counter + ( SystemTimeStep / ZoneTimeStep ) )
|
950
|
+
ELSE
|
951
|
+
SET ELWT_SP = SP
|
952
|
+
ENDIF
|
953
|
+
ELSE
|
954
|
+
SET ELWT_SP = SP
|
955
|
+
ENDIF
|
956
|
+
EMS
|
957
|
+
chiller_limit_program.setBody(body)
|
958
|
+
|
959
|
+
# Determine Capacity Limit of the Chiller in Watts (Also initializes limit counter)
|
960
|
+
chiller_limit_calculation = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
|
961
|
+
chiller_limit_calculation.setName('Chiller_Limit_Calc')
|
962
|
+
body = <<-EMS
|
963
|
+
SET Chiller_Limited_Capacity = ( #{chiller_limit} * CTES_Chiller_Capacity )
|
964
|
+
SET Limit_Counter = 0
|
965
|
+
SET DR_Flag = 0
|
966
|
+
SET DT_Max = #{dt_max}
|
967
|
+
SET TES_Cap = #{storage_capacity}
|
968
|
+
EMS
|
969
|
+
chiller_limit_calculation.setBody(body)
|
970
|
+
|
971
|
+
# Program Calling Manager(s)
|
972
|
+
chiller_limit_pcm = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model)
|
973
|
+
chiller_limit_pcm.setName('Chiller_Limiter_CallMgr')
|
974
|
+
chiller_limit_pcm.setCallingPoint('InsideHVACSystemIterationLoop')
|
975
|
+
chiller_limit_pcm.addProgram(chiller_limit_program)
|
976
|
+
|
977
|
+
chiller_limit_calc_pcm = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model)
|
978
|
+
chiller_limit_calc_pcm.setName('Chiller_Limit_Calc_CallMgr')
|
979
|
+
chiller_limit_calc_pcm.setCallingPoint('BeginNewEnvironment')
|
980
|
+
chiller_limit_calc_pcm.addProgram(chiller_limit_calculation)
|
981
|
+
|
982
|
+
# EMS Output Variable(s)
|
983
|
+
eout_chiller_cap = OpenStudio::Model::EnergyManagementSystemOutputVariable.new(model, evar_chiller_cap)
|
984
|
+
eout_chiller_cap.setName('Chiller Nominal Capacity')
|
985
|
+
|
986
|
+
eout_chiller_limit = OpenStudio::Model::EnergyManagementSystemOutputVariable.new(model, evar_chiller_limit)
|
987
|
+
eout_chiller_limit.setName('Chiller Limited Capacity')
|
988
|
+
|
989
|
+
eout_limit_counter = OpenStudio::Model::EnergyManagementSystemOutputVariable.new(model, evar_limit_counter)
|
990
|
+
eout_limit_counter.setName('Chiller Limit Counter')
|
991
|
+
|
992
|
+
eout_tes_cap = OpenStudio::Model::EnergyManagementSystemOutputVariable.new(model, evar_tes_cap)
|
993
|
+
eout_tes_cap.setName('Ice Thermal Storage Capacity')
|
994
|
+
end
|
995
|
+
|
996
|
+
## DR EVENT TESTER EMS --------------------------
|
997
|
+
## Add Demand Response Event Tester if Applicable (EMS Controller Override)-----------------------------------------
|
998
|
+
|
999
|
+
if dr
|
1000
|
+
|
1001
|
+
# Create EMS Script that:
|
1002
|
+
# => 1. Determines if DR Event has been triggered (inspects date/time)
|
1003
|
+
# => 2. Actuates full storage if in a Load Shed DR event
|
1004
|
+
# => 3. Actuates ice charging/chiller @ max if in a Load Add DR event
|
1005
|
+
# => 4. Allows staged chiller ramp if ice runs out (if selected by user)
|
1006
|
+
|
1007
|
+
# Create DR EMS Program
|
1008
|
+
dr_program = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
|
1009
|
+
dr_program.setName('Demand_Response_Pgm')
|
1010
|
+
|
1011
|
+
# Define Program Script Based on Permission of Chiller to Operate to Meet Load
|
1012
|
+
if dr_chill && dr_add_shed == 'Shed' # Chiller is permitted to pick up unmet load
|
1013
|
+
body = <<-EMS
|
1014
|
+
IF ( Month == #{dr_mon} ) && ( DayOfMonth == #{dr_day} )
|
1015
|
+
IF ( CurrentTime > #{dr_time} ) && ( CurrentTime <= #{dr_time + dr_dur} )
|
1016
|
+
SET DR_Flag = 1
|
1017
|
+
IF ( ILWT - SWT_SP < 0.05 )
|
1018
|
+
SET ELWT_SP = EEWT
|
1019
|
+
ELSEIF ( ILWT - SWT_SP <= 0.33 * DT_Max )
|
1020
|
+
SET ELWT_SP = EEWT - ( 0.33 * DT_Max )
|
1021
|
+
ELSEIF ( ILWT - SWT_SP <= 0.67 * DT_Max )
|
1022
|
+
SET ELWT_SP = EEWT - ( 0.67 * DT_Max )
|
1023
|
+
ELSE
|
1024
|
+
SET ELWT_SP = ( EEWT - DT_Max )
|
1025
|
+
SET Limit_Counter = ( Limit_Counter + ( SystemTimeStep / ZoneTimeStep ) )
|
1026
|
+
ENDIF
|
1027
|
+
ELSEIF ( DR_Flag == 1 )
|
1028
|
+
SET DR_Flag = 0
|
1029
|
+
SET ELWT_SP = SP
|
1030
|
+
ENDIF
|
1031
|
+
ENDIF
|
1032
|
+
EMS
|
1033
|
+
dr_program.setBody(body)
|
1034
|
+
elsif !dr_chill && dr_add_shed == 'Shed' # Chiller is not permitted to pick up unmet load when ice is deficient
|
1035
|
+
body = <<-EMS
|
1036
|
+
IF ( Month == #{dr_mon} ) && ( DayOfMonth == #{dr_day} )
|
1037
|
+
IF ( CurrentTime > #{dr_time} ) && ( CurrentTime <= #{dr_time + dr_dur} )
|
1038
|
+
SET DR_Flag = 1
|
1039
|
+
SET ELWT_SP = EEWT + 10.0
|
1040
|
+
ELSEIF ( DR_Flag == 1 )
|
1041
|
+
SET DR_Flag = 0
|
1042
|
+
ENDIF
|
1043
|
+
ENDIF
|
1044
|
+
EMS
|
1045
|
+
dr_program.setBody(body)
|
1046
|
+
elsif dr_add_shed == 'Add'
|
1047
|
+
body = <<-EMS
|
1048
|
+
IF ( Month == #{dr_mon} ) && ( DayOfMonth == #{dr_day} )
|
1049
|
+
IF ( CurrentTime > #{dr_time} ) && ( CurrentTime <= #{dr_time + dr_dur} )
|
1050
|
+
SET DR_Flag = 1
|
1051
|
+
IF ( SOC < 0.99 ) && ( ICE_AV == 1 )
|
1052
|
+
SET ELWT_SP = #{chg_sp}
|
1053
|
+
ELSEIF SOC > 0.95
|
1054
|
+
SET ELWT_SP = SWT_SP
|
1055
|
+
ENDIF
|
1056
|
+
ELSEIF ( DR_Flag == 1 )
|
1057
|
+
SET DR_Flag = 0
|
1058
|
+
ENDIF
|
1059
|
+
ENDIF
|
1060
|
+
EMS
|
1061
|
+
dr_program.setBody(body)
|
1062
|
+
end
|
1063
|
+
|
1064
|
+
# Create DR EMS Program Calling Manager
|
1065
|
+
dr_pcm = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model)
|
1066
|
+
dr_pcm.setName('Demand_Response_PCM')
|
1067
|
+
dr_pcm.setCallingPoint('InsideHVACSystemIterationLoop')
|
1068
|
+
dr_pcm.addProgram(dr_program)
|
1069
|
+
|
1070
|
+
# EMS Output Variable(s)
|
1071
|
+
eout_drflag = OpenStudio::Model::EnergyManagementSystemOutputVariable.new(model, dr_flag)
|
1072
|
+
eout_drflag.setName('Demand Response Flag')
|
1073
|
+
end
|
1074
|
+
## END DR EVENT TESTER EMS ---------------------
|
1075
|
+
|
1076
|
+
## Add Output Variables and Meters----------------------------------------------------------------------------------
|
1077
|
+
|
1078
|
+
# Identify existing output variables
|
1079
|
+
vars = model.getOutputVariables
|
1080
|
+
var_names = []
|
1081
|
+
vars.each do |v|
|
1082
|
+
var_names << v.variableName
|
1083
|
+
end
|
1084
|
+
|
1085
|
+
# List names of desired output variables
|
1086
|
+
ovar_names = ['Ice Thermal Storage Cooling Rate',
|
1087
|
+
'Ice Thermal Storage Cooling Charge Rate',
|
1088
|
+
'Ice Thermal Storage Cooling Discharge Rate',
|
1089
|
+
'Ice Thermal Storage Cooling Charge Energy',
|
1090
|
+
'Ice Thermal Storage Cooling Discharge Energy',
|
1091
|
+
'Ice Thermal Storage Cooling Discharge Energy',
|
1092
|
+
'Ice Thermal Storage End Fraction',
|
1093
|
+
'Ice Thermal Storage On Coil Fraction',
|
1094
|
+
'Ice Thermal Storage Mass Flow Rate',
|
1095
|
+
'Ice Thermal Storage Tank Mass Flow Rate',
|
1096
|
+
'Ice Thermal Storage Bypass Mass Flow Rate',
|
1097
|
+
'Ice Thermal Storage Fluid Inlet Temperature',
|
1098
|
+
'Ice Thermal Storage Tank Outlet Temperature',
|
1099
|
+
'Ice Thermal Storage Blended Outlet Temperature',
|
1100
|
+
'Ice Thermal Storage Ancillary Electric Power',
|
1101
|
+
'Ice Thermal Storage Ancillary Electric Energy',
|
1102
|
+
'Chiller COP',
|
1103
|
+
'Chiller Cycling Ratio',
|
1104
|
+
'Chiller Part Load Ratio',
|
1105
|
+
'Chiller Electric Power',
|
1106
|
+
'Chiller Electric Energy',
|
1107
|
+
'Chiller Evaporator Cooling Rate',
|
1108
|
+
'Chiller Evaporator Cooling Energy',
|
1109
|
+
'Chiller Condenser Heat Transfer Rate',
|
1110
|
+
'Chiller Condenser Heat Transfer Energy',
|
1111
|
+
'Chiller False Load Heat Transfer Rate',
|
1112
|
+
'Chiller False Load Heat Transfer Energy',
|
1113
|
+
'Chiller Evaporator Inlet Temperature',
|
1114
|
+
'Chiller Evaporator Outlet Temperature',
|
1115
|
+
'Chiller Evaporator Mass Flow Rate',
|
1116
|
+
'Site Outdoor Air Drybulb Temperature',
|
1117
|
+
'Site Outdoor Air Wetbulb Temperature']
|
1118
|
+
|
1119
|
+
# Create new output variables if they do not already exist
|
1120
|
+
ovars = []
|
1121
|
+
ovar_names.each do |nm|
|
1122
|
+
# if !var_names.include?(nm)
|
1123
|
+
ovars << OpenStudio::Model::OutputVariable.new(nm, model)
|
1124
|
+
# end
|
1125
|
+
end
|
1126
|
+
|
1127
|
+
# Create output variable for loop demand inlet temperature
|
1128
|
+
v = OpenStudio::Model::OutputVariable.new('System Node Temperature', model)
|
1129
|
+
v.setKeyValue(ctes_loop.demandInletNode.name.to_s)
|
1130
|
+
ovars << v
|
1131
|
+
|
1132
|
+
# Create output variable for loop demand outlet temperature
|
1133
|
+
v = OpenStudio::Model::OutputVariable.new('System Node Temperature', model)
|
1134
|
+
v.setKeyValue(ctes_loop.demandOutletNode.name.to_s)
|
1135
|
+
ovars << v
|
1136
|
+
|
1137
|
+
# Create output variable for loop supply inlet temperature
|
1138
|
+
v = OpenStudio::Model::OutputVariable.new('System Node Temperature', model)
|
1139
|
+
v.setKeyValue(ctes_loop.supplyInletNode.name.to_s)
|
1140
|
+
ovars << v
|
1141
|
+
|
1142
|
+
# Create output variable for loop supply outlet temperature
|
1143
|
+
v = OpenStudio::Model::OutputVariable.new('System Node Temperature', model)
|
1144
|
+
v.setKeyValue(ctes_loop.supplyOutletNode.name.to_s)
|
1145
|
+
ovars << v
|
1146
|
+
|
1147
|
+
# Create output variable for chiller inlet temperature
|
1148
|
+
v = OpenStudio::Model::OutputVariable.new('System Node Temperature', model)
|
1149
|
+
v.setKeyValue(ctes_chiller.supplyInletModelObject.get.name.to_s)
|
1150
|
+
ovars << v
|
1151
|
+
|
1152
|
+
# Create output variable for chiller outlet temperature
|
1153
|
+
v = OpenStudio::Model::OutputVariable.new('System Node Temperature', model)
|
1154
|
+
v.setKeyValue(ctes_chiller.supplyOutletModelObject.get.name.to_s)
|
1155
|
+
ovars << v
|
1156
|
+
|
1157
|
+
# Create output variable for ice tank inlet temperature
|
1158
|
+
v = OpenStudio::Model::OutputVariable.new('System Node Temperature', model)
|
1159
|
+
v.setKeyValue(ctes.inletModelObject.get.name.to_s)
|
1160
|
+
ovars << v
|
1161
|
+
|
1162
|
+
# Create output variable for ice tank outlet temperature
|
1163
|
+
v = OpenStudio::Model::OutputVariable.new('System Node Temperature', model)
|
1164
|
+
v.setKeyValue(ctes.outletModelObject.get.name.to_s)
|
1165
|
+
ovars << v
|
1166
|
+
|
1167
|
+
# Create output variables for the new operating schedules
|
1168
|
+
v = OpenStudio::Model::OutputVariable.new('Schedule Value', model)
|
1169
|
+
v.setKeyValue(ctes_avail_sched.name.to_s)
|
1170
|
+
ovars << v
|
1171
|
+
|
1172
|
+
v = OpenStudio::Model::OutputVariable.new('Schedule Value', model)
|
1173
|
+
v.setKeyValue(ctes_temp_sched.name.to_s)
|
1174
|
+
ovars << v
|
1175
|
+
|
1176
|
+
v = OpenStudio::Model::OutputVariable.new('Schedule Value', model)
|
1177
|
+
v.setKeyValue(chill_temp_sched.name.to_s)
|
1178
|
+
ovars << v
|
1179
|
+
|
1180
|
+
# Create output variable for plant loop setpoint temperature - if new = true
|
1181
|
+
if new
|
1182
|
+
v = OpenStudio::Model::OutputVariable.new('Schedule Value', model)
|
1183
|
+
v.setKeyValue(loop_sch_new.name.to_s)
|
1184
|
+
ovars << v
|
1185
|
+
end
|
1186
|
+
|
1187
|
+
# Create output variables for ice discharge performance curve
|
1188
|
+
v = OpenStudio::Model::OutputVariable.new('Performance Curve Input Variable 1 Value', model)
|
1189
|
+
v.setKeyValue(ctes.dischargingCurve.name.to_s)
|
1190
|
+
v.setName('Discharge Curve Input Value 1')
|
1191
|
+
ovars << v
|
1192
|
+
|
1193
|
+
v = OpenStudio::Model::OutputVariable.new('Performance Curve Input Variable 2 Value', model)
|
1194
|
+
v.setKeyValue(ctes.dischargingCurve.name.to_s)
|
1195
|
+
v.setName('Discharge Curve Input Value 2')
|
1196
|
+
ovars << v
|
1197
|
+
|
1198
|
+
v = OpenStudio::Model::OutputVariable.new('Performance Curve Output Value', model)
|
1199
|
+
v.setKeyValue(ctes.dischargingCurve.name.to_s)
|
1200
|
+
v.setName('Discharge Curve Output Value')
|
1201
|
+
ovars << v
|
1202
|
+
|
1203
|
+
# Create output variables for ice charge performance curve
|
1204
|
+
v = OpenStudio::Model::OutputVariable.new('Performance Curve Input Variable 1 Value', model)
|
1205
|
+
v.setKeyValue(ctes.chargingCurve.name.to_s)
|
1206
|
+
v.setName('Charge Curve Input Value 1')
|
1207
|
+
ovars << v
|
1208
|
+
|
1209
|
+
v = OpenStudio::Model::OutputVariable.new('Performance Curve Input Variable 2 Value', model)
|
1210
|
+
v.setKeyValue(ctes.chargingCurve.name.to_s)
|
1211
|
+
v.setName('Charge Curve Input Value 2')
|
1212
|
+
ovars << v
|
1213
|
+
|
1214
|
+
v = OpenStudio::Model::OutputVariable.new('Performance Curve Output Value', model)
|
1215
|
+
v.setKeyValue(ctes.chargingCurve.name.to_s)
|
1216
|
+
v.setName('Charge Curve Output Value')
|
1217
|
+
ovars << v
|
1218
|
+
|
1219
|
+
# Create output variables for chiller performance
|
1220
|
+
v = OpenStudio::Model::OutputVariable.new('Performance Curve Output Value', model)
|
1221
|
+
v.setKeyValue(ctes_chiller.coolingCapacityFunctionOfTemperature.name.to_s)
|
1222
|
+
v.setName('Charge Curve Output Value')
|
1223
|
+
ovars << v
|
1224
|
+
|
1225
|
+
v = OpenStudio::Model::OutputVariable.new('Performance Curve Output Value', model)
|
1226
|
+
v.setKeyValue(ctes_chiller.electricInputToCoolingOutputRatioFunctionOfTemperature.name.to_s)
|
1227
|
+
v.setName('Charge Curve Output Value')
|
1228
|
+
ovars << v
|
1229
|
+
|
1230
|
+
v = OpenStudio::Model::OutputVariable.new('Performance Curve Output Value', model)
|
1231
|
+
v.setKeyValue(ctes_chiller.electricInputToCoolingOutputRatioFunctionOfPLR.name.to_s)
|
1232
|
+
v.setName('Charge Curve Output Value')
|
1233
|
+
ovars << v
|
1234
|
+
|
1235
|
+
if chiller_limit != 1.0 # flag for EMS use, following EMS vars only exist if previous script ran
|
1236
|
+
|
1237
|
+
# Create output variable for chiller nominal capacity (from EMS Output Variable)
|
1238
|
+
v = OpenStudio::Model::OutputVariable.new(eout_chiller_cap.name.to_s, model)
|
1239
|
+
v.setName("#{ctes_chiller.name} Nominal Capacity [W]")
|
1240
|
+
v.setVariableName('Chiller Nominal Capacity')
|
1241
|
+
ovars << v
|
1242
|
+
|
1243
|
+
# Create output variable for chiller limited capacity (from EMS Output Variable)
|
1244
|
+
v = OpenStudio::Model::OutputVariable.new(eout_chiller_limit.name.to_s, model)
|
1245
|
+
v.setName("#{ctes_chiller.name} Limited Capacity")
|
1246
|
+
v.setVariableName('Chiller Limited Capacity')
|
1247
|
+
ovars << v
|
1248
|
+
|
1249
|
+
# Create output variable for chiller limit counter (from EMS Output Variable)
|
1250
|
+
v = OpenStudio::Model::OutputVariable.new(eout_limit_counter.name.to_s, model)
|
1251
|
+
v.setName("#{ctes_chiller.name} Limit Counter [Zone Timesteps]")
|
1252
|
+
v.setVariableName('Chiller Limit Counter')
|
1253
|
+
ovars << v
|
1254
|
+
|
1255
|
+
end
|
1256
|
+
|
1257
|
+
# Create output variable for Demand Response Flag (from EMS Output Variable)
|
1258
|
+
if dr
|
1259
|
+
|
1260
|
+
v = OpenStudio::Model::OutputVariable.new(eout_drflag.name.to_s, model)
|
1261
|
+
v.setName('Demand Response Event Flag')
|
1262
|
+
v.setVariableName('Demand Response Flag')
|
1263
|
+
ovars << v
|
1264
|
+
|
1265
|
+
end
|
1266
|
+
|
1267
|
+
# Create output variable for TES Capacity (from EMS Global Variable)
|
1268
|
+
v = OpenStudio::Model::OutputVariable.new(eout_tes_cap.name.to_s, model)
|
1269
|
+
v.setName("#{ctes.name} Ice Thermal Storage Capacity [GJ]")
|
1270
|
+
v.setVariableName('Ice Thermal Storage Capacity')
|
1271
|
+
ovars << v
|
1272
|
+
|
1273
|
+
# Set variable reporting frequency for newly created output variables
|
1274
|
+
ovars.each do |var|
|
1275
|
+
var.setReportingFrequency(report_freq)
|
1276
|
+
end
|
1277
|
+
|
1278
|
+
# Register info about new output variables
|
1279
|
+
runner.registerInfo("#{ovars.size} chiller and ice storage output variables were added to the model.")
|
1280
|
+
|
1281
|
+
# Create new energy/specific end use meters
|
1282
|
+
omet_names = ['Pumps:Electricity',
|
1283
|
+
'Fans:Electricity',
|
1284
|
+
'Cooling:Electricity',
|
1285
|
+
'Electricity:HVAC',
|
1286
|
+
'Electricity:Plant',
|
1287
|
+
'Electricity:Building',
|
1288
|
+
'Electricity:Facility']
|
1289
|
+
|
1290
|
+
omet_names.each do |nm|
|
1291
|
+
omet = OpenStudio::Model::OutputMeter.new(model)
|
1292
|
+
omet.setName(nm)
|
1293
|
+
omet.setReportingFrequency(report_freq)
|
1294
|
+
omet.setMeterFileOnly(false)
|
1295
|
+
omet.setCumulative(false)
|
1296
|
+
end
|
1297
|
+
|
1298
|
+
# Register info about new output meters
|
1299
|
+
runner.registerInfo("#{omet_names.size} output meters were added to the model.")
|
1300
|
+
|
1301
|
+
## Report Final Condition of Model----------------------------------------------------------------------------------
|
1302
|
+
total_storage = model.getThermalStorageIceDetaileds.size
|
1303
|
+
runner.registerFinalCondition("The model finished with #{total_storage} ice energy storage device(s).")
|
1304
|
+
|
1305
|
+
true
|
1306
|
+
end
|
1307
|
+
end
|
1308
|
+
|
1309
|
+
# register the measure to be used by the application
|
1310
|
+
AddIceStorageToPlantLoopForLoadFlexibility.new.registerWithApplication
|