openstudio-load-flexibility-measures 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +36 -36
- data/.rubocop.yml +6 -9
- data/CHANGELOG.html +75 -75
- data/CHANGELOG.md +59 -37
- data/Gemfile +31 -7
- data/Jenkinsfile +11 -0
- data/README.md +53 -42
- data/Rakefile +15 -15
- data/doc_templates/LICENSE.md +26 -26
- data/doc_templates/README.md.erb +41 -41
- data/doc_templates/copyright_erb.txt +35 -35
- data/doc_templates/copyright_js.txt +3 -3
- data/doc_templates/copyright_ruby.txt +33 -33
- data/lib/measures/add_central_ice_storage/LICENSE.md +26 -26
- data/lib/measures/add_central_ice_storage/README.md +264 -264
- data/lib/measures/add_central_ice_storage/README.md.erb +41 -41
- data/lib/measures/add_central_ice_storage/measure.rb +1325 -1324
- data/lib/measures/add_central_ice_storage/measure.xml +503 -503
- data/lib/measures/add_central_ice_storage/resources/OsLib_Schedules.rb +171 -173
- data/lib/measures/add_central_ice_storage/tests/add_central_ice_storage_test.rb +203 -203
- data/lib/measures/add_central_ice_storage/tests/ice_test_model.osm +21523 -21523
- data/lib/measures/add_hpwh/LICENSE.md +26 -26
- data/lib/measures/add_hpwh/README.md +186 -186
- data/lib/measures/add_hpwh/README.md.erb +41 -41
- data/lib/measures/add_hpwh/docs/Flexible Domestic Hot Water Implementation Guide.pdf +0 -0
- data/lib/measures/add_hpwh/measure.rb +663 -647
- data/lib/measures/add_hpwh/measure.xml +402 -397
- data/lib/measures/add_hpwh/tests/SmallHotel-2A.osm +42893 -42893
- data/lib/measures/add_hpwh/tests/{add_hphw_test.rb → add_hpwh_test.rb} +137 -98
- data/lib/measures/add_packaged_ice_storage/LICENSE.md +26 -26
- data/lib/measures/add_packaged_ice_storage/README.html +185 -185
- data/lib/measures/add_packaged_ice_storage/README.md +189 -189
- data/lib/measures/add_packaged_ice_storage/measure.rb +694 -691
- data/lib/measures/add_packaged_ice_storage/measure.xml +245 -245
- data/lib/measures/add_packaged_ice_storage/resources/TESCurves.idf +1059 -1059
- data/lib/measures/add_packaged_ice_storage/tests/MeasureTest.osm +9507 -9507
- data/lib/measures/add_packaged_ice_storage/tests/add_packaged_ice_storage_test.rb +96 -96
- data/lib/openstudio/load_flexibility_measures/version.rb +40 -40
- data/lib/openstudio/load_flexibility_measures.rb +50 -50
- data/openstudio-load-flexibility-measures.gemspec +33 -32
- metadata +27 -27
@@ -1,691 +1,694 @@
|
|
1
|
-
# *******************************************************************************
|
2
|
-
# OpenStudio(R), Copyright (c) 2008-
|
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
|
-
# Revised - KH July 2019
|
37
|
-
# Measure Renamed, License Updated, and Code Cleaned - KH June 2020
|
38
|
-
|
39
|
-
# start the measure
|
40
|
-
class AddPackagedIceStorage < OpenStudio::Measure::EnergyPlusMeasure
|
41
|
-
# human readable name
|
42
|
-
def name
|
43
|
-
'Add Packaged Ice Storage'
|
44
|
-
end
|
45
|
-
|
46
|
-
# human readable description
|
47
|
-
def description
|
48
|
-
'This measure removes the cooling coils in the model and replaces them with packaged air conditioning units with integrated ice storage.'
|
49
|
-
end
|
50
|
-
|
51
|
-
# human readable description of modeling approach
|
52
|
-
def modeler_description
|
53
|
-
"This measure applies to packaged single zone air conditioning systems or packaged variable air volume systems that were originally modeled with CoilSystem:Cooling:DX or AirLoopHVAC:UnitarySystem container objects. It adds a Coil:Cooling:DX:SingleSpeed:ThermalStorage coil object to each user-selected thermal zone and deletes the existing cooling coil.
|
54
|
-
|
55
|
-
Users inputs are accepted for cooling coil size, ice storage size, system control method, modes of operation, and operating schedule.
|
56
|
-
|
57
|
-
The measure requires schedule objects and performance curves from an included resource file TESCurves.idf. Output variables of typical interest are included as well."
|
58
|
-
end
|
59
|
-
|
60
|
-
# define the arguments that the user will input
|
61
|
-
def arguments(workspace)
|
62
|
-
args = OpenStudio::Measure::OSArgumentVector.new
|
63
|
-
|
64
|
-
# # Add a delimiter for clarify
|
65
|
-
# delimiter = OpenStudio::Measure::OSArgument.makeStringArgument('delimiter', false)
|
66
|
-
# delimiter.setDisplayName('Select Coils to Replace:')
|
67
|
-
# delimiter.setDefaultValue('-----------------------------------------------------------------')
|
68
|
-
# args << delimiter
|
69
|
-
|
70
|
-
# get existing dx coils for user selection
|
71
|
-
coils = workspace.getObjectsByType('Coil:Cooling:DX:SingleSpeed'.to_IddObjectType)
|
72
|
-
coils += workspace.getObjectsByType('Coil:Cooling:DX:TwoSpeed'.to_IddObjectType)
|
73
|
-
coilhash = {}
|
74
|
-
c = coils.sort { |a, b| a.getString(0).get <=> b.getString(0).get }
|
75
|
-
c.each do |coil|
|
76
|
-
c_name = coil.name.to_s
|
77
|
-
coilhash[c_name] = true
|
78
|
-
end
|
79
|
-
|
80
|
-
# make boolean argument for selecting cooling coils to replace
|
81
|
-
coilhash.each do |k, v|
|
82
|
-
coil_selection = OpenStudio::Measure::OSArgument.makeBoolArgument(k, true)
|
83
|
-
coil_selection.setDisplayName(k)
|
84
|
-
coil_selection.setDefaultValue(v)
|
85
|
-
coil_selection.setDescription('Replace this coil?')
|
86
|
-
args << coil_selection
|
87
|
-
end
|
88
|
-
|
89
|
-
ice_cap = OpenStudio::Measure::OSArgument.makeStringArgument('ice_cap', true)
|
90
|
-
ice_cap.setDisplayName('Input the ice storage capacity [ton-hours]')
|
91
|
-
ice_cap.setDescription('To specify by coil, in alphabetical order, enter values for each separated by comma.')
|
92
|
-
ice_cap.setDefaultValue('AutoSize')
|
93
|
-
args << ice_cap
|
94
|
-
|
95
|
-
size_mult = OpenStudio::Measure::OSArgument.makeStringArgument('size_mult', false)
|
96
|
-
size_mult.setDisplayName('Enter a sizing multiplier to manually adjust the autosize results for ice tank capacities')
|
97
|
-
size_mult.setDefaultValue('1.0')
|
98
|
-
args << size_mult
|
99
|
-
|
100
|
-
# make argument for control method
|
101
|
-
ctl = OpenStudio::Measure::OSArgument.makeChoiceArgument('ctl', ['ScheduledModes', 'EMSControlled'], true)
|
102
|
-
ctl.setDisplayName('Select ice storage control method')
|
103
|
-
ctl.setDefaultValue('EMSControlled')
|
104
|
-
args << ctl
|
105
|
-
|
106
|
-
# obtain default schedule names in TESCurves.idf. This allows users to manually add schedules to the idf and be able to access them in OS or PAT
|
107
|
-
source_idf = OpenStudio::IdfFile.load(OpenStudio::Path.new(File.dirname(__FILE__)
|
108
|
-
schedules = source_idf.getObjectsByType('Schedule:Compact'.to_IddObjectType)
|
109
|
-
schedule_names = OpenStudio::StringVector.new
|
110
|
-
|
111
|
-
schedules.each do |sch|
|
112
|
-
schedule_names << sch.name.to_s
|
113
|
-
end
|
114
|
-
|
115
|
-
# make argument for TES operating mode schedule
|
116
|
-
sched = OpenStudio::Measure::OSArgument.makeChoiceArgument('sched', schedule_names, true)
|
117
|
-
sched.setDisplayName('Select the operating mode schedule for the new TES coils')
|
118
|
-
sched.setDescription('Use the fields below to set a simple daily ice charge/discharge schedule. Or, select from pre-defined options.')
|
119
|
-
sched.setDefaultValue('Simple User Sched')
|
120
|
-
args << sched
|
121
|
-
|
122
|
-
# make arguement for weekend TES operation
|
123
|
-
wknd = OpenStudio::Measure::OSArgument.makeBoolArgument('wknd', false)
|
124
|
-
wknd.setDisplayName('Run TES on the weekends')
|
125
|
-
wknd.setDescription('Select if building is occupied on weekends')
|
126
|
-
wknd.setDefaultValue(true)
|
127
|
-
args << wknd
|
128
|
-
|
129
|
-
# make arguments for operating season
|
130
|
-
season = OpenStudio::Measure::OSArgument.makeStringArgument('season', false)
|
131
|
-
season.setDisplayName('Select season during which the ice cooling may be used')
|
132
|
-
season.setDescription('Use MM/DD-MM/DD format')
|
133
|
-
season.setDefaultValue('01/01-12/31')
|
134
|
-
args << season
|
135
|
-
|
136
|
-
# make arguments for simple charging period
|
137
|
-
charge_start = OpenStudio::Measure::OSArgument.makeStringArgument('charge_start', false)
|
138
|
-
charge_start.setDisplayName('Input start time for ice charge (hr:min)')
|
139
|
-
charge_start.setDescription('Use 24 hour format')
|
140
|
-
charge_start.setDefaultValue('22:00')
|
141
|
-
args << charge_start
|
142
|
-
|
143
|
-
charge_end = OpenStudio::Measure::OSArgument.makeStringArgument('charge_end', false)
|
144
|
-
charge_end.setDisplayName('Input end time for ice charge (hr:min)')
|
145
|
-
charge_end.setDescription('Use 24 hour format')
|
146
|
-
charge_end.setDefaultValue('07:00')
|
147
|
-
args << charge_end
|
148
|
-
|
149
|
-
# make arguments for simple discharging period
|
150
|
-
discharge_start = OpenStudio::Measure::OSArgument.makeStringArgument('discharge_start', false)
|
151
|
-
discharge_start.setDisplayName('Input start time for ice discharge (hr:min)')
|
152
|
-
discharge_start.setDescription("Use 24hour format.\nIf 'AutoSize' is selected for ice capacity, these inputs set an ice capacity sizing factor. Otherwise, these only affect discharging schedule.")
|
153
|
-
discharge_start.setDefaultValue('12:00')
|
154
|
-
args << discharge_start
|
155
|
-
|
156
|
-
discharge_end = OpenStudio::Measure::OSArgument.makeStringArgument('discharge_end', false)
|
157
|
-
discharge_end.setDisplayName('Input target end time for ice discharge (hr:min)')
|
158
|
-
discharge_end.setDescription('Use 24 hour format')
|
159
|
-
discharge_end.setDefaultValue('18:00')
|
160
|
-
args << discharge_end
|
161
|
-
|
162
|
-
args
|
163
|
-
# end the arguments method
|
164
|
-
end
|
165
|
-
|
166
|
-
# define what happens when the measure is run
|
167
|
-
def run(workspace, runner, user_arguments)
|
168
|
-
super(workspace, runner, user_arguments)
|
169
|
-
|
170
|
-
# use the built-in error checking
|
171
|
-
unless runner.validateUserArguments(arguments(workspace), user_arguments)
|
172
|
-
return false
|
173
|
-
end
|
174
|
-
|
175
|
-
# load required TESCurves.idf. This contains all the TES performance curves and default schedules
|
176
|
-
source_idf = OpenStudio::IdfFile.load(OpenStudio::Path.new(File.dirname(__FILE__)
|
177
|
-
|
178
|
-
# workspace.addObjects(idf_obj_vector) does not work here. Add each obj individually.
|
179
|
-
source_idf.objects.each do |o|
|
180
|
-
workspace.addObject(o)
|
181
|
-
end
|
182
|
-
runner.registerInfo("#{source_idf.objects.size} performance curves, schedule objects, and output variables were imported from 'TESCurves.idf'.\n\n")
|
183
|
-
|
184
|
-
# assign user arguments to variables
|
185
|
-
ice_cap = runner.getStringArgumentValue('ice_cap', user_arguments) # ice capacity value (in ton-hours)
|
186
|
-
size_mult = runner.getStringArgumentValue('size_mult', user_arguments) # size multiplier for ice tank capacity - use if autosize is excessively oversizing
|
187
|
-
ctl = runner.getStringArgumentValue('ctl', user_arguments) # control method (schedule or EMS)
|
188
|
-
sched = runner.getStringArgumentValue('sched', user_arguments) # select operating mode schedule (schedule objects located in resources\TESCurves.idf)
|
189
|
-
wknd = runner.getBoolArgumentValue('wknd', user_arguments) # turn tes on/off for weekend operation
|
190
|
-
season = runner.getStringArgumentValue('season', user_arguments) # set operating season for Simple User Sched
|
191
|
-
charge_start = runner.getStringArgumentValue('charge_start', user_arguments) # time ice charging begins
|
192
|
-
charge_end = runner.getStringArgumentValue('charge_end', user_arguments) # time ice charging ends
|
193
|
-
discharge_start = runner.getStringArgumentValue('discharge_start', user_arguments) # time ice discharge begins
|
194
|
-
discharge_end = runner.getStringArgumentValue('discharge_end', user_arguments) # time ice discharge ends
|
195
|
-
|
196
|
-
# retrieve user selected coils and assign to vector
|
197
|
-
coils = workspace.getObjectsByType('Coil:Cooling:DX:SingleSpeed'.to_IddObjectType)
|
198
|
-
coils += workspace.getObjectsByType('Coil:Cooling:DX:TwoSpeed'.to_IddObjectType)
|
199
|
-
coilhash = {}
|
200
|
-
c = coils.sort { |a, b| a.getString(0).get <=> b.getString(0).get }
|
201
|
-
c.each do |coil|
|
202
|
-
c_name = coil.name.to_s
|
203
|
-
coilhash[c_name] = true
|
204
|
-
end
|
205
|
-
|
206
|
-
coil_selection = []
|
207
|
-
coilhash.each do |k, v|
|
208
|
-
temp_var = runner.getBoolArgumentValue(k, user_arguments)
|
209
|
-
coil_selection << k if temp_var
|
210
|
-
end
|
211
|
-
|
212
|
-
# create other useful variables
|
213
|
-
replacement_count = 0 # tracks number of coils replaced by measure
|
214
|
-
time_size_factor = '' # sets Storage Capacity Sizing Factor {hr}
|
215
|
-
discharge_cop = '63.6' # default COP for Ice Discharge
|
216
|
-
curve_d_shr_ft = 'Discharge-SHR-fT-NREL' # default curve for sensible heat ratio f(T) during ice discharge
|
217
|
-
|
218
|
-
# convert string time values into floats for math comparisons
|
219
|
-
# ds/de = discharge start/end, cs/ce = charge start/end
|
220
|
-
ds = discharge_start.split(':')[0].to_f + (discharge_start.split(':')[1].to_f / 0.6)
|
221
|
-
de = discharge_end.split(':')[0].to_f + (discharge_end.split(':')[1].to_f / 0.6)
|
222
|
-
cs = charge_start.split(':')[0].to_f + (charge_start.split(':')[1].to_f / 0.6)
|
223
|
-
ce = charge_end.split(':')[0].to_f + (charge_end.split(':')[1].to_f / 0.6)
|
224
|
-
|
225
|
-
# #Check User Inputs and Define Variables
|
226
|
-
hardcaps = []
|
227
|
-
if ice_cap != 'AutoSize'
|
228
|
-
if ice_cap == ''
|
229
|
-
runner.registerWarning("No ice capacity was entered for 'User Input' selection, 'AutoSize' was used instead.")
|
230
|
-
ice_cap = 'AutoSize'
|
231
|
-
elsif ice_cap.split(',').size > 1
|
232
|
-
runner.registerInfo('Ice storage tanks will be hardsized based on user inputs, assigned alphabetically.')
|
233
|
-
ice_cap.split(',').each { |i| hardcaps.push((i.to_f * 0.0126608).to_s) }
|
234
|
-
while hardcaps.size != coil_selection.size
|
235
|
-
runner.registerInfo("No user-defined thermal storage capacity for #{coil_selection[hardcaps.size]}; unit will be AutoSized.")
|
236
|
-
hardcaps.push('AutoSize')
|
237
|
-
end
|
238
|
-
else
|
239
|
-
ice_cap = (ice_cap.to_f * 0.0126608).to_s # convert units from ton-hours to GJ
|
240
|
-
end
|
241
|
-
elsif sched == 'Simple User Sched'
|
242
|
-
time_size_factor = ((de - ds) * size_mult.to_f).to_s
|
243
|
-
elsif sched == 'TES Sched 2: 1-5 Peak'
|
244
|
-
time_size_factor = (4.0 * size_mult.to_f).to_s
|
245
|
-
elsif sched == 'TES Sched 3: 3-8 Peak'
|
246
|
-
time_size_factor = (5.0 * size_mult.to_f).to_s
|
247
|
-
elsif sched == 'TES Sched 4: GSS-T'
|
248
|
-
time_size_factor = (3.0 * size_mult.to_f).to_s
|
249
|
-
else
|
250
|
-
time_size_factor = (4.0 * size_mult.to_f).to_s # sets default time size factor to 4 hours
|
251
|
-
end
|
252
|
-
|
253
|
-
# Check user schedule inputs and build schedule
|
254
|
-
if sched == 'Simple User Sched'
|
255
|
-
|
256
|
-
# find empty user input schedule object from TESCurves.idf import
|
257
|
-
user_schedules = workspace.getObjectsByName('Simple User Sched')
|
258
|
-
user_schedule = user_schedules[0]
|
259
|
-
|
260
|
-
# check ice discharge times to ensure end occurs after start. Exit gracefully if it doesn't.
|
261
|
-
if de < ds
|
262
|
-
runner.registerError('Ice discharge end time occurs before the start time. If ice discharge is desired overnight, create a schedule object in ../resources/TESCurves.idf. Measure was not applied.')
|
263
|
-
return false
|
264
|
-
end
|
265
|
-
|
266
|
-
# sets charge and discharge mode ** May be modified if Cool_Charge or Cool_Discharge modes become available
|
267
|
-
charge_mode = 4
|
268
|
-
discharge_mode = 5
|
269
|
-
|
270
|
-
# format user input for cooling season values
|
271
|
-
czn = season.split(/[\s-]/)
|
272
|
-
cool_start = czn[0].to_s
|
273
|
-
cool_end = czn[-1].to_s
|
274
|
-
|
275
|
-
# set cooling season start periods
|
276
|
-
a = 4 # index variable to ensure schedule is built properly under various conditions
|
277
|
-
c = 3 # index variable to help ensure a weekday-only schedule is properly built
|
278
|
-
|
279
|
-
if cool_start != '01/01'
|
280
|
-
user_schedule.setString(2, "Through: #{cool_start}")
|
281
|
-
user_schedule.setString(3, 'For: AllDays')
|
282
|
-
user_schedule.setString(4, 'Until: 24:00')
|
283
|
-
user_schedule.setString(5, '1')
|
284
|
-
user_schedule.setString(7, 'For: AllDays')
|
285
|
-
a = 8
|
286
|
-
c = 7
|
287
|
-
end
|
288
|
-
|
289
|
-
# build user defined schedule object
|
290
|
-
if cs > ce
|
291
|
-
# assign times to schedule fields
|
292
|
-
user_schedule.setString(a, "Until: #{charge_end}")
|
293
|
-
user_schedule.setString(a + 1, charge_mode.to_s)
|
294
|
-
user_schedule.setString(a + 2, "Until: #{discharge_start}")
|
295
|
-
user_schedule.setString(a + 3, '1')
|
296
|
-
user_schedule.setString(a + 4, "Until: #{discharge_end}")
|
297
|
-
user_schedule.setString(a + 5, discharge_mode.to_s)
|
298
|
-
user_schedule.setString(a + 6, "Until: #{charge_start}")
|
299
|
-
user_schedule.setString(a + 7, '1')
|
300
|
-
user_schedule.setString(a + 8, 'Until: 24:00')
|
301
|
-
user_schedule.setString(a + 9, charge_mode.to_s)
|
302
|
-
b = a + 10
|
303
|
-
elsif charge_start != '00:00'
|
304
|
-
user_schedule.setString(a, "Until: #{charge_end}")
|
305
|
-
user_schedule.setString(a + 1, charge_mode.to_s)
|
306
|
-
user_schedule.setString(a + 2, "Until: #{discharge_start}")
|
307
|
-
user_schedule.setString(a + 3, '1')
|
308
|
-
user_schedule.setString(a + 4, "Until: #{discharge_end}")
|
309
|
-
user_schedule.setString(a + 5, discharge_mode.to_s)
|
310
|
-
user_schedule.setString(a + 6, 'Until: 24:00')
|
311
|
-
user_schedule.setString(a + 7, '1')
|
312
|
-
b = a + 8
|
313
|
-
else
|
314
|
-
user_schedule.setString(a, "Until: #{charge_end}")
|
315
|
-
user_schedule.setString(a + 1, charge_mode.to_s)
|
316
|
-
user_schedule.setString(a + 2, "Until: #{discharge_start}")
|
317
|
-
user_schedule.setString(a + 3, '1')
|
318
|
-
user_schedule.setString(a + 4, "Until: #{discharge_end}")
|
319
|
-
user_schedule.setString(a + 5, discharge_mode.to_s)
|
320
|
-
user_schedule.setString(a + 6, 'Until: 24:00')
|
321
|
-
user_schedule.setString(a + 7, '1')
|
322
|
-
b = a + 8
|
323
|
-
end
|
324
|
-
|
325
|
-
# make weekend modification if necessary
|
326
|
-
unless wknd
|
327
|
-
user_schedule.setString(c, 'For: WeekDays')
|
328
|
-
user_schedule.setString(b, 'For: Weekends')
|
329
|
-
user_schedule.setString(b + 1, "Until: #{charge_end}")
|
330
|
-
user_schedule.setString(b + 2, charge_mode.to_s)
|
331
|
-
user_schedule.setString(b + 3, 'Until: 24:00')
|
332
|
-
user_schedule.setString(b + 4, '1')
|
333
|
-
b += 5
|
334
|
-
end
|
335
|
-
|
336
|
-
# complete cooling season schedule if not through 12/31
|
337
|
-
if cool_end != '12/31'
|
338
|
-
user_schedule.setString(c - 1, "Through: #{cool_end}")
|
339
|
-
user_schedule.setString(b, 'Through: 12/31')
|
340
|
-
user_schedule.setString(b + 1, 'For: AllDays')
|
341
|
-
user_schedule.setString(b + 2, 'Until: 24:00')
|
342
|
-
user_schedule.setString(b + 3, '1')
|
343
|
-
end
|
344
|
-
end
|
345
|
-
|
346
|
-
# find objects of interest in the model (used to identify container objects, air loops, and thermal zones)
|
347
|
-
cooling_coil_systems = workspace.getObjectsByType('CoilSystem:Cooling:DX'.to_IddObjectType)
|
348
|
-
air_loops = workspace.getObjectsByType('AirLoopHVAC'.to_IddObjectType)
|
349
|
-
branches = workspace.getObjectsByType('Branch'.to_IddObjectType)
|
350
|
-
hvac_zone_mixers = workspace.getObjectsByType('AirLoopHVAC:ZoneMixer'.to_IddObjectType)
|
351
|
-
zone_connections = workspace.getObjectsByType('ZoneHVAC:EquipmentConnections'.to_IddObjectType)
|
352
|
-
unitary_generic_obj = workspace.getObjectsByType('AirLoopHVAC:UnitarySystem'.to_IddObjectType)
|
353
|
-
node_lists = workspace.getObjectsByType('NodeList'.to_IddObjectType)
|
354
|
-
|
355
|
-
# create vector of all cooling system container objects
|
356
|
-
cooling_containers = OpenStudio::IdfObjectVector.new
|
357
|
-
cooling_coil_systems.each do |coil_sys|
|
358
|
-
if coil_selection.include?(coil_sys.getString(6).to_s)
|
359
|
-
cooling_containers << coil_sys
|
360
|
-
end
|
361
|
-
end
|
362
|
-
unitary_generic_obj.each do |unitary_sys|
|
363
|
-
if coil_selection.include?(unitary_sys.getString(15).to_s)
|
364
|
-
cooling_containers << unitary_sys
|
365
|
-
end
|
366
|
-
end
|
367
|
-
|
368
|
-
# exit gracefully if original model does not have coilSystem:Cooling objects
|
369
|
-
if cooling_containers.empty?
|
370
|
-
runner.registerError('This measure only operates on the following EnergyPlus container objects: CoilSystem:Cooling:DX and AirLoopHVAC:UnitarySystem. Measure was not applied.')
|
371
|
-
end
|
372
|
-
|
373
|
-
# create TES object string template for use in replacing existing coils; incorporates user input variables
|
374
|
-
new_tes_string =
|
375
|
-
"Coil:Cooling:DX:SingleSpeed:ThermalStorage,
|
376
|
-
NAME PLACEHOLDER, !- Name
|
377
|
-
ALWAYS_ON, !- Availability Schedule Name
|
378
|
-
#{ctl}, !- Operating Mode Control Method
|
379
|
-
#{sched}, !- Operation Mode Control Schedule Name
|
380
|
-
Ice, !- Storage Type
|
381
|
-
, !- User Defined Fluid Type
|
382
|
-
, !- Fluid Storage Volume {m3}
|
383
|
-
AutoSize, !- Ice Storage Capacity {GJ}
|
384
|
-
#{time_size_factor}, !- Storage Capacity Sizing Factor {hr}
|
385
|
-
AMBIENT NODE, !- Storage Tank Ambient Temperature Node Name
|
386
|
-
7.913, !- Storage Tank to Ambient U-value Times Area Heat Transfer Coefficient {W/K}
|
387
|
-
, !- Fluid Storage Tank Rating Temperature {C}
|
388
|
-
AutoSize, !- Rated Evaporator Air Flow Rate {m3/s}
|
389
|
-
EVAP IN NODE, !- Evaporator Air Inlet Node Name
|
390
|
-
EVAP OUT NODE, !- Evaporator Air Outlet Node Name
|
391
|
-
Yes, !- Cooling Only Mode Available
|
392
|
-
AutoSize, !- Cooling Only Mode Rated Total Evaporator Cooling Capacity {W} **IB40 Limits: 10551 W (3 ton) to 70337 W (20 ton)**
|
393
|
-
0.7, !- Cooling Only Mode Rated Sensible Heat Ratio
|
394
|
-
3.23372055845678, !- Cooling Only Mode Rated COP {W/W}
|
395
|
-
Cool-Cap-fT, !- Cooling Only Mode Total Evaporator Cooling Capacity Function of Temperature Curve Name
|
396
|
-
ConstantCubic, !- Cooling Only Mode Total Evaporator Cooling Capacity Function of Flow Fraction Curve Name
|
397
|
-
Cool-EIR-fT, !- Cooling Only Mode Energy Input Ratio Function of Temperature Curve Name
|
398
|
-
ConstantCubic, !- Cooling Only Mode Energy Input Ratio Function of Flow Fraction Curve Name
|
399
|
-
Cool-PLF-fPLR, !- Cooling Only Mode Part Load Fraction Correlation Curve Name
|
400
|
-
Cool-SHR-fT, !- Cooling Only Mode Sensible Heat Ratio Function of Temperature Curve Name
|
401
|
-
Cool-SHR-fFF, !- Cooling Only Mode Sensible Heat Ratio Function of Flow Fraction Curve Name
|
402
|
-
No, !- Cooling And Charge Mode Available
|
403
|
-
AutoSize, !- Cooling And Charge Mode Rated Total Evaporator Cooling Capacity
|
404
|
-
1.0, !- Cooling And Charge Mode Capacity Sizing Factor
|
405
|
-
AutoSize, !- Cooling And Charge Mode Rated Storage Charging Capacity
|
406
|
-
0.86, !- Cooling And Charge Mode Storage Capacity Sizing Factor
|
407
|
-
0.7, !- Cooling And Charge Mode Rated Sensible Heat Ratio
|
408
|
-
3.66668443E+00, !- Cooling And Charge Mode Cooling Rated COP
|
409
|
-
2.17, !- Cooling And Charge Mode Charging Rated COP
|
410
|
-
CoolCharge-Cool-Cap-fT, !- Cooling And Charge Mode Total Evaporator Cooling Capacity Function of Temperature Curve Name
|
411
|
-
ConstantCubic, !- Cooling And Charge Mode Total Evaporator Cooling Capacity Function of Flow Fraction Curve Name
|
412
|
-
CoolCharge-Cool-EIR-fT, !- Cooling And Charge Mode Evaporator Energy Input Ratio Function of Temperature Curve Name
|
413
|
-
ConstantCubic, !- Cooling And Charge Mode Evaporator Energy Input Ratio Function of Flow Fraction Curve Name
|
414
|
-
Cool-PLF-fPLR, !- Cooling And Charge Mode Evaporator Part Load Fraction Correlation Curve Name
|
415
|
-
CoolCharge-Charge-Cap-fT,!- Cooling And Charge Mode Storage Charge Capacity Function of Temperature Curve Name
|
416
|
-
ConstantCubic, !- Cooling And Charge Mode Storage Charge Capacity Function of Total Evaporator PLR Curve Name
|
417
|
-
CoolCharge-Charge-EIR-fT,!- Cooling And Charge Mode Storage Energy Input Ratio Function of Temperature Curve Name
|
418
|
-
ConstantCubic, !- Cooling And Charge Mode Storage Energy Input Ratio Function of Flow Fraction Curve Name
|
419
|
-
ConstantCubic, !- Cooling And Charge Mode Storage Energy Part Load Fraction Correlation Curve Name
|
420
|
-
Cool-SHR-fT, !- Cooling And Charge Mode Sensible Heat Ratio Function of Temperature Curve Name
|
421
|
-
Cool-SHR-fFF, !- Cooling And Charge Mode Sensible Heat Ratio Function of Flow Fraction Curve Name
|
422
|
-
No, !- Cooling And Discharge Mode Available
|
423
|
-
, !- Cooling And Discharge Mode Rated Total Evaporator Cooling Capacity {W}
|
424
|
-
, !- Cooling And Discharge Mode Evaporator Capacity Sizing Factor
|
425
|
-
, !- Cooling And Discharge Mode Rated Storage Discharging Capacity {W}
|
426
|
-
, !- Cooling And Discharge Mode Storage Discharge Capacity Sizing Factor
|
427
|
-
, !- Cooling And Discharge Mode Rated Sensible Heat Ratio
|
428
|
-
, !- Cooling And Discharge Mode Cooling Rated COP {W/W}
|
429
|
-
, !- Cooling And Discharge Mode Discharging Rated COP {W/W}
|
430
|
-
, !- Cooling And Discharge Mode Total Evaporator Cooling Capacity Function of Temperature Curve Name
|
431
|
-
, !- Cooling And Discharge Mode Total Evaporator Cooling Capacity Function of Flow Fraction Curve Name
|
432
|
-
, !- Cooling And Discharge Mode Evaporator Energy Input Ratio Function of Temperature Curve Name
|
433
|
-
, !- Cooling And Discharge Mode Evaporator Energy Input Ratio Function of Flow Fraction Curve Name
|
434
|
-
, !- Cooling And Discharge Mode Evaporator Part Load Fraction Correlation Curve Name
|
435
|
-
, !- Cooling And Discharge Mode Storage Discharge Capacity Function of Temperature Curve Name
|
436
|
-
, !- Cooling And Discharge Mode Storage Discharge Capacity Function of Flow Fraction Curve Name
|
437
|
-
, !- Cooling And Discharge Mode Storage Discharge Capacity Function of Total Evaporator PLR Curve Name
|
438
|
-
, !- Cooling And Discharge Mode Storage Energy Input Ratio Function of Temperature Curve Name
|
439
|
-
, !- Cooling And Discharge Mode Storage Energy Input Ratio Function of Flow Fraction Curve Name
|
440
|
-
, !- Cooling And Discharge Mode Storage Energy Part Load Fraction Correlation Curve Name
|
441
|
-
, !- Cooling And Discharge Mode Sensible Heat Ratio Function of Temperature Curve Name
|
442
|
-
, !- Cooling And Discharge Mode Sensible Heat Ratio Function of Flow Fraction Curve Name
|
443
|
-
Yes, !- Charge Only Mode Available
|
444
|
-
AutoSize, !- Charge Only Mode Rated Storage Charging Capacity {W}
|
445
|
-
0.8, !- Charge Only Mode Capacity Sizing Factor
|
446
|
-
3.09, !- Charge Only Mode Charging Rated COP {W/W}
|
447
|
-
ChargeOnly-Cap-fT, !- Charge Only Mode Storage Charge Capacity Function of Temperature Curve Name
|
448
|
-
ChargeOnly-EIR-fT, !- Charge Only Mode Storage Energy Input Ratio Function of Temperature Curve Name
|
449
|
-
Yes, !- Discharge Only Mode Available
|
450
|
-
AutoSize, !- Discharge Only Mode Rated Storage Discharging Capacity {W}
|
451
|
-
1.37, !- Discharge Only Mode Capacity Sizing Factor
|
452
|
-
0.64, !- Discharge Only Mode Rated Sensible Heat Ratio
|
453
|
-
#{discharge_cop}, !- Discharge Only Mode Rated COP {W/W}
|
454
|
-
Discharge-Cap-fT, !- Discharge Only Mode Storage Discharge Capacity Function of Temperature Curve Name
|
455
|
-
Discharge-Cap-fFF, !- Discharge Only Mode Storage Discharge Capacity Function of Flow Fraction Curve Name
|
456
|
-
ConstantBi, !- Discharge Only Mode Energy Input Ratio Function of Temperature Curve Name
|
457
|
-
ConstantCubic, !- Discharge Only Mode Energy Input Ratio Function of Flow Fraction Curve Name
|
458
|
-
ConstantCubic, !- Discharge Only Mode Part Load Fraction Correlation Curve Name
|
459
|
-
#{curve_d_shr_ft}, !- Discharge Only Mode Sensible Heat Ratio Function of Temperature Curve Name
|
460
|
-
Discharge-SHR-fFF, !- Discharge Only Mode Sensible Heat Ratio Function of Flow Fraction Curve Name
|
461
|
-
0.0, !- Ancillary Electric Power {W}
|
462
|
-
2.0, !- Cold Weather Operation Minimum Outdoor Air Temperature {C}
|
463
|
-
0.0, !- Cold Weather Operation Ancillary Power {W}
|
464
|
-
CONDENSER INLET NODE, !- Condenser Air Inlet Node Name
|
465
|
-
CONDENSER OUTLET NODE, !- Condenser Air Outlet Node Name
|
466
|
-
autocalculate, !- Condenser Design Air Flow Rate {m3/s}
|
467
|
-
1.25, !- Condenser Air Flow Sizing Factor
|
468
|
-
AirCooled, !- Condenser Type
|
469
|
-
, !- Evaporative Condenser Effectiveness {dimensionless}
|
470
|
-
, !- Evaporative Condenser Pump Rated Power Consumption {W}
|
471
|
-
, !- Basin Heater Capacity {W/K}
|
472
|
-
, !- Basin Heater Setpoint Temperature {C}
|
473
|
-
, !- Basin Heater Availability Schedule Name
|
474
|
-
, !- Supply Water Storage Tank Name
|
475
|
-
, !- Condensate Collection Water Storage Tank Name
|
476
|
-
, !- Storage Tank Plant Connection Inlet Node Name
|
477
|
-
, !- Storage Tank Plant Connection Outlet Node Name
|
478
|
-
, !- Storage Tank Plant Connection Design Flow Rate {m3/s}
|
479
|
-
, !- Storage Tank Plant Connection Heat Transfer Effectiveness
|
480
|
-
, !- Storage Tank Minimum Operating Limit Fluid Temperature {C}
|
481
|
-
; !- Storage Tank Maximum Operating Limit Fluid Temperature {C}"
|
482
|
-
# end of new TES coil string
|
483
|
-
|
484
|
-
# #Begin Coil Replacement
|
485
|
-
# iterate through all CoilSystem:Cooling objects and replace existing coils with TES coils
|
486
|
-
coil_selection.each do |sel_coil|
|
487
|
-
# get workspace object for selected coil from name
|
488
|
-
sel_coil = workspace.getObjectsByName(sel_coil)[0]
|
489
|
-
ice_cap = hardcaps[replacement_count] unless hardcaps.empty?
|
490
|
-
|
491
|
-
# get coil type in order to find get appropriate field keys
|
492
|
-
# fields of interest: (0 - Max Cap, 1 - Rated COP, 2 - Inlet Node, 3 - Outlet Node)
|
493
|
-
field_names = [] # may not be required. Check scope in Ruby documentation
|
494
|
-
sel_type = sel_coil.iddObject.type.valueDescription.to_s
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
inlet
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
oa_ambient
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
condenser_inlet
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
condenser_outlet
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
utss.
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
'
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
utss.setString(keys[
|
557
|
-
utss.setString(keys[
|
558
|
-
utss.setString(keys[
|
559
|
-
utss.setString(keys[
|
560
|
-
utss.setString(
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
utss.setString(
|
567
|
-
utss.setString(
|
568
|
-
utss.setString(
|
569
|
-
utss.setString(
|
570
|
-
utss.setString(
|
571
|
-
|
572
|
-
utss.setString(
|
573
|
-
|
574
|
-
utss.setString(
|
575
|
-
utss.setString(
|
576
|
-
utss.setString(
|
577
|
-
utss.setString(
|
578
|
-
utss.setString(
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
#
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
new_sched_sensor
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
#
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
new_sensor
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
new_actuator.
|
633
|
-
new_actuator.setString(
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
IF
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
new_pcm.
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
#
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
#
|
684
|
-
runner.
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
1
|
+
# *******************************************************************************
|
2
|
+
# OpenStudio(R), Copyright (c) 2008-2021, 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
|
+
# Revised - KH July 2019
|
37
|
+
# Measure Renamed, License Updated, and Code Cleaned - KH June 2020
|
38
|
+
|
39
|
+
# start the measure
|
40
|
+
class AddPackagedIceStorage < OpenStudio::Measure::EnergyPlusMeasure
|
41
|
+
# human readable name
|
42
|
+
def name
|
43
|
+
'Add Packaged Ice Storage'
|
44
|
+
end
|
45
|
+
|
46
|
+
# human readable description
|
47
|
+
def description
|
48
|
+
'This measure removes the cooling coils in the model and replaces them with packaged air conditioning units with integrated ice storage.'
|
49
|
+
end
|
50
|
+
|
51
|
+
# human readable description of modeling approach
|
52
|
+
def modeler_description
|
53
|
+
"This measure applies to packaged single zone air conditioning systems or packaged variable air volume systems that were originally modeled with CoilSystem:Cooling:DX or AirLoopHVAC:UnitarySystem container objects. It adds a Coil:Cooling:DX:SingleSpeed:ThermalStorage coil object to each user-selected thermal zone and deletes the existing cooling coil.
|
54
|
+
|
55
|
+
Users inputs are accepted for cooling coil size, ice storage size, system control method, modes of operation, and operating schedule.
|
56
|
+
|
57
|
+
The measure requires schedule objects and performance curves from an included resource file TESCurves.idf. Output variables of typical interest are included as well."
|
58
|
+
end
|
59
|
+
|
60
|
+
# define the arguments that the user will input
|
61
|
+
def arguments(workspace)
|
62
|
+
args = OpenStudio::Measure::OSArgumentVector.new
|
63
|
+
|
64
|
+
# # Add a delimiter for clarify
|
65
|
+
# delimiter = OpenStudio::Measure::OSArgument.makeStringArgument('delimiter', false)
|
66
|
+
# delimiter.setDisplayName('Select Coils to Replace:')
|
67
|
+
# delimiter.setDefaultValue('-----------------------------------------------------------------')
|
68
|
+
# args << delimiter
|
69
|
+
|
70
|
+
# get existing dx coils for user selection
|
71
|
+
coils = workspace.getObjectsByType('Coil:Cooling:DX:SingleSpeed'.to_IddObjectType)
|
72
|
+
coils += workspace.getObjectsByType('Coil:Cooling:DX:TwoSpeed'.to_IddObjectType)
|
73
|
+
coilhash = {}
|
74
|
+
c = coils.sort { |a, b| a.getString(0).get <=> b.getString(0).get }
|
75
|
+
c.each do |coil|
|
76
|
+
c_name = coil.name.to_s
|
77
|
+
coilhash[c_name] = true
|
78
|
+
end
|
79
|
+
|
80
|
+
# make boolean argument for selecting cooling coils to replace
|
81
|
+
coilhash.each do |k, v|
|
82
|
+
coil_selection = OpenStudio::Measure::OSArgument.makeBoolArgument(k, true)
|
83
|
+
coil_selection.setDisplayName(k)
|
84
|
+
coil_selection.setDefaultValue(v)
|
85
|
+
coil_selection.setDescription('Replace this coil?')
|
86
|
+
args << coil_selection
|
87
|
+
end
|
88
|
+
|
89
|
+
ice_cap = OpenStudio::Measure::OSArgument.makeStringArgument('ice_cap', true)
|
90
|
+
ice_cap.setDisplayName('Input the ice storage capacity [ton-hours]')
|
91
|
+
ice_cap.setDescription('To specify by coil, in alphabetical order, enter values for each separated by comma.')
|
92
|
+
ice_cap.setDefaultValue('AutoSize')
|
93
|
+
args << ice_cap
|
94
|
+
|
95
|
+
size_mult = OpenStudio::Measure::OSArgument.makeStringArgument('size_mult', false)
|
96
|
+
size_mult.setDisplayName('Enter a sizing multiplier to manually adjust the autosize results for ice tank capacities')
|
97
|
+
size_mult.setDefaultValue('1.0')
|
98
|
+
args << size_mult
|
99
|
+
|
100
|
+
# make argument for control method
|
101
|
+
ctl = OpenStudio::Measure::OSArgument.makeChoiceArgument('ctl', ['ScheduledModes', 'EMSControlled'], true)
|
102
|
+
ctl.setDisplayName('Select ice storage control method')
|
103
|
+
ctl.setDefaultValue('EMSControlled')
|
104
|
+
args << ctl
|
105
|
+
|
106
|
+
# obtain default schedule names in TESCurves.idf. This allows users to manually add schedules to the idf and be able to access them in OS or PAT
|
107
|
+
source_idf = OpenStudio::IdfFile.load(OpenStudio::Path.new("#{File.dirname(__FILE__)}/resources/TESCurves.idf")).get
|
108
|
+
schedules = source_idf.getObjectsByType('Schedule:Compact'.to_IddObjectType)
|
109
|
+
schedule_names = OpenStudio::StringVector.new
|
110
|
+
|
111
|
+
schedules.each do |sch|
|
112
|
+
schedule_names << sch.name.to_s
|
113
|
+
end
|
114
|
+
|
115
|
+
# make argument for TES operating mode schedule
|
116
|
+
sched = OpenStudio::Measure::OSArgument.makeChoiceArgument('sched', schedule_names, true)
|
117
|
+
sched.setDisplayName('Select the operating mode schedule for the new TES coils')
|
118
|
+
sched.setDescription('Use the fields below to set a simple daily ice charge/discharge schedule. Or, select from pre-defined options.')
|
119
|
+
sched.setDefaultValue('Simple User Sched')
|
120
|
+
args << sched
|
121
|
+
|
122
|
+
# make arguement for weekend TES operation
|
123
|
+
wknd = OpenStudio::Measure::OSArgument.makeBoolArgument('wknd', false)
|
124
|
+
wknd.setDisplayName('Run TES on the weekends')
|
125
|
+
wknd.setDescription('Select if building is occupied on weekends')
|
126
|
+
wknd.setDefaultValue(true)
|
127
|
+
args << wknd
|
128
|
+
|
129
|
+
# make arguments for operating season
|
130
|
+
season = OpenStudio::Measure::OSArgument.makeStringArgument('season', false)
|
131
|
+
season.setDisplayName('Select season during which the ice cooling may be used')
|
132
|
+
season.setDescription('Use MM/DD-MM/DD format')
|
133
|
+
season.setDefaultValue('01/01-12/31')
|
134
|
+
args << season
|
135
|
+
|
136
|
+
# make arguments for simple charging period
|
137
|
+
charge_start = OpenStudio::Measure::OSArgument.makeStringArgument('charge_start', false)
|
138
|
+
charge_start.setDisplayName('Input start time for ice charge (hr:min)')
|
139
|
+
charge_start.setDescription('Use 24 hour format')
|
140
|
+
charge_start.setDefaultValue('22:00')
|
141
|
+
args << charge_start
|
142
|
+
|
143
|
+
charge_end = OpenStudio::Measure::OSArgument.makeStringArgument('charge_end', false)
|
144
|
+
charge_end.setDisplayName('Input end time for ice charge (hr:min)')
|
145
|
+
charge_end.setDescription('Use 24 hour format')
|
146
|
+
charge_end.setDefaultValue('07:00')
|
147
|
+
args << charge_end
|
148
|
+
|
149
|
+
# make arguments for simple discharging period
|
150
|
+
discharge_start = OpenStudio::Measure::OSArgument.makeStringArgument('discharge_start', false)
|
151
|
+
discharge_start.setDisplayName('Input start time for ice discharge (hr:min)')
|
152
|
+
discharge_start.setDescription("Use 24hour format.\nIf 'AutoSize' is selected for ice capacity, these inputs set an ice capacity sizing factor. Otherwise, these only affect discharging schedule.")
|
153
|
+
discharge_start.setDefaultValue('12:00')
|
154
|
+
args << discharge_start
|
155
|
+
|
156
|
+
discharge_end = OpenStudio::Measure::OSArgument.makeStringArgument('discharge_end', false)
|
157
|
+
discharge_end.setDisplayName('Input target end time for ice discharge (hr:min)')
|
158
|
+
discharge_end.setDescription('Use 24 hour format')
|
159
|
+
discharge_end.setDefaultValue('18:00')
|
160
|
+
args << discharge_end
|
161
|
+
|
162
|
+
args
|
163
|
+
# end the arguments method
|
164
|
+
end
|
165
|
+
|
166
|
+
# define what happens when the measure is run
|
167
|
+
def run(workspace, runner, user_arguments)
|
168
|
+
super(workspace, runner, user_arguments)
|
169
|
+
|
170
|
+
# use the built-in error checking
|
171
|
+
unless runner.validateUserArguments(arguments(workspace), user_arguments)
|
172
|
+
return false
|
173
|
+
end
|
174
|
+
|
175
|
+
# load required TESCurves.idf. This contains all the TES performance curves and default schedules
|
176
|
+
source_idf = OpenStudio::IdfFile.load(OpenStudio::Path.new("#{File.dirname(__FILE__)}/resources/TESCurves.idf")).get
|
177
|
+
|
178
|
+
# workspace.addObjects(idf_obj_vector) does not work here. Add each obj individually.
|
179
|
+
source_idf.objects.each do |o|
|
180
|
+
workspace.addObject(o)
|
181
|
+
end
|
182
|
+
runner.registerInfo("#{source_idf.objects.size} performance curves, schedule objects, and output variables were imported from 'TESCurves.idf'.\n\n")
|
183
|
+
|
184
|
+
# assign user arguments to variables
|
185
|
+
ice_cap = runner.getStringArgumentValue('ice_cap', user_arguments) # ice capacity value (in ton-hours)
|
186
|
+
size_mult = runner.getStringArgumentValue('size_mult', user_arguments) # size multiplier for ice tank capacity - use if autosize is excessively oversizing
|
187
|
+
ctl = runner.getStringArgumentValue('ctl', user_arguments) # control method (schedule or EMS)
|
188
|
+
sched = runner.getStringArgumentValue('sched', user_arguments) # select operating mode schedule (schedule objects located in resources\TESCurves.idf)
|
189
|
+
wknd = runner.getBoolArgumentValue('wknd', user_arguments) # turn tes on/off for weekend operation
|
190
|
+
season = runner.getStringArgumentValue('season', user_arguments) # set operating season for Simple User Sched
|
191
|
+
charge_start = runner.getStringArgumentValue('charge_start', user_arguments) # time ice charging begins
|
192
|
+
charge_end = runner.getStringArgumentValue('charge_end', user_arguments) # time ice charging ends
|
193
|
+
discharge_start = runner.getStringArgumentValue('discharge_start', user_arguments) # time ice discharge begins
|
194
|
+
discharge_end = runner.getStringArgumentValue('discharge_end', user_arguments) # time ice discharge ends
|
195
|
+
|
196
|
+
# retrieve user selected coils and assign to vector
|
197
|
+
coils = workspace.getObjectsByType('Coil:Cooling:DX:SingleSpeed'.to_IddObjectType)
|
198
|
+
coils += workspace.getObjectsByType('Coil:Cooling:DX:TwoSpeed'.to_IddObjectType)
|
199
|
+
coilhash = {}
|
200
|
+
c = coils.sort { |a, b| a.getString(0).get <=> b.getString(0).get }
|
201
|
+
c.each do |coil|
|
202
|
+
c_name = coil.name.to_s
|
203
|
+
coilhash[c_name] = true
|
204
|
+
end
|
205
|
+
|
206
|
+
coil_selection = []
|
207
|
+
coilhash.each do |k, v|
|
208
|
+
temp_var = runner.getBoolArgumentValue(k, user_arguments)
|
209
|
+
coil_selection << k if temp_var
|
210
|
+
end
|
211
|
+
|
212
|
+
# create other useful variables
|
213
|
+
replacement_count = 0 # tracks number of coils replaced by measure
|
214
|
+
time_size_factor = '' # sets Storage Capacity Sizing Factor {hr}
|
215
|
+
discharge_cop = '63.6' # default COP for Ice Discharge
|
216
|
+
curve_d_shr_ft = 'Discharge-SHR-fT-NREL' # default curve for sensible heat ratio f(T) during ice discharge
|
217
|
+
|
218
|
+
# convert string time values into floats for math comparisons
|
219
|
+
# ds/de = discharge start/end, cs/ce = charge start/end
|
220
|
+
ds = discharge_start.split(':')[0].to_f + (discharge_start.split(':')[1].to_f / 0.6)
|
221
|
+
de = discharge_end.split(':')[0].to_f + (discharge_end.split(':')[1].to_f / 0.6)
|
222
|
+
cs = charge_start.split(':')[0].to_f + (charge_start.split(':')[1].to_f / 0.6)
|
223
|
+
ce = charge_end.split(':')[0].to_f + (charge_end.split(':')[1].to_f / 0.6)
|
224
|
+
|
225
|
+
# #Check User Inputs and Define Variables
|
226
|
+
hardcaps = []
|
227
|
+
if ice_cap != 'AutoSize'
|
228
|
+
if ice_cap == ''
|
229
|
+
runner.registerWarning("No ice capacity was entered for 'User Input' selection, 'AutoSize' was used instead.")
|
230
|
+
ice_cap = 'AutoSize'
|
231
|
+
elsif ice_cap.split(',').size > 1
|
232
|
+
runner.registerInfo('Ice storage tanks will be hardsized based on user inputs, assigned alphabetically.')
|
233
|
+
ice_cap.split(',').each { |i| hardcaps.push((i.to_f * 0.0126608).to_s) }
|
234
|
+
while hardcaps.size != coil_selection.size
|
235
|
+
runner.registerInfo("No user-defined thermal storage capacity for #{coil_selection[hardcaps.size]}; unit will be AutoSized.")
|
236
|
+
hardcaps.push('AutoSize')
|
237
|
+
end
|
238
|
+
else
|
239
|
+
ice_cap = (ice_cap.to_f * 0.0126608).to_s # convert units from ton-hours to GJ
|
240
|
+
end
|
241
|
+
elsif sched == 'Simple User Sched'
|
242
|
+
time_size_factor = ((de - ds) * size_mult.to_f).to_s
|
243
|
+
elsif sched == 'TES Sched 2: 1-5 Peak'
|
244
|
+
time_size_factor = (4.0 * size_mult.to_f).to_s
|
245
|
+
elsif sched == 'TES Sched 3: 3-8 Peak'
|
246
|
+
time_size_factor = (5.0 * size_mult.to_f).to_s
|
247
|
+
elsif sched == 'TES Sched 4: GSS-T'
|
248
|
+
time_size_factor = (3.0 * size_mult.to_f).to_s
|
249
|
+
else
|
250
|
+
time_size_factor = (4.0 * size_mult.to_f).to_s # sets default time size factor to 4 hours
|
251
|
+
end
|
252
|
+
|
253
|
+
# Check user schedule inputs and build schedule
|
254
|
+
if sched == 'Simple User Sched'
|
255
|
+
|
256
|
+
# find empty user input schedule object from TESCurves.idf import
|
257
|
+
user_schedules = workspace.getObjectsByName('Simple User Sched')
|
258
|
+
user_schedule = user_schedules[0]
|
259
|
+
|
260
|
+
# check ice discharge times to ensure end occurs after start. Exit gracefully if it doesn't.
|
261
|
+
if de < ds
|
262
|
+
runner.registerError('Ice discharge end time occurs before the start time. If ice discharge is desired overnight, create a schedule object in ../resources/TESCurves.idf. Measure was not applied.')
|
263
|
+
return false
|
264
|
+
end
|
265
|
+
|
266
|
+
# sets charge and discharge mode ** May be modified if Cool_Charge or Cool_Discharge modes become available
|
267
|
+
charge_mode = 4
|
268
|
+
discharge_mode = 5
|
269
|
+
|
270
|
+
# format user input for cooling season values
|
271
|
+
czn = season.split(/[\s-]/)
|
272
|
+
cool_start = czn[0].to_s
|
273
|
+
cool_end = czn[-1].to_s
|
274
|
+
|
275
|
+
# set cooling season start periods
|
276
|
+
a = 4 # index variable to ensure schedule is built properly under various conditions
|
277
|
+
c = 3 # index variable to help ensure a weekday-only schedule is properly built
|
278
|
+
|
279
|
+
if cool_start != '01/01'
|
280
|
+
user_schedule.setString(2, "Through: #{cool_start}")
|
281
|
+
user_schedule.setString(3, 'For: AllDays')
|
282
|
+
user_schedule.setString(4, 'Until: 24:00')
|
283
|
+
user_schedule.setString(5, '1')
|
284
|
+
user_schedule.setString(7, 'For: AllDays')
|
285
|
+
a = 8
|
286
|
+
c = 7
|
287
|
+
end
|
288
|
+
|
289
|
+
# build user defined schedule object
|
290
|
+
if cs > ce
|
291
|
+
# assign times to schedule fields
|
292
|
+
user_schedule.setString(a, "Until: #{charge_end}")
|
293
|
+
user_schedule.setString(a + 1, charge_mode.to_s)
|
294
|
+
user_schedule.setString(a + 2, "Until: #{discharge_start}")
|
295
|
+
user_schedule.setString(a + 3, '1')
|
296
|
+
user_schedule.setString(a + 4, "Until: #{discharge_end}")
|
297
|
+
user_schedule.setString(a + 5, discharge_mode.to_s)
|
298
|
+
user_schedule.setString(a + 6, "Until: #{charge_start}")
|
299
|
+
user_schedule.setString(a + 7, '1')
|
300
|
+
user_schedule.setString(a + 8, 'Until: 24:00')
|
301
|
+
user_schedule.setString(a + 9, charge_mode.to_s)
|
302
|
+
b = a + 10
|
303
|
+
elsif charge_start != '00:00'
|
304
|
+
user_schedule.setString(a, "Until: #{charge_end}")
|
305
|
+
user_schedule.setString(a + 1, charge_mode.to_s)
|
306
|
+
user_schedule.setString(a + 2, "Until: #{discharge_start}")
|
307
|
+
user_schedule.setString(a + 3, '1')
|
308
|
+
user_schedule.setString(a + 4, "Until: #{discharge_end}")
|
309
|
+
user_schedule.setString(a + 5, discharge_mode.to_s)
|
310
|
+
user_schedule.setString(a + 6, 'Until: 24:00')
|
311
|
+
user_schedule.setString(a + 7, '1')
|
312
|
+
b = a + 8
|
313
|
+
else
|
314
|
+
user_schedule.setString(a, "Until: #{charge_end}")
|
315
|
+
user_schedule.setString(a + 1, charge_mode.to_s)
|
316
|
+
user_schedule.setString(a + 2, "Until: #{discharge_start}")
|
317
|
+
user_schedule.setString(a + 3, '1')
|
318
|
+
user_schedule.setString(a + 4, "Until: #{discharge_end}")
|
319
|
+
user_schedule.setString(a + 5, discharge_mode.to_s)
|
320
|
+
user_schedule.setString(a + 6, 'Until: 24:00')
|
321
|
+
user_schedule.setString(a + 7, '1')
|
322
|
+
b = a + 8
|
323
|
+
end
|
324
|
+
|
325
|
+
# make weekend modification if necessary
|
326
|
+
unless wknd
|
327
|
+
user_schedule.setString(c, 'For: WeekDays')
|
328
|
+
user_schedule.setString(b, 'For: Weekends')
|
329
|
+
user_schedule.setString(b + 1, "Until: #{charge_end}")
|
330
|
+
user_schedule.setString(b + 2, charge_mode.to_s)
|
331
|
+
user_schedule.setString(b + 3, 'Until: 24:00')
|
332
|
+
user_schedule.setString(b + 4, '1')
|
333
|
+
b += 5
|
334
|
+
end
|
335
|
+
|
336
|
+
# complete cooling season schedule if not through 12/31
|
337
|
+
if cool_end != '12/31'
|
338
|
+
user_schedule.setString(c - 1, "Through: #{cool_end}")
|
339
|
+
user_schedule.setString(b, 'Through: 12/31')
|
340
|
+
user_schedule.setString(b + 1, 'For: AllDays')
|
341
|
+
user_schedule.setString(b + 2, 'Until: 24:00')
|
342
|
+
user_schedule.setString(b + 3, '1')
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
# find objects of interest in the model (used to identify container objects, air loops, and thermal zones)
|
347
|
+
cooling_coil_systems = workspace.getObjectsByType('CoilSystem:Cooling:DX'.to_IddObjectType)
|
348
|
+
air_loops = workspace.getObjectsByType('AirLoopHVAC'.to_IddObjectType)
|
349
|
+
branches = workspace.getObjectsByType('Branch'.to_IddObjectType)
|
350
|
+
hvac_zone_mixers = workspace.getObjectsByType('AirLoopHVAC:ZoneMixer'.to_IddObjectType)
|
351
|
+
zone_connections = workspace.getObjectsByType('ZoneHVAC:EquipmentConnections'.to_IddObjectType)
|
352
|
+
unitary_generic_obj = workspace.getObjectsByType('AirLoopHVAC:UnitarySystem'.to_IddObjectType)
|
353
|
+
node_lists = workspace.getObjectsByType('NodeList'.to_IddObjectType)
|
354
|
+
|
355
|
+
# create vector of all cooling system container objects
|
356
|
+
cooling_containers = OpenStudio::IdfObjectVector.new
|
357
|
+
cooling_coil_systems.each do |coil_sys|
|
358
|
+
if coil_selection.include?(coil_sys.getString(6).to_s)
|
359
|
+
cooling_containers << coil_sys
|
360
|
+
end
|
361
|
+
end
|
362
|
+
unitary_generic_obj.each do |unitary_sys|
|
363
|
+
if coil_selection.include?(unitary_sys.getString(15).to_s)
|
364
|
+
cooling_containers << unitary_sys
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
# exit gracefully if original model does not have coilSystem:Cooling objects
|
369
|
+
if cooling_containers.empty?
|
370
|
+
runner.registerError('This measure only operates on the following EnergyPlus container objects: CoilSystem:Cooling:DX and AirLoopHVAC:UnitarySystem. Measure was not applied.')
|
371
|
+
end
|
372
|
+
|
373
|
+
# create TES object string template for use in replacing existing coils; incorporates user input variables
|
374
|
+
new_tes_string =
|
375
|
+
"Coil:Cooling:DX:SingleSpeed:ThermalStorage,
|
376
|
+
NAME PLACEHOLDER, !- Name
|
377
|
+
ALWAYS_ON, !- Availability Schedule Name
|
378
|
+
#{ctl}, !- Operating Mode Control Method
|
379
|
+
#{sched}, !- Operation Mode Control Schedule Name
|
380
|
+
Ice, !- Storage Type
|
381
|
+
, !- User Defined Fluid Type
|
382
|
+
, !- Fluid Storage Volume {m3}
|
383
|
+
AutoSize, !- Ice Storage Capacity {GJ}
|
384
|
+
#{time_size_factor}, !- Storage Capacity Sizing Factor {hr}
|
385
|
+
AMBIENT NODE, !- Storage Tank Ambient Temperature Node Name
|
386
|
+
7.913, !- Storage Tank to Ambient U-value Times Area Heat Transfer Coefficient {W/K}
|
387
|
+
, !- Fluid Storage Tank Rating Temperature {C}
|
388
|
+
AutoSize, !- Rated Evaporator Air Flow Rate {m3/s}
|
389
|
+
EVAP IN NODE, !- Evaporator Air Inlet Node Name
|
390
|
+
EVAP OUT NODE, !- Evaporator Air Outlet Node Name
|
391
|
+
Yes, !- Cooling Only Mode Available
|
392
|
+
AutoSize, !- Cooling Only Mode Rated Total Evaporator Cooling Capacity {W} **IB40 Limits: 10551 W (3 ton) to 70337 W (20 ton)**
|
393
|
+
0.7, !- Cooling Only Mode Rated Sensible Heat Ratio
|
394
|
+
3.23372055845678, !- Cooling Only Mode Rated COP {W/W}
|
395
|
+
Cool-Cap-fT, !- Cooling Only Mode Total Evaporator Cooling Capacity Function of Temperature Curve Name
|
396
|
+
ConstantCubic, !- Cooling Only Mode Total Evaporator Cooling Capacity Function of Flow Fraction Curve Name
|
397
|
+
Cool-EIR-fT, !- Cooling Only Mode Energy Input Ratio Function of Temperature Curve Name
|
398
|
+
ConstantCubic, !- Cooling Only Mode Energy Input Ratio Function of Flow Fraction Curve Name
|
399
|
+
Cool-PLF-fPLR, !- Cooling Only Mode Part Load Fraction Correlation Curve Name
|
400
|
+
Cool-SHR-fT, !- Cooling Only Mode Sensible Heat Ratio Function of Temperature Curve Name
|
401
|
+
Cool-SHR-fFF, !- Cooling Only Mode Sensible Heat Ratio Function of Flow Fraction Curve Name
|
402
|
+
No, !- Cooling And Charge Mode Available
|
403
|
+
AutoSize, !- Cooling And Charge Mode Rated Total Evaporator Cooling Capacity
|
404
|
+
1.0, !- Cooling And Charge Mode Capacity Sizing Factor
|
405
|
+
AutoSize, !- Cooling And Charge Mode Rated Storage Charging Capacity
|
406
|
+
0.86, !- Cooling And Charge Mode Storage Capacity Sizing Factor
|
407
|
+
0.7, !- Cooling And Charge Mode Rated Sensible Heat Ratio
|
408
|
+
3.66668443E+00, !- Cooling And Charge Mode Cooling Rated COP
|
409
|
+
2.17, !- Cooling And Charge Mode Charging Rated COP
|
410
|
+
CoolCharge-Cool-Cap-fT, !- Cooling And Charge Mode Total Evaporator Cooling Capacity Function of Temperature Curve Name
|
411
|
+
ConstantCubic, !- Cooling And Charge Mode Total Evaporator Cooling Capacity Function of Flow Fraction Curve Name
|
412
|
+
CoolCharge-Cool-EIR-fT, !- Cooling And Charge Mode Evaporator Energy Input Ratio Function of Temperature Curve Name
|
413
|
+
ConstantCubic, !- Cooling And Charge Mode Evaporator Energy Input Ratio Function of Flow Fraction Curve Name
|
414
|
+
Cool-PLF-fPLR, !- Cooling And Charge Mode Evaporator Part Load Fraction Correlation Curve Name
|
415
|
+
CoolCharge-Charge-Cap-fT,!- Cooling And Charge Mode Storage Charge Capacity Function of Temperature Curve Name
|
416
|
+
ConstantCubic, !- Cooling And Charge Mode Storage Charge Capacity Function of Total Evaporator PLR Curve Name
|
417
|
+
CoolCharge-Charge-EIR-fT,!- Cooling And Charge Mode Storage Energy Input Ratio Function of Temperature Curve Name
|
418
|
+
ConstantCubic, !- Cooling And Charge Mode Storage Energy Input Ratio Function of Flow Fraction Curve Name
|
419
|
+
ConstantCubic, !- Cooling And Charge Mode Storage Energy Part Load Fraction Correlation Curve Name
|
420
|
+
Cool-SHR-fT, !- Cooling And Charge Mode Sensible Heat Ratio Function of Temperature Curve Name
|
421
|
+
Cool-SHR-fFF, !- Cooling And Charge Mode Sensible Heat Ratio Function of Flow Fraction Curve Name
|
422
|
+
No, !- Cooling And Discharge Mode Available
|
423
|
+
, !- Cooling And Discharge Mode Rated Total Evaporator Cooling Capacity {W}
|
424
|
+
, !- Cooling And Discharge Mode Evaporator Capacity Sizing Factor
|
425
|
+
, !- Cooling And Discharge Mode Rated Storage Discharging Capacity {W}
|
426
|
+
, !- Cooling And Discharge Mode Storage Discharge Capacity Sizing Factor
|
427
|
+
, !- Cooling And Discharge Mode Rated Sensible Heat Ratio
|
428
|
+
, !- Cooling And Discharge Mode Cooling Rated COP {W/W}
|
429
|
+
, !- Cooling And Discharge Mode Discharging Rated COP {W/W}
|
430
|
+
, !- Cooling And Discharge Mode Total Evaporator Cooling Capacity Function of Temperature Curve Name
|
431
|
+
, !- Cooling And Discharge Mode Total Evaporator Cooling Capacity Function of Flow Fraction Curve Name
|
432
|
+
, !- Cooling And Discharge Mode Evaporator Energy Input Ratio Function of Temperature Curve Name
|
433
|
+
, !- Cooling And Discharge Mode Evaporator Energy Input Ratio Function of Flow Fraction Curve Name
|
434
|
+
, !- Cooling And Discharge Mode Evaporator Part Load Fraction Correlation Curve Name
|
435
|
+
, !- Cooling And Discharge Mode Storage Discharge Capacity Function of Temperature Curve Name
|
436
|
+
, !- Cooling And Discharge Mode Storage Discharge Capacity Function of Flow Fraction Curve Name
|
437
|
+
, !- Cooling And Discharge Mode Storage Discharge Capacity Function of Total Evaporator PLR Curve Name
|
438
|
+
, !- Cooling And Discharge Mode Storage Energy Input Ratio Function of Temperature Curve Name
|
439
|
+
, !- Cooling And Discharge Mode Storage Energy Input Ratio Function of Flow Fraction Curve Name
|
440
|
+
, !- Cooling And Discharge Mode Storage Energy Part Load Fraction Correlation Curve Name
|
441
|
+
, !- Cooling And Discharge Mode Sensible Heat Ratio Function of Temperature Curve Name
|
442
|
+
, !- Cooling And Discharge Mode Sensible Heat Ratio Function of Flow Fraction Curve Name
|
443
|
+
Yes, !- Charge Only Mode Available
|
444
|
+
AutoSize, !- Charge Only Mode Rated Storage Charging Capacity {W}
|
445
|
+
0.8, !- Charge Only Mode Capacity Sizing Factor
|
446
|
+
3.09, !- Charge Only Mode Charging Rated COP {W/W}
|
447
|
+
ChargeOnly-Cap-fT, !- Charge Only Mode Storage Charge Capacity Function of Temperature Curve Name
|
448
|
+
ChargeOnly-EIR-fT, !- Charge Only Mode Storage Energy Input Ratio Function of Temperature Curve Name
|
449
|
+
Yes, !- Discharge Only Mode Available
|
450
|
+
AutoSize, !- Discharge Only Mode Rated Storage Discharging Capacity {W}
|
451
|
+
1.37, !- Discharge Only Mode Capacity Sizing Factor
|
452
|
+
0.64, !- Discharge Only Mode Rated Sensible Heat Ratio
|
453
|
+
#{discharge_cop}, !- Discharge Only Mode Rated COP {W/W}
|
454
|
+
Discharge-Cap-fT, !- Discharge Only Mode Storage Discharge Capacity Function of Temperature Curve Name
|
455
|
+
Discharge-Cap-fFF, !- Discharge Only Mode Storage Discharge Capacity Function of Flow Fraction Curve Name
|
456
|
+
ConstantBi, !- Discharge Only Mode Energy Input Ratio Function of Temperature Curve Name
|
457
|
+
ConstantCubic, !- Discharge Only Mode Energy Input Ratio Function of Flow Fraction Curve Name
|
458
|
+
ConstantCubic, !- Discharge Only Mode Part Load Fraction Correlation Curve Name
|
459
|
+
#{curve_d_shr_ft}, !- Discharge Only Mode Sensible Heat Ratio Function of Temperature Curve Name
|
460
|
+
Discharge-SHR-fFF, !- Discharge Only Mode Sensible Heat Ratio Function of Flow Fraction Curve Name
|
461
|
+
0.0, !- Ancillary Electric Power {W}
|
462
|
+
2.0, !- Cold Weather Operation Minimum Outdoor Air Temperature {C}
|
463
|
+
0.0, !- Cold Weather Operation Ancillary Power {W}
|
464
|
+
CONDENSER INLET NODE, !- Condenser Air Inlet Node Name
|
465
|
+
CONDENSER OUTLET NODE, !- Condenser Air Outlet Node Name
|
466
|
+
autocalculate, !- Condenser Design Air Flow Rate {m3/s}
|
467
|
+
1.25, !- Condenser Air Flow Sizing Factor
|
468
|
+
AirCooled, !- Condenser Type
|
469
|
+
, !- Evaporative Condenser Effectiveness {dimensionless}
|
470
|
+
, !- Evaporative Condenser Pump Rated Power Consumption {W}
|
471
|
+
, !- Basin Heater Capacity {W/K}
|
472
|
+
, !- Basin Heater Setpoint Temperature {C}
|
473
|
+
, !- Basin Heater Availability Schedule Name
|
474
|
+
, !- Supply Water Storage Tank Name
|
475
|
+
, !- Condensate Collection Water Storage Tank Name
|
476
|
+
, !- Storage Tank Plant Connection Inlet Node Name
|
477
|
+
, !- Storage Tank Plant Connection Outlet Node Name
|
478
|
+
, !- Storage Tank Plant Connection Design Flow Rate {m3/s}
|
479
|
+
, !- Storage Tank Plant Connection Heat Transfer Effectiveness
|
480
|
+
, !- Storage Tank Minimum Operating Limit Fluid Temperature {C}
|
481
|
+
; !- Storage Tank Maximum Operating Limit Fluid Temperature {C}"
|
482
|
+
# end of new TES coil string
|
483
|
+
|
484
|
+
# #Begin Coil Replacement
|
485
|
+
# iterate through all CoilSystem:Cooling objects and replace existing coils with TES coils
|
486
|
+
coil_selection.each do |sel_coil|
|
487
|
+
# get workspace object for selected coil from name
|
488
|
+
sel_coil = workspace.getObjectsByName(sel_coil)[0]
|
489
|
+
ice_cap = hardcaps[replacement_count] unless hardcaps.empty?
|
490
|
+
|
491
|
+
# get coil type in order to find get appropriate field keys
|
492
|
+
# fields of interest: (0 - Max Cap, 1 - Rated COP, 2 - Inlet Node, 3 - Outlet Node)
|
493
|
+
field_names = [] # may not be required. Check scope in Ruby documentation
|
494
|
+
sel_type = sel_coil.iddObject.type.valueDescription.to_s
|
495
|
+
case sel_type
|
496
|
+
when 'Coil:Cooling:DX:SingleSpeed'
|
497
|
+
field_names = ['Gross Rated Total Cooling Capacity', 'Gross Rated Cooling COP',
|
498
|
+
'Air Inlet Node Name', 'Air Outlet Node Name']
|
499
|
+
when 'Coil:Cooling:DX:TwoSpeed'
|
500
|
+
field_names = ['High Speed Gross Rated Total Cooling Capacity', 'High Speed Gross Rated Cooling COP',
|
501
|
+
'Air Inlet Node Name', 'Air Outlet Node Name']
|
502
|
+
end
|
503
|
+
|
504
|
+
# get field indices associated with desired keys
|
505
|
+
keys = []
|
506
|
+
field_names.each do |fn|
|
507
|
+
keys << sel_coil.iddObject.getFieldIndex(fn).to_i
|
508
|
+
end
|
509
|
+
|
510
|
+
# get old coil name and create new coil name
|
511
|
+
old_coil_name = sel_coil.getString(0).to_s
|
512
|
+
utss_name = "UTSS Coil #{replacement_count}"
|
513
|
+
|
514
|
+
# grab inlet and outlet air nodes form selected coil
|
515
|
+
inlet = sel_coil.getString(keys[2]).to_s
|
516
|
+
outlet = sel_coil.getString(keys[3]).to_s
|
517
|
+
|
518
|
+
# update inlet and outlet node names - not needed possibly add later if names become confusing
|
519
|
+
|
520
|
+
# create local ambient node
|
521
|
+
idf_oa_ambient = OpenStudio::IdfObject.new('OutdoorAir:Node'.to_IddObjectType)
|
522
|
+
ws_oa_ambient = workspace.addObject(idf_oa_ambient)
|
523
|
+
oa_ambient = ws_oa_ambient.get
|
524
|
+
oa_ambient.setString(0, "#{utss_name} OA Ambient Node")
|
525
|
+
|
526
|
+
# create new condenser inlet node
|
527
|
+
idf_condenser_inlet = OpenStudio::IdfObject.new('OutdoorAir:Node'.to_IddObjectType)
|
528
|
+
ws_condenser_inlet = workspace.addObject(idf_condenser_inlet)
|
529
|
+
condenser_inlet = ws_condenser_inlet.get
|
530
|
+
condenser_inlet.setString(0, "#{utss_name} Condenser Inlet Node")
|
531
|
+
|
532
|
+
# create new condenser inlet node
|
533
|
+
idf_condenser_outlet = OpenStudio::IdfObject.new('OutdoorAir:Node'.to_IddObjectType)
|
534
|
+
ws_condenser_outlet = workspace.addObject(idf_condenser_outlet)
|
535
|
+
condenser_outlet = ws_condenser_outlet.get
|
536
|
+
condenser_outlet.setString(0, "#{utss_name} Condenser Out Node")
|
537
|
+
|
538
|
+
# create a new UTSS object
|
539
|
+
idf_coil_object = OpenStudio::IdfObject.load(new_tes_string)
|
540
|
+
utss_obj = idf_coil_object.get
|
541
|
+
ws_tes_obj = workspace.addObject(utss_obj)
|
542
|
+
utss = ws_tes_obj.get
|
543
|
+
utss.setString(0, utss_name)
|
544
|
+
|
545
|
+
# get indices for required utss fields - reused variable names, consider changing if confusing
|
546
|
+
field_names = ['Evaporator Air Inlet Node Name', 'Evaporator Air Outlet Node Name',
|
547
|
+
'Storage Tank Ambient Temperature Node Name',
|
548
|
+
'Condenser Air Inlet Node Name', 'Condenser Air Outlet Node Name']
|
549
|
+
|
550
|
+
keys = []
|
551
|
+
field_names.each do |fn|
|
552
|
+
keys << utss.iddObject.getFieldIndex(fn).to_i
|
553
|
+
end
|
554
|
+
|
555
|
+
# updated required fields in utss object
|
556
|
+
utss.setString(keys[0], inlet) # air inlet node
|
557
|
+
utss.setString(keys[1], outlet) # air outlet node
|
558
|
+
utss.setString(keys[2], oa_ambient.name.to_s) # outdoor ambient node
|
559
|
+
utss.setString(keys[3], condenser_inlet.name.to_s) # condenser inlet node
|
560
|
+
utss.setString(keys[4], condenser_outlet.name.to_s) # condenser outlet node
|
561
|
+
utss.setString(7, ice_cap) # hardsized thermal storage capacity
|
562
|
+
|
563
|
+
# copy old coil information over to TES object (use low-speed info for 2spd coils)
|
564
|
+
case sel_coil.iddObject.name
|
565
|
+
when 'Coil:Cooling:DX:SingleSpeed'
|
566
|
+
utss.setString(16, sel_coil.getString(2).get)
|
567
|
+
utss.setString(18, sel_coil.getString(4).get)
|
568
|
+
utss.setString(19, sel_coil.getString(9).get)
|
569
|
+
utss.setString(20, sel_coil.getString(10).get)
|
570
|
+
utss.setString(21, sel_coil.getString(11).get)
|
571
|
+
utss.setString(22, sel_coil.getString(12).get)
|
572
|
+
utss.setString(23, sel_coil.getString(13).get)
|
573
|
+
when 'Coil:Cooling:DX:TwoSpeed'
|
574
|
+
utss.setString(16, sel_coil.getString(14).get)
|
575
|
+
utss.setString(18, sel_coil.getString(16).get)
|
576
|
+
utss.setString(19, sel_coil.getString(18).get)
|
577
|
+
utss.setString(20, sel_coil.getString(10).get)
|
578
|
+
utss.setString(21, sel_coil.getString(19).get)
|
579
|
+
utss.setString(22, sel_coil.getString(12).get)
|
580
|
+
utss.setString(23, sel_coil.getString(13).get)
|
581
|
+
end
|
582
|
+
|
583
|
+
# identify container object in which the coil is used
|
584
|
+
cooling_containers.each do |cont|
|
585
|
+
case cont.iddObject.type.valueDescription.to_s
|
586
|
+
when 'CoilSystem:Cooling:DX'
|
587
|
+
if cont.getString(6).to_s == old_coil_name
|
588
|
+
cont.setString(5, 'Coil:Cooling:DX:SingleSpeed:ThermalStorage')
|
589
|
+
cont.setString(6, utss_name)
|
590
|
+
break
|
591
|
+
end
|
592
|
+
when 'AirLoopHVAC:UnitarySystem'
|
593
|
+
if cont.getString(15).to_s == old_coil_name
|
594
|
+
cont.setString(14, 'Coil:Cooling:DX:SingleSpeed:ThermalStorage')
|
595
|
+
cont.setString(15, utss_name)
|
596
|
+
break
|
597
|
+
end
|
598
|
+
end
|
599
|
+
end
|
600
|
+
|
601
|
+
# remove old coil
|
602
|
+
workspace.removeObject(sel_coil.handle)
|
603
|
+
|
604
|
+
# increment replacement count
|
605
|
+
replacement_count += 1
|
606
|
+
|
607
|
+
## Add EMS Controller Components
|
608
|
+
# create EMS intended schedule sensor once
|
609
|
+
if replacement_count == 1
|
610
|
+
idf_sched_sensor = OpenStudio::IdfObject.new('EnergyManagementSystem:Sensor'.to_IddObjectType)
|
611
|
+
ws_sched_sensor = workspace.addObject(idf_sched_sensor)
|
612
|
+
new_sched_sensor = ws_sched_sensor.get
|
613
|
+
new_sched_sensor.setString(0, 'TESIntendedSchedule')
|
614
|
+
new_sched_sensor.setString(1, sched)
|
615
|
+
new_sched_sensor.setString(2, 'Schedule Value')
|
616
|
+
end
|
617
|
+
|
618
|
+
# clean-up variable names for EMS purposes (no spaces allowed)
|
619
|
+
u_name = utss_name.gsub(/\s/, '_')
|
620
|
+
|
621
|
+
# add EMS sensor for TES control
|
622
|
+
idf_sensor = OpenStudio::IdfObject.new('EnergyManagementSystem:Sensor'.to_IddObjectType)
|
623
|
+
ws_sensor = workspace.addObject(idf_sensor)
|
624
|
+
new_sensor = ws_sensor.get
|
625
|
+
new_sensor.setString(0, "#{u_name}_sTES")
|
626
|
+
new_sensor.setString(1, utss_name)
|
627
|
+
new_sensor.setString(2, 'Cooling Coil Ice Thermal Storage End Fraction')
|
628
|
+
|
629
|
+
# add EMS actuator for TES control
|
630
|
+
idf_actuator = OpenStudio::IdfObject.new('EnergyManagementSystem:Actuator'.to_IddObjectType)
|
631
|
+
ws_actuator = workspace.addObject(idf_actuator)
|
632
|
+
new_actuator = ws_actuator.get
|
633
|
+
new_actuator.setString(0, "#{u_name}_OpMode")
|
634
|
+
new_actuator.setString(1, utss_name)
|
635
|
+
new_actuator.setString(2, 'Coil:Cooling:DX:SingleSpeed:ThermalStorage')
|
636
|
+
new_actuator.setString(3, 'Operating Mode')
|
637
|
+
|
638
|
+
# add Global Variable to track min SOC from previous use
|
639
|
+
idf_gvar = OpenStudio::IdfObject.new('EnergyManagementSystem:GlobalVariable'.to_IddObjectType)
|
640
|
+
ws_gvar = workspace.addObject(idf_gvar)
|
641
|
+
new_gvar = ws_gvar.get
|
642
|
+
new_gvar.setString(0, "#{u_name}_MinSOC")
|
643
|
+
|
644
|
+
# add EMS program for TES control
|
645
|
+
program_string = "
|
646
|
+
EnergyManagementSystem:Program,
|
647
|
+
#{u_name}_Control, !- Name
|
648
|
+
SET #{u_name}_OpMode = TESIntendedSchedule,
|
649
|
+
IF CurrentEnvironment == 1,
|
650
|
+
SET #{u_name}_MinSOC = 1,
|
651
|
+
ENDIF,
|
652
|
+
IF (#{u_name}_OpMode == 5),
|
653
|
+
IF ( #{u_name}_sTES < 0.05 ),
|
654
|
+
SET #{u_name}_OpMode = 1,
|
655
|
+
ENDIF,
|
656
|
+
SET #{u_name}_MinSOC = #{u_name}_sTES,
|
657
|
+
ENDIF,
|
658
|
+
IF (#{u_name}_OpMode == 4),
|
659
|
+
IF ( #{u_name}_sTES > 0.99 ),
|
660
|
+
SET #{u_name}_OpMode = 1,
|
661
|
+
ENDIF,
|
662
|
+
ENDIF;"
|
663
|
+
|
664
|
+
idf_program = OpenStudio::IdfObject.load(program_string)
|
665
|
+
idf_prgm = idf_program.get
|
666
|
+
workspace.addObject(idf_prgm)
|
667
|
+
|
668
|
+
# add EMS program calling manager for TES control
|
669
|
+
idf_pcm = OpenStudio::IdfObject.new('EnergyManagementSystem:ProgramCallingManager'.to_IddObjectType)
|
670
|
+
ws_pcm = workspace.addObject(idf_pcm)
|
671
|
+
new_pcm = ws_pcm.get
|
672
|
+
new_pcm.setString(0, "#{u_name}_TES_PrgmCallMgr")
|
673
|
+
new_pcm.setString(1, 'AfterPredictorAfterHVACManagers')
|
674
|
+
new_pcm.setString(2, "#{u_name}_Control")
|
675
|
+
|
676
|
+
# register info
|
677
|
+
# Coil replaced, Coil Added, EMS Program Added
|
678
|
+
runner.registerInfo("Coil '#{old_coil_name}' was replaced with a unitary thermal storage system named" \
|
679
|
+
"'#{utss.name}' with a capacity of #{ice_cap} GJ.\n")
|
680
|
+
# end of coil replacement routine
|
681
|
+
end
|
682
|
+
|
683
|
+
# additional output for schedule verification
|
684
|
+
runner.registerInfo("The user-selected schedule for the ice unit operation is:\n\n#{user_schedule}")
|
685
|
+
|
686
|
+
# register initial and final conditions
|
687
|
+
runner.registerInitialCondition("The building started with #{cooling_containers.size} cooling coils.")
|
688
|
+
runner.registerFinalCondition("A total of #{replacement_count} cooling coils were replaced with thermal storage coil systems.")
|
689
|
+
true
|
690
|
+
end
|
691
|
+
end
|
692
|
+
|
693
|
+
# register the measure to be used by the application
|
694
|
+
AddPackagedIceStorage.new.registerWithApplication
|