openstudio-load-flexibility-measures 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.gitignore +36 -0
- data/.rubocop.yml +9 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +3 -0
- data/README.md +42 -0
- data/Rakefile +15 -0
- data/doc_templates/LICENSE.md +27 -0
- data/doc_templates/README.md.erb +42 -0
- data/doc_templates/copyright_erb.txt +36 -0
- data/doc_templates/copyright_js.txt +4 -0
- data/doc_templates/copyright_ruby.txt +34 -0
- data/lib/measures/add_central_hpwh_for_load_flexibility/LICENSE.md +1 -0
- data/lib/measures/add_central_hpwh_for_load_flexibility/README.md +186 -0
- data/lib/measures/add_central_hpwh_for_load_flexibility/README.md.erb +42 -0
- data/lib/measures/add_central_hpwh_for_load_flexibility/docs/Flexible Domestic Hot Water Implementation Guide.pdf +0 -0
- data/lib/measures/add_central_hpwh_for_load_flexibility/measure.rb +648 -0
- data/lib/measures/add_central_hpwh_for_load_flexibility/measure.xml +398 -0
- data/lib/measures/add_central_hpwh_for_load_flexibility/tests/SmallHotel-2A.osm +42893 -0
- data/lib/measures/add_central_hpwh_for_load_flexibility/tests/add_central_hpwh_for_load_flexibility.rb +98 -0
- data/lib/measures/add_distributed_ice_storage_to_air_loop_for_load_flexibility/LICENSE.md +13 -0
- data/lib/measures/add_distributed_ice_storage_to_air_loop_for_load_flexibility/README.md +189 -0
- data/lib/measures/add_distributed_ice_storage_to_air_loop_for_load_flexibility/measure.rb +689 -0
- data/lib/measures/add_distributed_ice_storage_to_air_loop_for_load_flexibility/measure.xml +253 -0
- data/lib/measures/add_distributed_ice_storage_to_air_loop_for_load_flexibility/resources/TESCurves.idf +1059 -0
- data/lib/measures/add_distributed_ice_storage_to_air_loop_for_load_flexibility/tests/MeasureTest.osm +9507 -0
- data/lib/measures/add_distributed_ice_storage_to_air_loop_for_load_flexibility/tests/add_distributed_ice_storage_to_air_loop_for_load_flexibility_test.rb +96 -0
- data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/LICENSE.md +13 -0
- data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/README.md +264 -0
- data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/README.md.erb +42 -0
- data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/docs/Ice Measure Implementation Guide.pdf +0 -0
- data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/measure.rb +1310 -0
- data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/measure.xml +506 -0
- data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/resources/OsLib_Schedules.rb +173 -0
- data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/tests/add_ice_storage_to_plant_loop_for_load_flexibility_test.rb +202 -0
- data/lib/measures/add_ice_storage_to_plant_loop_for_load_flexibility/tests/ice_test_model.osm +21523 -0
- data/lib/openstudio/load_flexibility_measures.rb +50 -0
- data/lib/openstudio/load_flexibility_measures/version.rb +40 -0
- data/openstudio-load-flexibility-measures.gemspec +32 -0
- metadata +172 -0
@@ -0,0 +1,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
|