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.
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