openstudio-load-flexibility-measures 0.2.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +36 -36
  3. data/.rubocop.yml +6 -9
  4. data/CHANGELOG.html +75 -75
  5. data/CHANGELOG.md +59 -37
  6. data/Gemfile +31 -7
  7. data/Jenkinsfile +11 -0
  8. data/README.md +53 -42
  9. data/Rakefile +15 -15
  10. data/doc_templates/LICENSE.md +26 -26
  11. data/doc_templates/README.md.erb +41 -41
  12. data/doc_templates/copyright_erb.txt +35 -35
  13. data/doc_templates/copyright_js.txt +3 -3
  14. data/doc_templates/copyright_ruby.txt +33 -33
  15. data/lib/measures/add_central_ice_storage/LICENSE.md +26 -26
  16. data/lib/measures/add_central_ice_storage/README.md +264 -264
  17. data/lib/measures/add_central_ice_storage/README.md.erb +41 -41
  18. data/lib/measures/add_central_ice_storage/measure.rb +1325 -1324
  19. data/lib/measures/add_central_ice_storage/measure.xml +503 -503
  20. data/lib/measures/add_central_ice_storage/resources/OsLib_Schedules.rb +171 -173
  21. data/lib/measures/add_central_ice_storage/tests/add_central_ice_storage_test.rb +203 -203
  22. data/lib/measures/add_central_ice_storage/tests/ice_test_model.osm +21523 -21523
  23. data/lib/measures/add_hpwh/LICENSE.md +26 -26
  24. data/lib/measures/add_hpwh/README.md +186 -186
  25. data/lib/measures/add_hpwh/README.md.erb +41 -41
  26. data/lib/measures/add_hpwh/docs/Flexible Domestic Hot Water Implementation Guide.pdf +0 -0
  27. data/lib/measures/add_hpwh/measure.rb +663 -647
  28. data/lib/measures/add_hpwh/measure.xml +402 -397
  29. data/lib/measures/add_hpwh/tests/SmallHotel-2A.osm +42893 -42893
  30. data/lib/measures/add_hpwh/tests/{add_hphw_test.rb → add_hpwh_test.rb} +137 -98
  31. data/lib/measures/add_packaged_ice_storage/LICENSE.md +26 -26
  32. data/lib/measures/add_packaged_ice_storage/README.html +185 -185
  33. data/lib/measures/add_packaged_ice_storage/README.md +189 -189
  34. data/lib/measures/add_packaged_ice_storage/measure.rb +694 -691
  35. data/lib/measures/add_packaged_ice_storage/measure.xml +245 -245
  36. data/lib/measures/add_packaged_ice_storage/resources/TESCurves.idf +1059 -1059
  37. data/lib/measures/add_packaged_ice_storage/tests/MeasureTest.osm +9507 -9507
  38. data/lib/measures/add_packaged_ice_storage/tests/add_packaged_ice_storage_test.rb +96 -96
  39. data/lib/openstudio/load_flexibility_measures/version.rb +40 -40
  40. data/lib/openstudio/load_flexibility_measures.rb +50 -50
  41. data/openstudio-load-flexibility-measures.gemspec +33 -32
  42. metadata +27 -27
@@ -1,691 +1,694 @@
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
- # 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
- if sel_type == 'Coil:Cooling:DX:SingleSpeed'
496
- field_names = ['Gross Rated Total Cooling Capacity', 'Gross Rated Cooling COP',
497
- 'Air Inlet Node Name', 'Air Outlet Node Name']
498
- elsif sel_type == 'Coil:Cooling:DX:TwoSpeed'
499
- field_names = ['High Speed Gross Rated Total Cooling Capacity', 'High Speed Gross Rated Cooling COP',
500
- 'Air Inlet Node Name', 'Air Outlet Node Name']
501
- end
502
-
503
- # get field indices associated with desired keys
504
- keys = []
505
- field_names.each do |fn|
506
- keys << sel_coil.iddObject.getFieldIndex(fn).to_i
507
- end
508
-
509
- # get old coil name and create new coil name
510
- old_coil_name = sel_coil.getString(0).to_s
511
- utss_name = "UTSS Coil #{replacement_count}"
512
-
513
- # grab inlet and outlet air nodes form selected coil
514
- inlet = sel_coil.getString(keys[2]).to_s
515
- outlet = sel_coil.getString(keys[3]).to_s
516
-
517
- # update inlet and outlet node names - not needed possibly add later if names become confusing
518
-
519
- # create local ambient node
520
- idf_oa_ambient = OpenStudio::IdfObject.new('OutdoorAir:Node'.to_IddObjectType)
521
- ws_oa_ambient = workspace.addObject(idf_oa_ambient)
522
- oa_ambient = ws_oa_ambient.get
523
- oa_ambient.setString(0, "#{utss_name} OA Ambient Node")
524
-
525
- # create new condenser inlet node
526
- idf_condenser_inlet = OpenStudio::IdfObject.new('OutdoorAir:Node'.to_IddObjectType)
527
- ws_condenser_inlet = workspace.addObject(idf_condenser_inlet)
528
- condenser_inlet = ws_condenser_inlet.get
529
- condenser_inlet.setString(0, "#{utss_name} Condenser Inlet Node")
530
-
531
- # create new condenser inlet node
532
- idf_condenser_outlet = OpenStudio::IdfObject.new('OutdoorAir:Node'.to_IddObjectType)
533
- ws_condenser_outlet = workspace.addObject(idf_condenser_outlet)
534
- condenser_outlet = ws_condenser_outlet.get
535
- condenser_outlet.setString(0, "#{utss_name} Condenser Out Node")
536
-
537
- # create a new UTSS object
538
- idf_coil_object = OpenStudio::IdfObject.load(new_tes_string)
539
- utss_obj = idf_coil_object.get
540
- ws_tes_obj = workspace.addObject(utss_obj)
541
- utss = ws_tes_obj.get
542
- utss.setString(0, utss_name)
543
-
544
- # get indices for required utss fields - reused variable names, consider changing if confusing
545
- field_names = ['Evaporator Air Inlet Node Name', 'Evaporator Air Outlet Node Name',
546
- 'Storage Tank Ambient Temperature Node Name',
547
- 'Condenser Air Inlet Node Name', 'Condenser Air Outlet Node Name']
548
-
549
- keys = []
550
- field_names.each do |fn|
551
- keys << utss.iddObject.getFieldIndex(fn).to_i
552
- end
553
-
554
- # updated required fields in utss object
555
- utss.setString(keys[0], inlet) # air inlet node
556
- utss.setString(keys[1], outlet) # air outlet node
557
- utss.setString(keys[2], oa_ambient.name.to_s) # outdoor ambient node
558
- utss.setString(keys[3], condenser_inlet.name.to_s) # condenser inlet node
559
- utss.setString(keys[4], condenser_outlet.name.to_s) # condenser outlet node
560
- utss.setString(7, ice_cap) # hardsized thermal storage capacity
561
-
562
- # copy old coil information over to TES object (use low-speed info for 2spd coils)
563
- if sel_coil.iddObject.name == 'Coil:Cooling:DX:SingleSpeed'
564
- utss.setString(16, sel_coil.getString(2).get)
565
- utss.setString(18, sel_coil.getString(4).get)
566
- utss.setString(19, sel_coil.getString(9).get)
567
- utss.setString(20, sel_coil.getString(10).get)
568
- utss.setString(21, sel_coil.getString(11).get)
569
- utss.setString(22, sel_coil.getString(12).get)
570
- utss.setString(23, sel_coil.getString(13).get)
571
- elsif sel_coil.iddObject.name == 'Coil:Cooling:DX:TwoSpeed'
572
- utss.setString(16, sel_coil.getString(14).get)
573
- utss.setString(18, sel_coil.getString(16).get)
574
- utss.setString(19, sel_coil.getString(18).get)
575
- utss.setString(20, sel_coil.getString(10).get)
576
- utss.setString(21, sel_coil.getString(19).get)
577
- utss.setString(22, sel_coil.getString(12).get)
578
- utss.setString(23, sel_coil.getString(13).get)
579
- end
580
-
581
- # identify container object in which the coil is used
582
- cooling_containers.each do |cont|
583
- if cont.iddObject.type.valueDescription.to_s == 'CoilSystem:Cooling:DX'
584
- if cont.getString(6).to_s == old_coil_name
585
- cont.setString(5, 'Coil:Cooling:DX:SingleSpeed:ThermalStorage')
586
- cont.setString(6, utss_name)
587
- break
588
- end
589
- elsif cont.iddObject.type.valueDescription.to_s == 'AirLoopHVAC:UnitarySystem'
590
- if cont.getString(15).to_s == old_coil_name
591
- cont.setString(14, 'Coil:Cooling:DX:SingleSpeed:ThermalStorage')
592
- cont.setString(15, utss_name)
593
- break
594
- end
595
- end
596
- end
597
-
598
- # remove old coil
599
- workspace.removeObject(sel_coil.handle)
600
-
601
- # increment replacement count
602
- replacement_count += 1
603
-
604
- ## Add EMS Controller Components
605
- # create EMS intended schedule sensor once
606
- if replacement_count == 1
607
- idf_sched_sensor = OpenStudio::IdfObject.new('EnergyManagementSystem:Sensor'.to_IddObjectType)
608
- ws_sched_sensor = workspace.addObject(idf_sched_sensor)
609
- new_sched_sensor = ws_sched_sensor.get
610
- new_sched_sensor.setString(0, 'TESIntendedSchedule')
611
- new_sched_sensor.setString(1, sched)
612
- new_sched_sensor.setString(2, 'Schedule Value')
613
- end
614
-
615
- # clean-up variable names for EMS purposes (no spaces allowed)
616
- u_name = utss_name.gsub(/\s/, '_')
617
-
618
- # add EMS sensor for TES control
619
- idf_sensor = OpenStudio::IdfObject.new('EnergyManagementSystem:Sensor'.to_IddObjectType)
620
- ws_sensor = workspace.addObject(idf_sensor)
621
- new_sensor = ws_sensor.get
622
- new_sensor.setString(0, "#{u_name}_sTES")
623
- new_sensor.setString(1, utss_name)
624
- new_sensor.setString(2, 'Cooling Coil Ice Thermal Storage End Fraction')
625
-
626
- # add EMS actuator for TES control
627
- idf_actuator = OpenStudio::IdfObject.new('EnergyManagementSystem:Actuator'.to_IddObjectType)
628
- ws_actuator = workspace.addObject(idf_actuator)
629
- new_actuator = ws_actuator.get
630
- new_actuator.setString(0, "#{u_name}_OpMode")
631
- new_actuator.setString(1, utss_name)
632
- new_actuator.setString(2, 'Coil:Cooling:DX:SingleSpeed:ThermalStorage')
633
- new_actuator.setString(3, 'Operating Mode')
634
-
635
- # add Global Variable to track min SOC from previous use
636
- idf_gvar = OpenStudio::IdfObject.new('EnergyManagementSystem:GlobalVariable'.to_IddObjectType)
637
- ws_gvar = workspace.addObject(idf_gvar)
638
- new_gvar = ws_gvar.get
639
- new_gvar.setString(0, "#{u_name}_MinSOC")
640
-
641
- # add EMS program for TES control
642
- program_string = "
643
- EnergyManagementSystem:Program,
644
- #{u_name}_Control, !- Name
645
- SET #{u_name}_OpMode = TESIntendedSchedule,
646
- IF CurrentEnvironment == 1,
647
- SET #{u_name}_MinSOC = 1,
648
- ENDIF,
649
- IF (#{u_name}_OpMode == 5),
650
- IF ( #{u_name}_sTES < 0.05 ),
651
- SET #{u_name}_OpMode = 1,
652
- ENDIF,
653
- SET #{u_name}_MinSOC = #{u_name}_sTES,
654
- ENDIF,
655
- IF (#{u_name}_OpMode == 4),
656
- IF ( #{u_name}_sTES > 0.99 ),
657
- SET #{u_name}_OpMode = 1,
658
- ENDIF,
659
- ENDIF;"
660
-
661
- idf_program = OpenStudio::IdfObject.load(program_string)
662
- idf_prgm = idf_program.get
663
- workspace.addObject(idf_prgm)
664
-
665
- # add EMS program calling manager for TES control
666
- idf_pcm = OpenStudio::IdfObject.new('EnergyManagementSystem:ProgramCallingManager'.to_IddObjectType)
667
- ws_pcm = workspace.addObject(idf_pcm)
668
- new_pcm = ws_pcm.get
669
- new_pcm.setString(0, "#{u_name}_TES_PrgmCallMgr")
670
- new_pcm.setString(1, 'AfterPredictorAfterHVACManagers')
671
- new_pcm.setString(2, "#{u_name}_Control")
672
-
673
- # register info
674
- # Coil replaced, Coil Added, EMS Program Added
675
- runner.registerInfo("Coil '#{old_coil_name}' was replaced with a unitary thermal storage system named" \
676
- "'#{utss.name}' with a capacity of #{ice_cap} GJ.\n")
677
- # end of coil replacement routine
678
- end
679
-
680
- # additional output for schedule verification
681
- runner.registerInfo("The user-selected schedule for the ice unit operation is:\n\n#{user_schedule}")
682
-
683
- # register initial and final conditions
684
- runner.registerInitialCondition("The building started with #{cooling_containers.size} cooling coils.")
685
- runner.registerFinalCondition("A total of #{replacement_count} cooling coils were replaced with thermal storage coil systems.")
686
- true
687
- end
688
- end
689
-
690
- # register the measure to be used by the application
691
- AddPackagedIceStorage.new.registerWithApplication
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