openstudio-load-flexibility-measures 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.gitignore +36 -0
  4. data/.rubocop.yml +9 -0
  5. data/CHANGELOG.md +5 -0
  6. data/Gemfile +3 -0
  7. data/README.md +42 -0
  8. data/Rakefile +15 -0
  9. data/doc_templates/LICENSE.md +27 -0
  10. data/doc_templates/README.md.erb +42 -0
  11. data/doc_templates/copyright_erb.txt +36 -0
  12. data/doc_templates/copyright_js.txt +4 -0
  13. data/doc_templates/copyright_ruby.txt +34 -0
  14. data/lib/measures/add_central_hpwh_for_load_flexibility/LICENSE.md +1 -0
  15. data/lib/measures/add_central_hpwh_for_load_flexibility/README.md +186 -0
  16. data/lib/measures/add_central_hpwh_for_load_flexibility/README.md.erb +42 -0
  17. data/lib/measures/add_central_hpwh_for_load_flexibility/docs/Flexible Domestic Hot Water Implementation Guide.pdf +0 -0
  18. data/lib/measures/add_central_hpwh_for_load_flexibility/measure.rb +648 -0
  19. data/lib/measures/add_central_hpwh_for_load_flexibility/measure.xml +398 -0
  20. data/lib/measures/add_central_hpwh_for_load_flexibility/tests/SmallHotel-2A.osm +42893 -0
  21. data/lib/measures/add_central_hpwh_for_load_flexibility/tests/add_central_hpwh_for_load_flexibility.rb +98 -0
  22. data/lib/measures/add_distributed_ice_storage_to_air_loop_for_load_flexibility/LICENSE.md +13 -0
  23. data/lib/measures/add_distributed_ice_storage_to_air_loop_for_load_flexibility/README.md +189 -0
  24. data/lib/measures/add_distributed_ice_storage_to_air_loop_for_load_flexibility/measure.rb +689 -0
  25. data/lib/measures/add_distributed_ice_storage_to_air_loop_for_load_flexibility/measure.xml +253 -0
  26. data/lib/measures/add_distributed_ice_storage_to_air_loop_for_load_flexibility/resources/TESCurves.idf +1059 -0
  27. data/lib/measures/add_distributed_ice_storage_to_air_loop_for_load_flexibility/tests/MeasureTest.osm +9507 -0
  28. data/lib/measures/add_distributed_ice_storage_to_air_loop_for_load_flexibility/tests/add_distributed_ice_storage_to_air_loop_for_load_flexibility_test.rb +96 -0
  29. data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/LICENSE.md +13 -0
  30. data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/README.md +264 -0
  31. data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/README.md.erb +42 -0
  32. data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/docs/Ice Measure Implementation Guide.pdf +0 -0
  33. data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/measure.rb +1310 -0
  34. data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/measure.xml +506 -0
  35. data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/resources/OsLib_Schedules.rb +173 -0
  36. data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/tests/add_ice_storage_to_plant_loop_for_load_flexibility_test.rb +202 -0
  37. data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/tests/ice_test_model.osm +21523 -0
  38. data/lib/openstudio/load_flexibility_measures.rb +50 -0
  39. data/lib/openstudio/load_flexibility_measures/version.rb +40 -0
  40. data/openstudio-load-flexibility-measures.gemspec +32 -0
  41. metadata +172 -0
@@ -0,0 +1,98 @@
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
+ # insert your copyright here
37
+
38
+ require 'openstudio'
39
+ require 'openstudio/measure/ShowRunnerOutput'
40
+ require 'minitest/autorun'
41
+ require_relative '../measure.rb'
42
+ require 'fileutils'
43
+
44
+ class AddCentralHPWHForLoadFlexibilityTest < Minitest::Test
45
+ # def setup
46
+ # end
47
+
48
+ # def teardown
49
+ # end
50
+
51
+ def test_good_argument_values
52
+ # create an instance of the measure
53
+ measure = AddCentralHPWHForLoadFlexibility.new
54
+
55
+ # create runner with empty OSW
56
+ osw = OpenStudio::WorkflowJSON.new
57
+ runner = OpenStudio::Measure::OSRunner.new(osw)
58
+
59
+ # load the test model
60
+ translator = OpenStudio::OSVersion::VersionTranslator.new
61
+ path = "#{File.dirname(__FILE__)}/SmallHotel-2A.osm"
62
+ model = translator.loadModel(path)
63
+ assert(!model.empty?)
64
+ model = model.get
65
+
66
+ # get arguments
67
+ arguments = measure.arguments(model)
68
+ argument_map = OpenStudio::Measure.convertOSArgumentVectorToMap(arguments)
69
+
70
+ # create hash of argument values.
71
+ # If the argument has a default that you want to use, you don't need it in the hash
72
+ args_hash = {}
73
+ # using defaults values from measure.rb for other arguments
74
+
75
+ # populate argument with specified hash value if specified
76
+ arguments.each do |arg|
77
+ temp_arg_var = arg.clone
78
+ if args_hash.key?(arg.name)
79
+ assert(temp_arg_var.setValue(args_hash[arg.name]))
80
+ end
81
+ argument_map[arg.name] = temp_arg_var
82
+ end
83
+
84
+ # run the measure
85
+ measure.run(model, runner, argument_map)
86
+ result = runner.result
87
+
88
+ # show the output
89
+ show_output(result)
90
+
91
+ # assert that it ran correctly
92
+ assert_equal('Success', result.value.valueName)
93
+
94
+ # save the model to test output directory
95
+ output_file_path = "#{File.dirname(__FILE__)}//output/test_output.osm"
96
+ model.save(output_file_path, true)
97
+ end
98
+ end
@@ -0,0 +1,13 @@
1
+ OpenStudio(R), Copyright (c) 2008-2020, Alliance for Sustainable Energy, LLC. All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4
+
5
+ (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6
+
7
+ (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+
9
+ (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission from the respective party.
10
+
11
+ (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior written permission from Alliance for Sustainable Energy, LLC.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER, THE UNITED STATES GOVERNMENT, OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,189 @@
1
+ # Guide for Using Replace Cooling Cool with Packaged Ice Storage Unit Measure
2
+
3
+ ## Description
4
+ This measure removes the cooling coils in the model and replaces them with packaged air conditioning units with integrated ice storage.
5
+
6
+ ## Modeler Description
7
+ 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.
8
+
9
+ Users inputs are accepted for cooling coil size, ice storage size, system control method, modes of operation, and operating schedule.
10
+
11
+ The measure requires schedule objects and performance curves from an included resource file TESCurves.idf. Output variables of typical interest are included as well..
12
+
13
+ ## Measure Type
14
+ EnergyPlus Measure
15
+
16
+ ## Application
17
+ This measure is built to simulate packaged ice storage units which integrate with packaged single zone AC units (eg. RTUs). An EnergyPlus object was created in 2013 to model this system, but it is not currently available in OpenStudio.
18
+
19
+ ## Requirements
20
+ - Existing OpenStudio model with PVAV or PSZAC HVAC
21
+ - Original HVAC must use *CoilSystem:Cooling:DX* or *AirLoopHVAC:UnitarySystem* container objects
22
+ - "TESCurves.idf" resource file
23
+
24
+ ## Limitations
25
+ - The measure **may** or **may not** properly replace the cooling coils in a VAV system, depending on the way the original HVAC model was built (container object dependent).
26
+ - If AutoSizing produces over/under-sized ice storage capacities, an adjustment multiplier is available. This multiplier will be applied to all AutoSized ice units.
27
+ - Cooling and Discharge Mode is currently unavailable due to the lack of available performance curves in the referenced EnergyPlus 9.3 example file.
28
+ - Cooling and Charge Mode is currently unavailable due to the lack of available performance curves in the referenced EnergyPlus 9.3 example file.
29
+
30
+ ## How the Measure Works
31
+ The measure works by replacing user-selected single-speed and two-speed cooling coils (all applicable coils are pre-selected by default) with *Coil:Cooling:DX:SingleSpeed:ThermalStorage* objects. These TES units may be AutoSized, or hard-sized
32
+
33
+ An Energy Management System (EMS) program is created for each new TES coil. The default controller turns ice charging and discharging on/off based on ice storage tank end fraction (state of charge). If users desire advanced control strategies, the EMS code may be modified directly in the "measure.rb" file (line 663 ff.). The default controller includes state-of-charge criteria that must be met before ice re-charging is permitted. This value is set at 70%, but may be manually adjusted within the measure.rb file at line 681. A built-in Schedule Modes controller is also available.
34
+
35
+ Whether EMS Control or Schedule Modes are used, a charge/discharge schedule is required. The default is a simple schedule created by user inputs. Ice storage capacity is based on the ice discharge window.
36
+
37
+ ### TES Coil Operating Schedules
38
+ - *Simple User Sched* - Default. Builds schedule from user inputs.
39
+ - *TES Sched 1: TES Off* - Useful for creating a baseline case.
40
+ - *TES Sched 2: 1-5 Peak* - Discharges ice between 1:00-5:00 pm, Charges from midnight to 7 am.
41
+ - *TES Sched 3: 3-8 Peak* - Discharges ice between 3:00-8:00 pm, Charges from midnight to 7 am.
42
+ - *Rate Sched: GSS-T* - Aligns ice discharge to Sacramento's 2018 GSS-T electricity rate plan peak hours.
43
+
44
+ More complex custom schedules may be added to "TESCurves.idf".
45
+
46
+ ### TES Coil Operating Modes
47
+ - 0: Off
48
+ - 1: Cooling Only (Ice tank is bypassed, state of charge is tracked)
49
+ - 2: Cooling and Charge (Ice charging simultaneous with zone cooling) - **UNAVAILABLE**
50
+ - 3: Cooling and Discharge (Zone cooling from both ice and AC cooling coil) - **UNAVAILABLE**
51
+ - 4: Charge Only (No zone cooling provided during ice charge)
52
+ - 5: Discharge Only (Zone cooling from ice only)
53
+
54
+ ### Default EMS Controller for each Coil
55
+ EnergyManagementSystem:Program,
56
+ #{u_name}_Control,
57
+ SET #{u_name}_OpMode = TESIntendedSchedule,
58
+ IF CurrentEnvironment == 1,
59
+ SET #{u_name}_MinSOC = 1,
60
+ ENDIF,
61
+ IF (#{u_name}_OpMode == 5),
62
+ IF ( #{u_name}_sTES < 0.05 ),
63
+ SET #{u_name}_OpMode = 1,
64
+ ENDIF,
65
+ SET #{u_name}_MinSOC = #{u_name}_sTES,
66
+ ENDIF,
67
+ IF (#{u_name}_OpMode == 4),
68
+ IF ( #{u_name}_sTES > 0.99 ),
69
+ SET #{u_name}_OpMode = 1,
70
+ ENDIF,
71
+ ENDIF;"
72
+
73
+ where
74
+
75
+ "z_name" is the zone name
76
+ "TESIntendedSchedule" obtains the scheduled coil operating mode (0-5)
77
+ "sTES" is the fractional state of charge of the ice tank (0-1)
78
+ "OpMode" sets the operating mode of the coil
79
+ "MinSOC" is the minimum state of charge of the ice tank at the end of the previous discharge period
80
+
81
+ ### Performance Curve Selection
82
+ Default curves are those used in EnergyPlus 9.3 example file "RetailPackagedTESCoil.idf". Performance curves may be added to "TESCurves.idf".
83
+
84
+ ### Arguments and Defaults
85
+
86
+ #### Select applicable zones:
87
+ **Name:** coil_selection,
88
+ **Type:** Boolean,
89
+ **Units:** ,
90
+ **Required:** true,
91
+ **Model Dependent:** true
92
+ **Default:** All Thermal Zones
93
+
94
+ #### Select ice storage capacity [ton-hours]
95
+ **Name:** ice_cap,
96
+ **Type:** Choice,
97
+ **Units:** ton-hours (refrigeration),
98
+ **Required:** true,
99
+ **Model Dependent:** false,
100
+ **Default:** Autosize
101
+
102
+ #### Enter a sizing multiplier to manually adjust the autosize results for ice tank capacities.
103
+ **Name:** size_mult
104
+ **Type:** String,
105
+ **Units:** ,
106
+ **Required:** false,
107
+ **Model Dependent:** false,
108
+ **Default:** 1.0
109
+
110
+ #### Select ice storage control method
111
+ **Name:** ctl,
112
+ **Type:** Choice,
113
+ **Units:** ,
114
+ **Required:** true,
115
+ **Model Dependent:** false,
116
+ **Default:** EMS Controlled
117
+
118
+ #### Select the operating mode schedule for the new TES coils
119
+ **Name:** sched,
120
+ **Type:** Choice,
121
+ **Units:** ,
122
+ **Required:** true,
123
+ **Model Dependent:** false,
124
+ **Default:** Simple User Sched
125
+
126
+ #### Run TES on the weekends?
127
+ **Name:** wknd,
128
+ **Type:** Boolean,
129
+ **Units:** ,
130
+ **Required:** false,
131
+ **Model Dependent:** false,
132
+ **Default:** true
133
+
134
+ #### Select season during which the ice cooling may be used:
135
+ **Name:** season,
136
+ **Type:** String,
137
+ **Units:** ,
138
+ **Required:** false,
139
+ **Model Dependent:** false,
140
+ **Default:** 01/01-12/31
141
+
142
+ #### Input start time for ice charge (hr:min)
143
+ **Name:** charge_start,
144
+ **Type:** String,
145
+ **Units:** ,
146
+ **Required:** false,
147
+ **Model Dependent:** false,
148
+ **Default:** 22:00
149
+
150
+ #### Input end time for ice charge (hr:min)
151
+ **Name:** charge_end,
152
+ **Type:** String,
153
+ **Units:** ,
154
+ **Required:** false,
155
+ **Model Dependent:** false,
156
+ **Default:** 07:00
157
+
158
+ #### Input start time for ice discharge (hr:min)
159
+ **Name:** discharge_start,
160
+ **Type:** String,
161
+ **Units:** ,
162
+ **Required:** false,
163
+ **Model Dependent:** false,
164
+ **Default:** 12:00
165
+
166
+ #### Input target end time for ice discharge (hr:min)
167
+ **Name:** discharge_end,
168
+ **Type:** String,
169
+ **Units:** ,
170
+ **Required:** false,
171
+ **Model Dependent:** false,
172
+ **Default:** 18:00
173
+
174
+ ## References
175
+ DOE (2020). *15.2.27 Packaged Thermal Storage Cooling Coil*. Engineering Reference, EnergyPlus Version 9.3.0 Documentation. Accessed June 16, 2020, from https://energyplus.net/documentation/.
176
+
177
+ DOE (2020). *1.41.39 Coil:Cooling:DX:SingleSpeed:ThermalStorage*. Input-Output Reference, EnergyPlus Version 9.3.0 Documentation. Accessed June 16, 2020, from https://energyplus.net/documentation/.
178
+
179
+ EnergyPlus 9.3.0 (2018). *RetailPackagedTESCoil.idf*. Example file included with software. Accessed June 16, 2020, from https://energyplus.net/downloads/.
180
+
181
+ IceEnergy. *Ice Bear 40 Product Information Sheet*. Accessed Jun 1, 2018 from
182
+ https://www.ice-energy.com/wp-content/uploads/2018/05/IB-40-ProductSheet-2018-US-D3.pdf
183
+
184
+ Kung, F., Deru, M., and Bonnema, E. (2013). *Evaluation Framework and Analyses for Thermal Energy Storage Integrated with Packaged Air Conditioning*. NREL/TP-5500-60415. Accessed June 27, 2018, from https://www.nrel.gov/docs/fy14osti/60415.pdf/.
185
+
186
+ Willis, R. and Parsonnet, B. (2010). *Energy Efficient TES Designs for Commercial DX Systems*, ASHRAE Transactions, Vol. 116, pt. 1, Orlando 2010.
187
+
188
+ ___
189
+ ###### Author: Karl Heine, January 2019; Revised June 2020
@@ -0,0 +1,689 @@
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 AddDistributedIceStorageToAirLoopForLoadFlexibility < OpenStudio::Measure::EnergyPlusMeasure
41
+ # human readable name
42
+ def name
43
+ 'Add Distributed Ice Storage to Air Loop for Load Flexibility'
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(c, true)
83
+ coil_selection.setDisplayName(k)
84
+ coil_selection.setDefaultValue(v)
85
+ args << coil_selection
86
+ end
87
+
88
+ ice_cap = OpenStudio::Measure::OSArgument.makeStringArgument('ice_cap', true)
89
+ ice_cap.setDisplayName('Input the ice storage capacity [ton-hours]')
90
+ ice_cap.setDescription('To specify by coil, in alphabetical order, enter values for each separated by comma.')
91
+ ice_cap.setDefaultValue('AutoSize')
92
+ args << ice_cap
93
+
94
+ size_mult = OpenStudio::Measure::OSArgument.makeStringArgument('size_mult', false)
95
+ size_mult.setDisplayName('Enter a sizing multiplier to manually adjust the autosize results for ice tank capacities.')
96
+ size_mult.setDefaultValue('1.0')
97
+ args << size_mult
98
+
99
+ # make argument for control method
100
+ ctl = OpenStudio::Measure::OSArgument.makeChoiceArgument('ctl', ['ScheduledModes', 'EMSControlled'], true)
101
+ ctl.setDisplayName('Select ice storage control method')
102
+ ctl.setDefaultValue('EMSControlled')
103
+ args << ctl
104
+
105
+ # 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
106
+ source_idf = OpenStudio::IdfFile.load(OpenStudio::Path.new(File.dirname(__FILE__) + '\resources\TESCurves.idf')).get
107
+ schedules = source_idf.getObjectsByType('Schedule:Compact'.to_IddObjectType)
108
+ schedule_names = OpenStudio::StringVector.new
109
+
110
+ schedules.each do |sch|
111
+ schedule_names << sch.name.to_s
112
+ end
113
+
114
+ # make argument for TES operating mode schedule
115
+ sched = OpenStudio::Measure::OSArgument.makeChoiceArgument('sched', schedule_names, true)
116
+ sched.setDisplayName('Select the operating mode schedule for the new TES coils')
117
+ sched.setDescription('Use the fields below to set a simple daily ice charge/discharge schedule. Or, select from pre-defined options.')
118
+ sched.setDefaultValue('Simple User Sched')
119
+ args << sched
120
+
121
+ # make arguement for weekend TES operation
122
+ wknd = OpenStudio::Measure::OSArgument.makeBoolArgument('wknd', false)
123
+ wknd.setDisplayName('Run TES on the weekends?')
124
+ wknd.setDescription('Select if building is occupied on weekends.')
125
+ wknd.setDefaultValue(true)
126
+ args << wknd
127
+
128
+ # make arguments for operating season
129
+ season = OpenStudio::Measure::OSArgument.makeStringArgument('season', false)
130
+ season.setDisplayName('Select season during which the ice cooling may be used:')
131
+ season.setDescription('Use MM/DD-MM/DD format')
132
+ season.setDefaultValue('01/01-12/31')
133
+ args << season
134
+
135
+ # make arguments for simple charging period
136
+ charge_start = OpenStudio::Measure::OSArgument.makeStringArgument('charge_start', false)
137
+ charge_start.setDisplayName('Input start time for ice charge (hr:min)')
138
+ charge_start.setDescription('Use 24 hour format')
139
+ charge_start.setDefaultValue('22:00')
140
+ args << charge_start
141
+
142
+ charge_end = OpenStudio::Measure::OSArgument.makeStringArgument('charge_end', false)
143
+ charge_end.setDisplayName('Input end time for ice charge (hr:min)')
144
+ charge_end.setDescription('Use 24 hour format')
145
+ charge_end.setDefaultValue('07:00')
146
+ args << charge_end
147
+
148
+ # make arguments for simple discharging period
149
+ discharge_start = OpenStudio::Measure::OSArgument.makeStringArgument('discharge_start', false)
150
+ discharge_start.setDisplayName('Input start time for ice discharge (hr:min)')
151
+ 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.")
152
+ discharge_start.setDefaultValue('12:00')
153
+ args << discharge_start
154
+
155
+ discharge_end = OpenStudio::Measure::OSArgument.makeStringArgument('discharge_end', false)
156
+ discharge_end.setDisplayName('Input target end time for ice discharge (hr:min)')
157
+ discharge_end.setDescription('Use 24 hour format')
158
+ discharge_end.setDefaultValue('18:00')
159
+ args << discharge_end
160
+
161
+ args
162
+ # end the arguments method
163
+ end
164
+
165
+ # define what happens when the measure is run
166
+ def run(workspace, runner, user_arguments)
167
+ super(workspace, runner, user_arguments)
168
+
169
+ # use the built-in error checking
170
+ unless runner.validateUserArguments(arguments(workspace), user_arguments)
171
+ return false
172
+ end
173
+
174
+ # load required TESCurves.idf. This contains all the TES performance curves and default schedules
175
+ source_idf = OpenStudio::IdfFile.load(OpenStudio::Path.new(File.dirname(__FILE__) + '\resources\TESCurves.idf')).get
176
+ # workspace.addObjects(idf_obj_vector) does not work here. Add each obj individually.
177
+ source_idf.objects.each do |o|
178
+ workspace.addObject(o)
179
+ end
180
+ runner.registerInfo("#{source_idf.objects.size} performance curves, schedule objects, and output variables were imported from 'TESCurves.idf'.\n\n")
181
+
182
+ # assign user arguments to variables
183
+ ice_cap = runner.getStringArgumentValue('ice_cap', user_arguments) # ice capacity value (in ton-hours)
184
+ size_mult = runner.getStringArgumentValue('size_mult', user_arguments) # size multiplier for ice tank capacity - use if autosize is excessively oversizing
185
+ ctl = runner.getStringArgumentValue('ctl', user_arguments) # control method (schedule or EMS)
186
+ sched = runner.getStringArgumentValue('sched', user_arguments) # select operating mode schedule (schedule objects located in resources\TESCurves.idf)
187
+ wknd = runner.getBoolArgumentValue('wknd', user_arguments) # turn tes on/off for weekend operation
188
+ season = runner.getStringArgumentValue('season', user_arguments) # set operating season for Simple User Sched
189
+ charge_start = runner.getStringArgumentValue('charge_start', user_arguments) # time ice charging begins
190
+ charge_end = runner.getStringArgumentValue('charge_end', user_arguments) # time ice charging ends
191
+ discharge_start = runner.getStringArgumentValue('discharge_start', user_arguments) # time ice discharge begins
192
+ discharge_end = runner.getStringArgumentValue('discharge_end', user_arguments) # time ice discharge ends
193
+
194
+ # retrieve user selected coils and assign to vector
195
+ coils = workspace.getObjectsByType('Coil:Cooling:DX:SingleSpeed'.to_IddObjectType)
196
+ coils += workspace.getObjectsByType('Coil:Cooling:DX:TwoSpeed'.to_IddObjectType)
197
+ coilhash = {}
198
+ c = coils.sort { |a, b| a.getString(0).get <=> b.getString(0).get }
199
+ c.each do |coil|
200
+ c_name = coil.name.to_s
201
+ coilhash[c_name] = true
202
+ end
203
+
204
+ coil_selection = []
205
+ coilhash.each do |k, v|
206
+ temp_var = runner.getBoolArgumentValue(k, user_arguments)
207
+ coil_selection << k if temp_var
208
+ end
209
+
210
+ # create other useful variables
211
+ replacement_count = 0 # tracks number of coils replaced by measure
212
+ time_size_factor = '' # sets Storage Capacity Sizing Factor {hr}
213
+ discharge_cop = '63.6' # default COP for Ice Discharge
214
+ curve_d_shr_ft = 'Discharge-SHR-fT-NREL' # default curve for sensible heat ratio f(T) during ice discharge
215
+
216
+ # convert string time values into floats for math comparisons
217
+ # ds/de = discharge start/end, cs/ce = charge start/end
218
+ ds = discharge_start.split(':')[0].to_f + (discharge_start.split(':')[1].to_f / 0.6)
219
+ de = discharge_end.split(':')[0].to_f + (discharge_end.split(':')[1].to_f / 0.6)
220
+ cs = charge_start.split(':')[0].to_f + (charge_start.split(':')[1].to_f / 0.6)
221
+ ce = charge_end.split(':')[0].to_f + (charge_end.split(':')[1].to_f / 0.6)
222
+
223
+ # #Check User Inputs and Define Variables
224
+ hardcaps = []
225
+ if ice_cap != 'AutoSize'
226
+ if ice_cap == ''
227
+ runner.registerWarning("No ice capacity was entered for 'User Input' selection, 'AutoSize' was used instead.")
228
+ ice_cap = 'AutoSize'
229
+ elsif ice_cap.split(',').size > 1
230
+ runner.registerInfo('Ice storage tanks will be hardsized based on user inputs, assigned alphabetically.')
231
+ ice_cap.split(',').each { |i| hardcaps.push((i.to_f * 0.0126608).to_s) }
232
+ while hardcaps.size != coil_selection.size
233
+ runner.registerInfo("No user-defined thermal storage capacity for #{coil_selection[hardcaps.size]}; unit will be AutoSized.")
234
+ hardcaps.push('AutoSize')
235
+ end
236
+ else
237
+ ice_cap = (ice_cap.to_f * 0.0126608).to_s # convert units from ton-hours to GJ
238
+ end
239
+ elsif sched == 'Simple User Sched'
240
+ time_size_factor = ((de - ds) * size_mult.to_f).to_s
241
+ elsif sched == 'TES Sched 2: 1-5 Peak'
242
+ time_size_factor = (4.0 * size_mult.to_f).to_s
243
+ elsif sched == 'TES Sched 3: 3-8 Peak'
244
+ time_size_factor = (5.0 * size_mult.to_f).to_s
245
+ elsif sched == 'TES Sched 4: GSS-T'
246
+ time_size_factor = (3.0 * size_mult.to_f).to_s
247
+ else
248
+ time_size_factor = (4.0 * size_mult.to_f).to_s # sets default time size factor to 4 hours
249
+ end
250
+
251
+ # Check user schedule inputs and build schedule
252
+ if sched == 'Simple User Sched'
253
+
254
+ # find empty user input schedule object from TESCurves.idf import
255
+ user_schedules = workspace.getObjectsByName('Simple User Sched')
256
+ user_schedule = user_schedules[0]
257
+
258
+ # check ice discharge times to ensure end occurs after start. Exit gracefully if it doesn't.
259
+ if de < ds
260
+ 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.')
261
+ return false
262
+ end
263
+
264
+ # sets charge and discharge mode ** May be modified if Cool_Charge or Cool_Discharge modes become available
265
+ charge_mode = 4
266
+ discharge_mode = 5
267
+
268
+ # format user input for cooling season values
269
+ czn = season.split(/[\s-]/)
270
+ cool_start = czn[0].to_s
271
+ cool_end = czn[-1].to_s
272
+
273
+ # set cooling season start periods
274
+ a = 4 # index variable to ensure schedule is built properly under various conditions
275
+ c = 3 # index variable to help ensure a weekday-only schedule is properly built
276
+
277
+ if cool_start != '01/01'
278
+ user_schedule.setString(2, "Through: #{cool_start}")
279
+ user_schedule.setString(3, 'For: AllDays')
280
+ user_schedule.setString(4, 'Until: 24:00')
281
+ user_schedule.setString(5, '1')
282
+ user_schedule.setString(7, 'For: AllDays')
283
+ a = 8
284
+ c = 7
285
+ end
286
+
287
+ # build user defined schedule object
288
+ if cs > ce
289
+ # assign times to schedule fields
290
+ user_schedule.setString(a, "Until: #{charge_end}")
291
+ user_schedule.setString(a + 1, charge_mode.to_s)
292
+ user_schedule.setString(a + 2, "Until: #{discharge_start}")
293
+ user_schedule.setString(a + 3, '1')
294
+ user_schedule.setString(a + 4, "Until: #{discharge_end}")
295
+ user_schedule.setString(a + 5, discharge_mode.to_s)
296
+ user_schedule.setString(a + 6, "Until: #{charge_start}")
297
+ user_schedule.setString(a + 7, '1')
298
+ user_schedule.setString(a + 8, 'Until: 24:00')
299
+ user_schedule.setString(a + 9, charge_mode.to_s)
300
+ b = a + 10
301
+ elsif charge_start != '00:00'
302
+ user_schedule.setString(a, "Until: #{charge_end}")
303
+ user_schedule.setString(a + 1, charge_mode.to_s)
304
+ user_schedule.setString(a + 2, "Until: #{discharge_start}")
305
+ user_schedule.setString(a + 3, '1')
306
+ user_schedule.setString(a + 4, "Until: #{discharge_end}")
307
+ user_schedule.setString(a + 5, discharge_mode.to_s)
308
+ user_schedule.setString(a + 6, 'Until: 24:00')
309
+ user_schedule.setString(a + 7, '1')
310
+ b = a + 8
311
+ else
312
+ user_schedule.setString(a, "Until: #{charge_end}")
313
+ user_schedule.setString(a + 1, charge_mode.to_s)
314
+ user_schedule.setString(a + 2, "Until: #{discharge_start}")
315
+ user_schedule.setString(a + 3, '1')
316
+ user_schedule.setString(a + 4, "Until: #{discharge_end}")
317
+ user_schedule.setString(a + 5, discharge_mode.to_s)
318
+ user_schedule.setString(a + 6, 'Until: 24:00')
319
+ user_schedule.setString(a + 7, '1')
320
+ b = a + 8
321
+ end
322
+
323
+ # make weekend modification if necessary
324
+ unless wknd
325
+ user_schedule.setString(c, 'For: WeekDays')
326
+ user_schedule.setString(b, 'For: Weekends')
327
+ user_schedule.setString(b + 1, "Until: #{charge_end}")
328
+ user_schedule.setString(b + 2, charge_mode.to_s)
329
+ user_schedule.setString(b + 3, 'Until: 24:00')
330
+ user_schedule.setString(b + 4, '1')
331
+ b += 5
332
+ end
333
+
334
+ # complete cooling season schedule if not through 12/31
335
+ if cool_end != '12/31'
336
+ user_schedule.setString(c - 1, "Through: #{cool_end}")
337
+ user_schedule.setString(b, 'Through: 12/31')
338
+ user_schedule.setString(b + 1, 'For: AllDays')
339
+ user_schedule.setString(b + 2, 'Until: 24:00')
340
+ user_schedule.setString(b + 3, '1')
341
+ end
342
+ end
343
+
344
+ # find objects of interest in the model (used to identify container objects, air loops, and thermal zones)
345
+ cooling_coil_systems = workspace.getObjectsByType('CoilSystem:Cooling:DX'.to_IddObjectType)
346
+ air_loops = workspace.getObjectsByType('AirLoopHVAC'.to_IddObjectType)
347
+ branches = workspace.getObjectsByType('Branch'.to_IddObjectType)
348
+ hvac_zone_mixers = workspace.getObjectsByType('AirLoopHVAC:ZoneMixer'.to_IddObjectType)
349
+ zone_connections = workspace.getObjectsByType('ZoneHVAC:EquipmentConnections'.to_IddObjectType)
350
+ unitary_generic_obj = workspace.getObjectsByType('AirLoopHVAC:UnitarySystem'.to_IddObjectType)
351
+ node_lists = workspace.getObjectsByType('NodeList'.to_IddObjectType)
352
+
353
+ # create vector of all cooling system container objects
354
+ cooling_containers = OpenStudio::IdfObjectVector.new
355
+ cooling_coil_systems.each do |coil_sys|
356
+ if coil_selection.include?(coil_sys.getString(6).to_s)
357
+ cooling_containers << coil_sys
358
+ end
359
+ end
360
+ unitary_generic_obj.each do |unitary_sys|
361
+ if coil_selection.include?(unitary_sys.getString(15).to_s)
362
+ cooling_containers << unitary_sys
363
+ end
364
+ end
365
+
366
+ # exit gracefully if original model does not have coilSystem:Cooling objects
367
+ if cooling_containers.empty?
368
+ runner.registerError('This measure only operates on the following EnergyPlus container objects: CoilSystem:Cooling:DX and AirLoopHVAC:UnitarySystem. Measure was not applied.')
369
+ end
370
+
371
+ # create TES object string template for use in replacing existing coils; incorporates user input variables
372
+ new_tes_string =
373
+ "Coil:Cooling:DX:SingleSpeed:ThermalStorage,
374
+ NAME PLACEHOLDER, !- Name
375
+ ALWAYS_ON, !- Availability Schedule Name
376
+ #{ctl}, !- Operating Mode Control Method
377
+ #{sched}, !- Operation Mode Control Schedule Name
378
+ Ice, !- Storage Type
379
+ , !- User Defined Fluid Type
380
+ , !- Fluid Storage Volume {m3}
381
+ AutoSize, !- Ice Storage Capacity {GJ}
382
+ #{time_size_factor}, !- Storage Capacity Sizing Factor {hr}
383
+ AMBIENT NODE, !- Storage Tank Ambient Temperature Node Name
384
+ 7.913, !- Storage Tank to Ambient U-value Times Area Heat Transfer Coefficient {W/K}
385
+ , !- Fluid Storage Tank Rating Temperature {C}
386
+ AutoSize, !- Rated Evaporator Air Flow Rate {m3/s}
387
+ EVAP IN NODE, !- Evaporator Air Inlet Node Name
388
+ EVAP OUT NODE, !- Evaporator Air Outlet Node Name
389
+ Yes, !- Cooling Only Mode Available
390
+ AutoSize, !- Cooling Only Mode Rated Total Evaporator Cooling Capacity {W} **IB40 Limits: 10551 W (3 ton) to 70337 W (20 ton)**
391
+ 0.7, !- Cooling Only Mode Rated Sensible Heat Ratio
392
+ 3.23372055845678, !- Cooling Only Mode Rated COP {W/W}
393
+ Cool-Cap-fT, !- Cooling Only Mode Total Evaporator Cooling Capacity Function of Temperature Curve Name
394
+ ConstantCubic, !- Cooling Only Mode Total Evaporator Cooling Capacity Function of Flow Fraction Curve Name
395
+ Cool-EIR-fT, !- Cooling Only Mode Energy Input Ratio Function of Temperature Curve Name
396
+ ConstantCubic, !- Cooling Only Mode Energy Input Ratio Function of Flow Fraction Curve Name
397
+ Cool-PLF-fPLR, !- Cooling Only Mode Part Load Fraction Correlation Curve Name
398
+ Cool-SHR-fT, !- Cooling Only Mode Sensible Heat Ratio Function of Temperature Curve Name
399
+ Cool-SHR-fFF, !- Cooling Only Mode Sensible Heat Ratio Function of Flow Fraction Curve Name
400
+ No, !- Cooling And Charge Mode Available
401
+ AutoSize, !- Cooling And Charge Mode Rated Total Evaporator Cooling Capacity
402
+ 1.0, !- Cooling And Charge Mode Capacity Sizing Factor
403
+ AutoSize, !- Cooling And Charge Mode Rated Storage Charging Capacity
404
+ 0.86, !- Cooling And Charge Mode Storage Capacity Sizing Factor
405
+ 0.7, !- Cooling And Charge Mode Rated Sensible Heat Ratio
406
+ 3.66668443E+00, !- Cooling And Charge Mode Cooling Rated COP
407
+ 2.17, !- Cooling And Charge Mode Charging Rated COP
408
+ CoolCharge-Cool-Cap-fT, !- Cooling And Charge Mode Total Evaporator Cooling Capacity Function of Temperature Curve Name
409
+ ConstantCubic, !- Cooling And Charge Mode Total Evaporator Cooling Capacity Function of Flow Fraction Curve Name
410
+ CoolCharge-Cool-EIR-fT, !- Cooling And Charge Mode Evaporator Energy Input Ratio Function of Temperature Curve Name
411
+ ConstantCubic, !- Cooling And Charge Mode Evaporator Energy Input Ratio Function of Flow Fraction Curve Name
412
+ Cool-PLF-fPLR, !- Cooling And Charge Mode Evaporator Part Load Fraction Correlation Curve Name
413
+ CoolCharge-Charge-Cap-fT,!- Cooling And Charge Mode Storage Charge Capacity Function of Temperature Curve Name
414
+ ConstantCubic, !- Cooling And Charge Mode Storage Charge Capacity Function of Total Evaporator PLR Curve Name
415
+ CoolCharge-Charge-EIR-fT,!- Cooling And Charge Mode Storage Energy Input Ratio Function of Temperature Curve Name
416
+ ConstantCubic, !- Cooling And Charge Mode Storage Energy Input Ratio Function of Flow Fraction Curve Name
417
+ ConstantCubic, !- Cooling And Charge Mode Storage Energy Part Load Fraction Correlation Curve Name
418
+ Cool-SHR-fT, !- Cooling And Charge Mode Sensible Heat Ratio Function of Temperature Curve Name
419
+ Cool-SHR-fFF, !- Cooling And Charge Mode Sensible Heat Ratio Function of Flow Fraction Curve Name
420
+ No, !- Cooling And Discharge Mode Available
421
+ , !- Cooling And Discharge Mode Rated Total Evaporator Cooling Capacity {W}
422
+ , !- Cooling And Discharge Mode Evaporator Capacity Sizing Factor
423
+ , !- Cooling And Discharge Mode Rated Storage Discharging Capacity {W}
424
+ , !- Cooling And Discharge Mode Storage Discharge Capacity Sizing Factor
425
+ , !- Cooling And Discharge Mode Rated Sensible Heat Ratio
426
+ , !- Cooling And Discharge Mode Cooling Rated COP {W/W}
427
+ , !- Cooling And Discharge Mode Discharging Rated COP {W/W}
428
+ , !- Cooling And Discharge Mode Total Evaporator Cooling Capacity Function of Temperature Curve Name
429
+ , !- Cooling And Discharge Mode Total Evaporator Cooling Capacity Function of Flow Fraction Curve Name
430
+ , !- Cooling And Discharge Mode Evaporator Energy Input Ratio Function of Temperature Curve Name
431
+ , !- Cooling And Discharge Mode Evaporator Energy Input Ratio Function of Flow Fraction Curve Name
432
+ , !- Cooling And Discharge Mode Evaporator Part Load Fraction Correlation Curve Name
433
+ , !- Cooling And Discharge Mode Storage Discharge Capacity Function of Temperature Curve Name
434
+ , !- Cooling And Discharge Mode Storage Discharge Capacity Function of Flow Fraction Curve Name
435
+ , !- Cooling And Discharge Mode Storage Discharge Capacity Function of Total Evaporator PLR Curve Name
436
+ , !- Cooling And Discharge Mode Storage Energy Input Ratio Function of Temperature Curve Name
437
+ , !- Cooling And Discharge Mode Storage Energy Input Ratio Function of Flow Fraction Curve Name
438
+ , !- Cooling And Discharge Mode Storage Energy Part Load Fraction Correlation Curve Name
439
+ , !- Cooling And Discharge Mode Sensible Heat Ratio Function of Temperature Curve Name
440
+ , !- Cooling And Discharge Mode Sensible Heat Ratio Function of Flow Fraction Curve Name
441
+ Yes, !- Charge Only Mode Available
442
+ AutoSize, !- Charge Only Mode Rated Storage Charging Capacity {W}
443
+ 0.8, !- Charge Only Mode Capacity Sizing Factor
444
+ 3.09, !- Charge Only Mode Charging Rated COP {W/W}
445
+ ChargeOnly-Cap-fT, !- Charge Only Mode Storage Charge Capacity Function of Temperature Curve Name
446
+ ChargeOnly-EIR-fT, !- Charge Only Mode Storage Energy Input Ratio Function of Temperature Curve Name
447
+ Yes, !- Discharge Only Mode Available
448
+ AutoSize, !- Discharge Only Mode Rated Storage Discharging Capacity {W}
449
+ 1.37, !- Discharge Only Mode Capacity Sizing Factor
450
+ 0.64, !- Discharge Only Mode Rated Sensible Heat Ratio
451
+ #{discharge_cop}, !- Discharge Only Mode Rated COP {W/W}
452
+ Discharge-Cap-fT, !- Discharge Only Mode Storage Discharge Capacity Function of Temperature Curve Name
453
+ Discharge-Cap-fFF, !- Discharge Only Mode Storage Discharge Capacity Function of Flow Fraction Curve Name
454
+ ConstantBi, !- Discharge Only Mode Energy Input Ratio Function of Temperature Curve Name
455
+ ConstantCubic, !- Discharge Only Mode Energy Input Ratio Function of Flow Fraction Curve Name
456
+ ConstantCubic, !- Discharge Only Mode Part Load Fraction Correlation Curve Name
457
+ #{curve_d_shr_ft}, !- Discharge Only Mode Sensible Heat Ratio Function of Temperature Curve Name
458
+ Discharge-SHR-fFF, !- Discharge Only Mode Sensible Heat Ratio Function of Flow Fraction Curve Name
459
+ 0.0, !- Ancillary Electric Power {W}
460
+ 2.0, !- Cold Weather Operation Minimum Outdoor Air Temperature {C}
461
+ 0.0, !- Cold Weather Operation Ancillary Power {W}
462
+ CONDENSER INLET NODE, !- Condenser Air Inlet Node Name
463
+ CONDENSER OUTLET NODE, !- Condenser Air Outlet Node Name
464
+ autocalculate, !- Condenser Design Air Flow Rate {m3/s}
465
+ 1.25, !- Condenser Air Flow Sizing Factor
466
+ AirCooled, !- Condenser Type
467
+ , !- Evaporative Condenser Effectiveness {dimensionless}
468
+ , !- Evaporative Condenser Pump Rated Power Consumption {W}
469
+ , !- Basin Heater Capacity {W/K}
470
+ , !- Basin Heater Setpoint Temperature {C}
471
+ , !- Basin Heater Availability Schedule Name
472
+ , !- Supply Water Storage Tank Name
473
+ , !- Condensate Collection Water Storage Tank Name
474
+ , !- Storage Tank Plant Connection Inlet Node Name
475
+ , !- Storage Tank Plant Connection Outlet Node Name
476
+ , !- Storage Tank Plant Connection Design Flow Rate {m3/s}
477
+ , !- Storage Tank Plant Connection Heat Transfer Effectiveness
478
+ , !- Storage Tank Minimum Operating Limit Fluid Temperature {C}
479
+ ; !- Storage Tank Maximum Operating Limit Fluid Temperature {C}"
480
+ # end of new TES coil string
481
+
482
+ # #Begin Coil Replacement
483
+ # iterate through all CoilSystem:Cooling objects and replace existing coils with TES coils
484
+ coil_selection.each do |sel_coil|
485
+ # get workspace object for selected coil from name
486
+ sel_coil = workspace.getObjectsByName(sel_coil)[0]
487
+ ice_cap = hardcaps[replacement_count] unless hardcaps.empty?
488
+
489
+ # get coil type in order to find get appropriate field keys
490
+ # fields of interest: (0 - Max Cap, 1 - Rated COP, 2 - Inlet Node, 3 - Outlet Node)
491
+ field_names = [] # may not be required. Check scope in Ruby documentation
492
+ sel_type = sel_coil.iddObject.type.valueDescription.to_s
493
+ if sel_type == 'Coil:Cooling:DX:SingleSpeed'
494
+ field_names = ['Gross Rated Total Cooling Capacity', 'Gross Rated Cooling COP',
495
+ 'Air Inlet Node Name', 'Air Outlet Node Name']
496
+ elsif sel_type == 'Coil:Cooling:DX:TwoSpeed'
497
+ field_names = ['High Speed Gross Rated Total Cooling Capacity', 'High Speed Gross Rated Cooling COP',
498
+ 'Air Inlet Node Name', 'Air Outlet Node Name']
499
+ end
500
+
501
+ # get field indices associated with desired keys
502
+ keys = []
503
+ field_names.each do |fn|
504
+ keys << sel_coil.iddObject.getFieldIndex(fn).to_i
505
+ end
506
+
507
+ # get old coil name and create new coil name
508
+ old_coil_name = sel_coil.getString(0).to_s
509
+ utss_name = "UTSS Coil #{replacement_count}"
510
+
511
+ # grab inlet and outlet air nodes form selected coil
512
+ inlet = sel_coil.getString(keys[2]).to_s
513
+ outlet = sel_coil.getString(keys[3]).to_s
514
+
515
+ # update inlet and outlet node names - not needed possibly add later if names become confusing
516
+
517
+ # create local ambient node
518
+ idf_oa_ambient = OpenStudio::IdfObject.new('OutdoorAir:Node'.to_IddObjectType)
519
+ ws_oa_ambient = workspace.addObject(idf_oa_ambient)
520
+ oa_ambient = ws_oa_ambient.get
521
+ oa_ambient.setString(0, "#{utss_name} OA Ambient Node")
522
+
523
+ # create new condenser inlet node
524
+ idf_condenser_inlet = OpenStudio::IdfObject.new('OutdoorAir:Node'.to_IddObjectType)
525
+ ws_condenser_inlet = workspace.addObject(idf_condenser_inlet)
526
+ condenser_inlet = ws_condenser_inlet.get
527
+ condenser_inlet.setString(0, "#{utss_name} Condenser Inlet Node")
528
+
529
+ # create new condenser inlet node
530
+ idf_condenser_outlet = OpenStudio::IdfObject.new('OutdoorAir:Node'.to_IddObjectType)
531
+ ws_condenser_outlet = workspace.addObject(idf_condenser_outlet)
532
+ condenser_outlet = ws_condenser_outlet.get
533
+ condenser_outlet.setString(0, "#{utss_name} Condenser Out Node")
534
+
535
+ # create a new UTSS object
536
+ idf_coil_object = OpenStudio::IdfObject.load(new_tes_string)
537
+ utss_obj = idf_coil_object.get
538
+ ws_tes_obj = workspace.addObject(utss_obj)
539
+ utss = ws_tes_obj.get
540
+ utss.setString(0, utss_name)
541
+
542
+ # get indices for required utss fields - reused variable names, consider changing if confusing
543
+ field_names = ['Evaporator Air Inlet Node Name', 'Evaporator Air Outlet Node Name',
544
+ 'Storage Tank Ambient Temperature Node Name',
545
+ 'Condenser Air Inlet Node Name', 'Condenser Air Outlet Node Name']
546
+
547
+ keys = []
548
+ field_names.each do |fn|
549
+ keys << utss.iddObject.getFieldIndex(fn).to_i
550
+ end
551
+
552
+ # updated required fields in utss object
553
+ utss.setString(keys[0], inlet) # air inlet node
554
+ utss.setString(keys[1], outlet) # air outlet node
555
+ utss.setString(keys[2], oa_ambient.name.to_s) # outdoor ambient node
556
+ utss.setString(keys[3], condenser_inlet.name.to_s) # condenser inlet node
557
+ utss.setString(keys[4], condenser_outlet.name.to_s) # condenser outlet node
558
+ utss.setString(7, ice_cap) # hardsized thermal storage capacity
559
+
560
+ # copy old coil information over to TES object (use low-speed info for 2spd coils)
561
+ if sel_coil.iddObject.name == 'Coil:Cooling:DX:SingleSpeed'
562
+ utss.setString(16, sel_coil.getString(2).get)
563
+ utss.setString(18, sel_coil.getString(4).get)
564
+ utss.setString(19, sel_coil.getString(9).get)
565
+ utss.setString(20, sel_coil.getString(10).get)
566
+ utss.setString(21, sel_coil.getString(11).get)
567
+ utss.setString(22, sel_coil.getString(12).get)
568
+ utss.setString(23, sel_coil.getString(13).get)
569
+ elsif sel_coil.iddObject.name == 'Coil:Cooling:DX:TwoSpeed'
570
+ utss.setString(16, sel_coil.getString(14).get)
571
+ utss.setString(18, sel_coil.getString(16).get)
572
+ utss.setString(19, sel_coil.getString(18).get)
573
+ utss.setString(20, sel_coil.getString(10).get)
574
+ utss.setString(21, sel_coil.getString(19).get)
575
+ utss.setString(22, sel_coil.getString(12).get)
576
+ utss.setString(23, sel_coil.getString(13).get)
577
+ end
578
+
579
+ # identify container object in which the coil is used
580
+ cooling_containers.each do |cont|
581
+ if cont.iddObject.type.valueDescription.to_s == 'CoilSystem:Cooling:DX'
582
+ if cont.getString(6).to_s == old_coil_name
583
+ cont.setString(5, 'Coil:Cooling:DX:SingleSpeed:ThermalStorage')
584
+ cont.setString(6, utss_name)
585
+ break
586
+ end
587
+ elsif cont.iddObject.type.valueDescription.to_s == 'AirLoopHVAC:UnitarySystem'
588
+ if cont.getString(15).to_s == old_coil_name
589
+ cont.setString(14, 'Coil:Cooling:DX:SingleSpeed:ThermalStorage')
590
+ cont.setString(15, utss_name)
591
+ break
592
+ end
593
+ end
594
+ end
595
+
596
+ # remove old coil
597
+ workspace.removeObject(sel_coil.handle)
598
+
599
+ # increment replacement count
600
+ replacement_count += 1
601
+
602
+ ## Add EMS Controller Components
603
+ # create EMS intended schedule sensor once
604
+ if replacement_count == 1
605
+ idf_sched_sensor = OpenStudio::IdfObject.new('EnergyManagementSystem:Sensor'.to_IddObjectType)
606
+ ws_sched_sensor = workspace.addObject(idf_sched_sensor)
607
+ new_sched_sensor = ws_sched_sensor.get
608
+ new_sched_sensor.setString(0, 'TESIntendedSchedule')
609
+ new_sched_sensor.setString(1, sched)
610
+ new_sched_sensor.setString(2, 'Schedule Value')
611
+ end
612
+
613
+ # clean-up variable names for EMS purposes (no spaces allowed)
614
+ u_name = utss_name.gsub(/\s/, '_')
615
+
616
+ # add EMS sensor for TES control
617
+ idf_sensor = OpenStudio::IdfObject.new('EnergyManagementSystem:Sensor'.to_IddObjectType)
618
+ ws_sensor = workspace.addObject(idf_sensor)
619
+ new_sensor = ws_sensor.get
620
+ new_sensor.setString(0, "#{u_name}_sTES")
621
+ new_sensor.setString(1, utss_name)
622
+ new_sensor.setString(2, 'Cooling Coil Ice Thermal Storage End Fraction')
623
+
624
+ # add EMS actuator for TES control
625
+ idf_actuator = OpenStudio::IdfObject.new('EnergyManagementSystem:Actuator'.to_IddObjectType)
626
+ ws_actuator = workspace.addObject(idf_actuator)
627
+ new_actuator = ws_actuator.get
628
+ new_actuator.setString(0, "#{u_name}_OpMode")
629
+ new_actuator.setString(1, utss_name)
630
+ new_actuator.setString(2, 'Coil:Cooling:DX:SingleSpeed:ThermalStorage')
631
+ new_actuator.setString(3, 'Operating Mode')
632
+
633
+ # add Global Variable to track min SOC from previous use
634
+ idf_gvar = OpenStudio::IdfObject.new('EnergyManagementSystem:GlobalVariable'.to_IddObjectType)
635
+ ws_gvar = workspace.addObject(idf_gvar)
636
+ new_gvar = ws_gvar.get
637
+ new_gvar.setString(0, "#{u_name}_MinSOC")
638
+
639
+ # add EMS program for TES control
640
+ program_string = "
641
+ EnergyManagementSystem:Program,
642
+ #{u_name}_Control, !- Name
643
+ SET #{u_name}_OpMode = TESIntendedSchedule,
644
+ IF CurrentEnvironment == 1,
645
+ SET #{u_name}_MinSOC = 1,
646
+ ENDIF,
647
+ IF (#{u_name}_OpMode == 5),
648
+ IF ( #{u_name}_sTES < 0.05 ),
649
+ SET #{u_name}_OpMode = 1,
650
+ ENDIF,
651
+ SET #{u_name}_MinSOC = #{u_name}_sTES,
652
+ ENDIF,
653
+ IF (#{u_name}_OpMode == 4),
654
+ IF ( #{u_name}_sTES > 0.99 ),
655
+ SET #{u_name}_OpMode = 1,
656
+ ENDIF,
657
+ ENDIF;"
658
+
659
+ idf_program = OpenStudio::IdfObject.load(program_string)
660
+ idf_prgm = idf_program.get
661
+ workspace.addObject(idf_prgm)
662
+
663
+ # add EMS program calling manager for TES control
664
+ idf_pcm = OpenStudio::IdfObject.new('EnergyManagementSystem:ProgramCallingManager'.to_IddObjectType)
665
+ ws_pcm = workspace.addObject(idf_pcm)
666
+ new_pcm = ws_pcm.get
667
+ new_pcm.setString(0, "#{u_name}_TES_PrgmCallMgr")
668
+ new_pcm.setString(1, 'AfterPredictorAfterHVACManagers')
669
+ new_pcm.setString(2, "#{u_name}_Control")
670
+
671
+ # register info
672
+ # Coil replaced, Coil Added, EMS Program Added
673
+ runner.registerInfo("Coil '#{old_coil_name}' was replaced with a unitary thermal storage system named" \
674
+ "'#{utss.name}' with a capacity of #{ice_cap} GJ.\n")
675
+ # end of coil replacement routine
676
+ end
677
+
678
+ # additional output for schedule verification
679
+ runner.registerInfo("The user-selected schedule for the ice unit operation is:\n\n#{user_schedule}")
680
+
681
+ # register initial and final conditions
682
+ runner.registerInitialCondition("The building started with #{cooling_containers.size} cooling coils.")
683
+ runner.registerFinalCondition("A total of #{replacement_count} cooling coils were replaced with thermal storage coil systems.")
684
+ true
685
+ end
686
+ end
687
+
688
+ # register the measure to be used by the application
689
+ AddDistributedIceStorageToAirLoopForLoadFlexibility.new.registerWithApplication