openstudio-ee 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/Rakefile +2 -0
- data/lib/measures/ImproveFanTotalEfficiencybyPercentage/measure.rb +333 -0
- data/lib/measures/ImproveFanTotalEfficiencybyPercentage/measure.xml +150 -0
- data/lib/measures/ReplaceFanTotalEfficiency/measure.rb +330 -0
- data/lib/measures/ReplaceFanTotalEfficiency/measure.xml +150 -0
- data/lib/measures/add_apszhp_to_each_zone/measure.rb +607 -0
- data/lib/measures/add_apszhp_to_each_zone/measure.xml +184 -0
- data/lib/measures/add_energy_recovery_ventilator/measure.rb +354 -0
- data/lib/measures/add_energy_recovery_ventilator/measure.xml +78 -0
- data/lib/measures/improve_simple_glazing_by_percentage/measure.rb +81 -0
- data/lib/measures/improve_simple_glazing_by_percentage/measure.xml +70 -0
- data/lib/measures/reduce_water_use_by_percentage/measure.rb +61 -0
- data/lib/measures/reduce_water_use_by_percentage/measure.xml +62 -0
- data/lib/measures/replace_hvac_with_gshp_and_doas/measure.rb +511 -0
- data/lib/measures/replace_hvac_with_gshp_and_doas/measure.xml +375 -0
- data/lib/measures/replace_hvac_with_gshp_and_doas/resources/OsLib_AedgMeasures.rb +454 -0
- data/lib/measures/replace_hvac_with_gshp_and_doas/resources/OsLib_Constructions.rb +221 -0
- data/lib/measures/replace_hvac_with_gshp_and_doas/resources/OsLib_Geometry.rb +41 -0
- data/lib/measures/replace_hvac_with_gshp_and_doas/resources/OsLib_HVAC.rb +1682 -0
- data/lib/measures/replace_hvac_with_gshp_and_doas/resources/OsLib_HelperMethods.rb +114 -0
- data/lib/measures/replace_hvac_with_gshp_and_doas/resources/OsLib_LightingAndEquipment.rb +99 -0
- data/lib/measures/replace_hvac_with_gshp_and_doas/resources/OsLib_Schedules.rb +142 -0
- data/lib/measures/replace_simple_glazing/measure.rb +86 -0
- data/lib/measures/replace_simple_glazing/measure.xml +78 -0
- data/lib/measures/set_boiler_thermal_efficiency/measure.rb +520 -0
- data/lib/measures/set_boiler_thermal_efficiency/measure.xml +78 -0
- data/lib/measures/set_water_heater_efficiency_heat_lossand_peak_water_flow_rate/measure.rb +207 -0
- data/lib/measures/set_water_heater_efficiency_heat_lossand_peak_water_flow_rate/measure.xml +78 -0
- data/lib/measures/tenant_star_internal_loads/measure.rb +134 -0
- data/lib/measures/tenant_star_internal_loads/measure.xml +67 -0
- data/lib/measures/tenant_star_internal_loads/resources/os_lib_helper_methods.rb +401 -0
- data/lib/measures/vr_fwith_doas/measure.rb +468 -0
- data/lib/measures/vr_fwith_doas/measure.xml +298 -0
- data/lib/measures/vr_fwith_doas/resources/OsLib_AedgMeasures.rb +454 -0
- data/lib/measures/vr_fwith_doas/resources/OsLib_Constructions.rb +221 -0
- data/lib/measures/vr_fwith_doas/resources/OsLib_Geometry.rb +41 -0
- data/lib/measures/vr_fwith_doas/resources/OsLib_HVAC.rb +1516 -0
- data/lib/measures/vr_fwith_doas/resources/OsLib_HelperMethods.rb +114 -0
- data/lib/measures/vr_fwith_doas/resources/OsLib_LightingAndEquipment.rb +99 -0
- data/lib/measures/vr_fwith_doas/resources/OsLib_Schedules.rb +142 -0
- data/lib/openstudio/ee_measures/version.rb +1 -1
- data/openstudio-ee.gemspec +7 -5
- metadata +48 -9
@@ -0,0 +1,607 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Author: Julien Marrec
|
4
|
+
# email: julien.marrec@gmail.com
|
5
|
+
|
6
|
+
# start the measure
|
7
|
+
class AddAPSZHPToEachZone < OpenStudio::Ruleset::ModelUserScript
|
8
|
+
# define the name that a user will see, this method may be deprecated as
|
9
|
+
# the display name in PAT comes from the name field in measure.xml
|
10
|
+
def name
|
11
|
+
return 'Add a PSZ-HP to each zone'
|
12
|
+
end
|
13
|
+
|
14
|
+
def description
|
15
|
+
return 'This will add a Rooftop Packaged Single Zone Heat Pump (RTU with DX cooling and DX heating coils) to each zone of the model.'
|
16
|
+
end
|
17
|
+
|
18
|
+
def modeler_description
|
19
|
+
return "Add a System 4 - PSZ-HP - unit for each zone. This is a single zone system.
|
20
|
+
Parameters:
|
21
|
+
- Double: COP cooling and COP heating (Double)
|
22
|
+
- Boolean: supplementary electric heating coil (Boolean)
|
23
|
+
- Pressure rise (Optional Double)
|
24
|
+
- Deletion of existing HVAC equipment (Boolean)
|
25
|
+
- DCV enabled or not (Boolean)
|
26
|
+
- Fan type: Variable Volume Fan (VFD) or not (Constant Volume) (Choice)
|
27
|
+
- Filter for the zone name (String): only zones that contains the string you input in filter will receive this system."
|
28
|
+
end
|
29
|
+
|
30
|
+
# define the arguments that the user will input
|
31
|
+
def arguments(model)
|
32
|
+
args = OpenStudio::Ruleset::OSArgumentVector.new
|
33
|
+
|
34
|
+
delete_existing = OpenStudio::Ruleset::OSArgument.makeBoolArgument('delete_existing', true)
|
35
|
+
delete_existing.setDisplayName('Delete any existing HVAC equipment?')
|
36
|
+
args << delete_existing
|
37
|
+
|
38
|
+
cop_cooling = OpenStudio::Ruleset::OSArgument.makeDoubleArgument('cop_cooling', true)
|
39
|
+
cop_cooling.setDisplayName('COP Cooling (SI)')
|
40
|
+
cop_cooling.setDefaultValue(3.1)
|
41
|
+
args << cop_cooling
|
42
|
+
|
43
|
+
cop_heating = OpenStudio::Ruleset::OSArgument.makeDoubleArgument('cop_heating', true)
|
44
|
+
cop_heating.setDisplayName('COP Heating (SI)')
|
45
|
+
cop_heating.setDefaultValue(3.1)
|
46
|
+
args << cop_heating
|
47
|
+
|
48
|
+
has_electric_coil = OpenStudio::Ruleset::OSArgument.makeBoolArgument('has_electric_coil', false)
|
49
|
+
has_electric_coil.setDisplayName('Include supplementary electric heating coils?')
|
50
|
+
has_electric_coil.setDefaultValue(true)
|
51
|
+
args << has_electric_coil
|
52
|
+
|
53
|
+
has_dcv = OpenStudio::Ruleset::OSArgument.makeBoolArgument('has_dcv', false)
|
54
|
+
has_dcv.setDisplayName('Enable Demand Controlled Ventilation?')
|
55
|
+
has_dcv.setDefaultValue(false)
|
56
|
+
args << has_dcv
|
57
|
+
|
58
|
+
chs = OpenStudio::StringVector.new
|
59
|
+
chs << 'Constant Volume (default)'
|
60
|
+
chs << 'Variable Volume (VFD)'
|
61
|
+
fan_type = OpenStudio::Ruleset::OSArgument.makeChoiceArgument('fan_type', chs, true)
|
62
|
+
fan_type.setDisplayName('Select fan type:')
|
63
|
+
args << fan_type
|
64
|
+
|
65
|
+
fan_pressure_rise = OpenStudio::Ruleset::OSArgument.makeDoubleArgument('fan_pressure_rise', false)
|
66
|
+
fan_pressure_rise.setDisplayName('Fan Pressure Rise (Pa)')
|
67
|
+
fan_pressure_rise.setDescription('Leave blank for default value')
|
68
|
+
# fan_pressure_rise.setDefaultValue(0)
|
69
|
+
args << fan_pressure_rise
|
70
|
+
|
71
|
+
chs = OpenStudio::StringVector.new
|
72
|
+
chs << 'By Space Type'
|
73
|
+
chs << "By Space Type's 'Standards Space Type'"
|
74
|
+
chs << 'By Zone Filter'
|
75
|
+
filter_type = OpenStudio::Ruleset::OSArgument.makeChoiceArgument('filter_type', chs, true)
|
76
|
+
filter_type.setDisplayName('How do you want to choose the affected zones?')
|
77
|
+
args << filter_type
|
78
|
+
|
79
|
+
# create an argument for a space type to be used in the model. Only return those that are used
|
80
|
+
spaceTypes = model.getSpaceTypes
|
81
|
+
usedSpaceTypes_handle = OpenStudio::StringVector.new
|
82
|
+
usedSpaceTypes_displayName = OpenStudio::StringVector.new
|
83
|
+
|
84
|
+
# Should normally be an OpenStudio::StringVector.new but it doesn't have a uniq! method and it works with a regular hash..
|
85
|
+
standardsSpaceType = []
|
86
|
+
|
87
|
+
spaceTypes.each do |spaceType|
|
88
|
+
if !spaceType.spaces.empty? # only show space types used in the building
|
89
|
+
usedSpaceTypes_handle << spaceType.handle.to_s
|
90
|
+
usedSpaceTypes_displayName << spaceType.name.to_s
|
91
|
+
|
92
|
+
if !spaceType.standardsSpaceType.empty?
|
93
|
+
standardsSpaceType << spaceType.standardsSpaceType.get
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# make an argument for space type
|
99
|
+
space_type = OpenStudio::Ruleset::OSArgument.makeChoiceArgument('space_type', usedSpaceTypes_handle, usedSpaceTypes_displayName, false)
|
100
|
+
space_type.setDisplayName('a. Which Space Type?')
|
101
|
+
args << space_type
|
102
|
+
|
103
|
+
# Argument for Standards Space Type
|
104
|
+
|
105
|
+
# First, make it unique
|
106
|
+
standardsSpaceType.uniq!
|
107
|
+
standards_space_type = OpenStudio::Ruleset::OSArgument.makeChoiceArgument('standards_space_type', standardsSpaceType, false)
|
108
|
+
standards_space_type.setDisplayName('b. Which Standards Space Type')
|
109
|
+
args << standards_space_type
|
110
|
+
|
111
|
+
zone_filter = OpenStudio::Ruleset::OSArgument.makeStringArgument('zone_filter', false)
|
112
|
+
zone_filter.setDisplayName('c. Only Apply to Zones that contain the following string')
|
113
|
+
zone_filter.setDescription("Case insensitive. For example, type 'retail' to apply to zones that have the word 'retail' or 'REtaiL' in their name. Leave blank to apply to all zones")
|
114
|
+
args << zone_filter
|
115
|
+
|
116
|
+
return args
|
117
|
+
end # end the arguments method
|
118
|
+
|
119
|
+
# define what happens when the measure is run
|
120
|
+
def run(model, runner, user_arguments)
|
121
|
+
super(model, runner, user_arguments)
|
122
|
+
|
123
|
+
# use the built-in error checking
|
124
|
+
if !runner.validateUserArguments(arguments(model), user_arguments)
|
125
|
+
return false
|
126
|
+
end
|
127
|
+
|
128
|
+
# Retrieve arguments' values
|
129
|
+
delete_existing = runner.getBoolArgumentValue('delete_existing', user_arguments)
|
130
|
+
cop_cooling = runner.getDoubleArgumentValue('cop_cooling', user_arguments)
|
131
|
+
cop_heating = runner.getDoubleArgumentValue('cop_heating', user_arguments)
|
132
|
+
has_electric_coil = runner.getBoolArgumentValue('has_electric_coil', user_arguments)
|
133
|
+
has_dcv = runner.getBoolArgumentValue('has_dcv', user_arguments)
|
134
|
+
|
135
|
+
# Get fan_pressure_rise: this is an OptionalDouble - we'll use '.get' later
|
136
|
+
fan_pressure_rise = runner.getOptionalDoubleArgumentValue('fan_pressure_rise', user_arguments)
|
137
|
+
|
138
|
+
# FanType
|
139
|
+
fan_type = runner.getStringArgumentValue('fan_type', user_arguments)
|
140
|
+
runner.registerInfo("Fan type: #{fan_type}")
|
141
|
+
|
142
|
+
if fan_type == 'Variable Volume (VFD)'
|
143
|
+
has_vfd = true
|
144
|
+
else
|
145
|
+
has_vfd = false
|
146
|
+
end
|
147
|
+
|
148
|
+
filter_type = runner.getStringArgumentValue('filter_type', user_arguments)
|
149
|
+
|
150
|
+
if filter_type == 'By Space Type'
|
151
|
+
space_type = runner.getOptionalWorkspaceObjectChoiceValue('space_type', user_arguments, model)
|
152
|
+
if !space_type.empty?
|
153
|
+
space_type = space_type.get
|
154
|
+
if !space_type.to_SpaceType.empty?
|
155
|
+
space_type = space_type.to_SpaceType.get
|
156
|
+
zones = []
|
157
|
+
space_type.spaces.each do |space|
|
158
|
+
if !space.thermalZone.empty?
|
159
|
+
z = space.thermalZone.get
|
160
|
+
zones << z
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
elsif filter_type == "By Space Type's 'Standards Space Type'"
|
167
|
+
|
168
|
+
standards_space_type = runner.getOptionalStringArgumentValue('standards_space_type', user_arguments)
|
169
|
+
puts standards_space_type.class
|
170
|
+
|
171
|
+
if !standards_space_type.empty?
|
172
|
+
standards_space_type = standards_space_type.get
|
173
|
+
puts standards_space_type
|
174
|
+
space_types = model.getSpaceTypes
|
175
|
+
|
176
|
+
zones = []
|
177
|
+
|
178
|
+
space_types.each do |space_type|
|
179
|
+
if space_type.standardsSpaceType.to_s.casecmp(standards_space_type).zero?
|
180
|
+
space_type.spaces.each do |space|
|
181
|
+
if !space.thermalZone.empty?
|
182
|
+
z = space.thermalZone.get
|
183
|
+
# We MUST check if zone isn't in there yet (or at the end do zones.uniq!) because several spaces can refer to the same thermal zone!
|
184
|
+
if !zones.include?(z)
|
185
|
+
zones << z
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
else
|
194
|
+
# Zone filter
|
195
|
+
zone_filter = runner.getOptionalStringArgumentValue('zone_filter', user_arguments)
|
196
|
+
|
197
|
+
# Get all thermal zones
|
198
|
+
all_zones = model.getThermalZones
|
199
|
+
|
200
|
+
# Array to store the zones that match the filter
|
201
|
+
zones = []
|
202
|
+
all_zones.each do |z|
|
203
|
+
# Skip zone if name doesn't include zone_filter
|
204
|
+
# Putting everything in Upper Case to make it case insensitive
|
205
|
+
if !zone_filter.empty?
|
206
|
+
if z.name.to_s.upcase.include? zone_filter.to_s.upcase
|
207
|
+
zones << z
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
if zones.empty?
|
213
|
+
runner.registerError("Your zone filter #{zone_filter} did not match anything")
|
214
|
+
return false
|
215
|
+
end
|
216
|
+
|
217
|
+
end # End of if filter_type
|
218
|
+
|
219
|
+
# Output zone names to console
|
220
|
+
puts "\n\n================ ZONES THAT MATCHED THE FILTER ================\n"
|
221
|
+
zones.each do |z|
|
222
|
+
puts z.name
|
223
|
+
end
|
224
|
+
|
225
|
+
# info for initial condition
|
226
|
+
initial_num_air_loops_demand_control = 0
|
227
|
+
final_num_air_loops_demand_control = 0
|
228
|
+
initial_num_fan_VFD = 0
|
229
|
+
final_num_fan_VFD = 0
|
230
|
+
delete_existing_air_loops = 0
|
231
|
+
delete_existing_chiller_loops = 0
|
232
|
+
delete_existing_condenser_loops = 0
|
233
|
+
affected_loops = 0
|
234
|
+
|
235
|
+
# If we need to delete existing HVAC loops, we'll store the PRE-EXISTING Loops in the following variables,
|
236
|
+
# They will be used for clean up at the end
|
237
|
+
if delete_existing
|
238
|
+
air_loops = model.getAirLoopHVACs
|
239
|
+
runner.registerInfo("Number of initial AirLoopHVACs: #{air_loops.size}")
|
240
|
+
plant_loops = model.getPlantLoops
|
241
|
+
runner.registerInfo("Number of initial PlantLoops: #{plant_loops.size}")
|
242
|
+
end
|
243
|
+
|
244
|
+
# For each thermal zones (zones is initialized above, depending on which filter you chose)
|
245
|
+
zones.each do |z|
|
246
|
+
# Create a system 4 (PSZ-HP)
|
247
|
+
air_handler = OpenStudio::Model.addSystemType4(model).to_AirLoopHVAC.get
|
248
|
+
|
249
|
+
# Set name of Air Loop to be thermal_zone + 'Airloop'
|
250
|
+
# Local variable name convention for a non-constant (dynamic) value is 'snake_case'
|
251
|
+
base_name = z.name.to_s
|
252
|
+
air_handler.setName(base_name + ' AirLoop')
|
253
|
+
|
254
|
+
# Get existing fan, created with System 4, constant volume by default
|
255
|
+
old_fan = air_handler.supplyComponents(OpenStudio::Model::FanConstantVolume.iddObjectType).first
|
256
|
+
old_fan = old_fan.to_FanConstantVolume.get
|
257
|
+
|
258
|
+
# If you want a VFD, we replace it with a Variable Volume one
|
259
|
+
if has_vfd
|
260
|
+
|
261
|
+
# Get the outlet node after the existing fan on the loop
|
262
|
+
next_node = old_fan.outletModelObject.get.to_Node.get
|
263
|
+
|
264
|
+
# Create the new Variable speed fan
|
265
|
+
fan = OpenStudio::Model::FanVariableVolume.new(model)
|
266
|
+
|
267
|
+
# Add the new fan to the oulet node of the existing fan
|
268
|
+
# before deleting the existing one
|
269
|
+
fan.addToNode(next_node)
|
270
|
+
|
271
|
+
# Remove the existing fan. When this happens, either the pump's
|
272
|
+
# inlet or outlet node will be deleted and the other will remain
|
273
|
+
old_fan.remove
|
274
|
+
|
275
|
+
# Rename the fan clearly
|
276
|
+
fan.setName(base_name + ' Variable Volume Fan')
|
277
|
+
|
278
|
+
# If fan_pressure_rise has a non zero null value, assign it.
|
279
|
+
if !fan_pressure_rise.empty?
|
280
|
+
# We need the .get because this is an OptionalDouble. the .get will return a Double (float)
|
281
|
+
fan.setPressureRise(fan_pressure_rise.get)
|
282
|
+
runner.registerInfo("Fan '#{fan.name}' was assigned pressure rise of '#{fan_pressure_rise.get}' Pa")
|
283
|
+
end
|
284
|
+
|
285
|
+
final_num_fan_VFD += 1
|
286
|
+
|
287
|
+
else
|
288
|
+
# If VFD isn't wanted, we just rename the constant volume fan
|
289
|
+
old_fan.setName(base_name + ' Constant Volume Fan')
|
290
|
+
|
291
|
+
# If fan_pressure_rise has a non zero null value, assign it.
|
292
|
+
if !fan_pressure_rise.empty?
|
293
|
+
# We need the .get because this is an OptionalDouble. the .get will return a Double (float)
|
294
|
+
old_fan.setPressureRise(fan_pressure_rise.get)
|
295
|
+
puts "Fan '#{old_fan.name}' was assigned pressure rise of '#{fan_pressure_rise.get}' Pa"
|
296
|
+
end
|
297
|
+
|
298
|
+
end
|
299
|
+
|
300
|
+
# The Cooling coil expects an OptionalDouble
|
301
|
+
coil = air_handler.supplyComponents(OpenStudio::Model::CoilCoolingDXSingleSpeed.iddObjectType).first
|
302
|
+
coil = coil.to_CoilCoolingDXSingleSpeed.get
|
303
|
+
# Set CoolingCoil COP
|
304
|
+
coil.setRatedCOP(OpenStudio::OptionalDouble.new(cop_cooling))
|
305
|
+
# Set CoolingCoil Name
|
306
|
+
coil.setName(base_name + ' Coil Cooling DX Single Speed')
|
307
|
+
|
308
|
+
# The Heating coil expects a Double
|
309
|
+
coilheating = air_handler.supplyComponents(OpenStudio::Model::CoilHeatingDXSingleSpeed.iddObjectType).first
|
310
|
+
coilheating = coilheating.to_CoilHeatingDXSingleSpeed.get
|
311
|
+
# Set HeatingCoil COP
|
312
|
+
coilheating.setRatedCOP(cop_heating)
|
313
|
+
# Set HeatingCoil Name
|
314
|
+
coilheating.setName(base_name + ' Coil Heating DX Single Speed')
|
315
|
+
|
316
|
+
# Delete the electric heating coil if unwanted
|
317
|
+
if !has_electric_coil
|
318
|
+
coilheatingelec = air_handler.supplyComponents(OpenStudio::Model::CoilHeatingElectric.iddObjectType).first
|
319
|
+
coilheatingelec.remove
|
320
|
+
end
|
321
|
+
|
322
|
+
# Enable DCV (dunno if working)
|
323
|
+
if has_dcv
|
324
|
+
|
325
|
+
# get air_handler supply components
|
326
|
+
supply_components = air_handler.supplyComponents
|
327
|
+
|
328
|
+
# find AirLoopHVACOutdoorAirSystem on loop
|
329
|
+
supply_components.each do |supply_component|
|
330
|
+
hVACComponent = supply_component.to_AirLoopHVACOutdoorAirSystem
|
331
|
+
if !hVACComponent.empty?
|
332
|
+
hVACComponent = hVACComponent.get
|
333
|
+
|
334
|
+
# get ControllerOutdoorAir
|
335
|
+
controller_oa = hVACComponent.getControllerOutdoorAir
|
336
|
+
controller_oa.setName(base_name + ' Controller Outdoor Air')
|
337
|
+
|
338
|
+
# get ControllerMechanicalVentilation
|
339
|
+
controller_mv = controller_oa.controllerMechanicalVentilation
|
340
|
+
|
341
|
+
# check if demand control is enabled, if not, then enable it
|
342
|
+
if controller_mv.demandControlledVentilation == true
|
343
|
+
initial_num_air_loops_demand_control += 1
|
344
|
+
else
|
345
|
+
controller_mv.setDemandControlledVentilation(true)
|
346
|
+
puts "Enabling demand control ventilation for #{air_handler.name}"
|
347
|
+
end # End of if
|
348
|
+
final_num_air_loops_demand_control += 1
|
349
|
+
|
350
|
+
end # End of HVACComponent.empty?
|
351
|
+
end # end of supply component do loop
|
352
|
+
|
353
|
+
end # End of has_dcv loop
|
354
|
+
|
355
|
+
# Add a branch for the zone in question
|
356
|
+
air_handler.addBranchForZone(z)
|
357
|
+
|
358
|
+
# Counter
|
359
|
+
affected_loops += 1
|
360
|
+
end # end of do loop on each thermal zone
|
361
|
+
|
362
|
+
# CLEAN-UP SECTION
|
363
|
+
# Idea: loop on PRE-EXISTING AirLoops, delete all that don't have any zones anymore
|
364
|
+
# Then loop on chiller loop, delete all that don't have a coil connected to an air loop
|
365
|
+
# then loop on condenser water, delette all that don't have a chiller anymore
|
366
|
+
|
367
|
+
# If we need to delete existing HVAC loops, we'll loop on the PRE-EXISTING Loops we stored earlier
|
368
|
+
if delete_existing
|
369
|
+
|
370
|
+
# Arrays to store the affected loops
|
371
|
+
chiller_plant_loops = []
|
372
|
+
boiler_plant_loops = []
|
373
|
+
condenser_plant_loops = []
|
374
|
+
|
375
|
+
# Display separator for clarity
|
376
|
+
runner.registerInfo('')
|
377
|
+
runner.registerInfo('========================== CLEAN-UP: AIR LOOPS ==========================')
|
378
|
+
|
379
|
+
# Loop on the pre-existing air loops (not the ones that were created above)
|
380
|
+
air_loops.each do |air_loop|
|
381
|
+
# Check if it's got a thermal zone attached left or not..
|
382
|
+
# We assume we'll delete it unless...
|
383
|
+
delete_flag = true
|
384
|
+
|
385
|
+
air_loop.demandComponents.each do |comp|
|
386
|
+
# If there is at least a single zone left, we can't delete it
|
387
|
+
if comp.to_ThermalZone.is_initialized
|
388
|
+
delete_flag = false
|
389
|
+
end # end of if
|
390
|
+
end # end of do loop on comp
|
391
|
+
|
392
|
+
# If deletion is warranted
|
393
|
+
if delete_flag
|
394
|
+
# before deletion, let's get the potential associated plant loop.
|
395
|
+
if air_loop.supplyComponents(OpenStudio::Model::CoilCoolingWater.iddObjectType).empty?
|
396
|
+
puts "Air loop '#{air_loop.name}' DOES NOT HAVE a CoilHeatingWater"
|
397
|
+
else
|
398
|
+
cooling_coil = air_loop.supplyComponents(OpenStudio::Model::CoilCoolingWater.iddObjectType).first.to_CoilCoolingWater.get
|
399
|
+
chiller_plant_loop = cooling_coil.plantLoop.get
|
400
|
+
# Store handle in array
|
401
|
+
chiller_plant_loops << chiller_plant_loop
|
402
|
+
runner.registerInfo("Air loop '#{air_loop.name}' has a CoilCoolingWater, connected to CHILLER plant loop '#{chiller_plant_loop.name}'")
|
403
|
+
end
|
404
|
+
if air_loop.supplyComponents(OpenStudio::Model::CoilHeatingWater.iddObjectType).empty?
|
405
|
+
puts "Air loop '#{air_loop.name}' DOES NOT HAVE a CoilHeatingWater"
|
406
|
+
else
|
407
|
+
heating_coil = air_loop.supplyComponents(OpenStudio::Model::CoilCoolingWater.iddObjectType).first.to_CoilCoolingWater.get
|
408
|
+
boiler_plant_loop = heating_coil.plantLoop.get
|
409
|
+
# Store handle in array
|
410
|
+
boiler_plant_loops << boiler_plant_loop
|
411
|
+
runner.registerInfo("Air loop '#{air_loop.name}' has a CoilHeatinggWater, connected to BOILER plant loop '#{boiler_plant_loop.name}'")
|
412
|
+
end
|
413
|
+
|
414
|
+
# Now we can delete and report.
|
415
|
+
air_loop.remove
|
416
|
+
runner.registerInfo("DELETED: Air loop '#{air_loop.name}' doesn't have Thermal zones attached and was removed")
|
417
|
+
delete_existing_air_loops += 1
|
418
|
+
else
|
419
|
+
runner.registerInfo("Air Loop '#{air_loop.name}' has thermal zones and was not deleted")
|
420
|
+
end # end if delete_flag
|
421
|
+
end # end air_loops.each do
|
422
|
+
|
423
|
+
# Display separator for clarity
|
424
|
+
runner.registerInfo('')
|
425
|
+
runner.registerInfo('====================== CLEAN-UP: CHILLER PLANT LOOPS ======================')
|
426
|
+
|
427
|
+
# First pass on plant loops: chilled water loops.
|
428
|
+
chiller_plant_loops.each do |chiller_plant_loop|
|
429
|
+
puts "Chiller plant loop name: #{chiller_plant_loop.name}"
|
430
|
+
|
431
|
+
# Check if the chiller plant loop has remaining demand components
|
432
|
+
|
433
|
+
# Delete flag: first assumption is that yes... unless!
|
434
|
+
delete_flag = true
|
435
|
+
|
436
|
+
if chiller_plant_loop.demandComponents(OpenStudio::Model::CoilCoolingWater.iddObjectType).empty?
|
437
|
+
puts "Chiller Plant loop '#{chiller_plant_loop.name}' DOES NOT HAVE a CoilCoolingWater"
|
438
|
+
else
|
439
|
+
puts "Chiller Plant loop '#{chiller_plant_loop.name}' has a CoilCoolingWater"
|
440
|
+
cooling_coil = chiller_plant_loop.demandComponents(OpenStudio::Model::CoilCoolingWater.iddObjectType).first.to_CoilCoolingWater.get
|
441
|
+
if cooling_coil.airLoopHVAC.empty?
|
442
|
+
puts "But Cooling coil '#{cooling_coil.name}' is not connected to any airloopHVAC"
|
443
|
+
else
|
444
|
+
runner.registerInfo("And Cooling coil '#{cooling_coil.name}' is connected to airloopHVAC '#{cooling_coil.airLoopHVAC.get.name}' and therefore can't be deleted")
|
445
|
+
# In this case, we can't delete the chiller plant loop
|
446
|
+
delete_flag = false
|
447
|
+
end # end cooling_coil.airLoopHVAC.empty?
|
448
|
+
|
449
|
+
end # end of chiller_plant_loop.demandComponents CoilCoolingWater
|
450
|
+
|
451
|
+
# We know it's a chiller plant so this is likely unnecessary, but better safe than sorry
|
452
|
+
if chiller_plant_loop.demandComponents(OpenStudio::Model::WaterUseConnections.iddObjectType).empty?
|
453
|
+
puts "Chiller Plant loop '#{chiller_plant_loop.name}' DOES NOT HAVE WaterUseConnections"
|
454
|
+
else
|
455
|
+
runner.registerInfo("Chiller Plant loop '#{chiller_plant_loop.name}' has WaterUseConnections and therefore can't be deleted")
|
456
|
+
delete_flag = false
|
457
|
+
end
|
458
|
+
|
459
|
+
# If deletion is warranted
|
460
|
+
if delete_flag
|
461
|
+
|
462
|
+
# This section below is actually optional (but it's nice to only delete affected ones
|
463
|
+
# before deletion, let's get the potential associated condenser water plant loop.
|
464
|
+
if chiller_plant_loop.supplyComponents(OpenStudio::Model::ChillerElectricEIR.iddObjectType).empty?
|
465
|
+
puts "Chiller Plant loop '#{chiller_plant_loop.name}' DOES NOT HAVE an electric chiller"
|
466
|
+
else
|
467
|
+
chiller = chiller_plant_loop.supplyComponents(OpenStudio::Model::ChillerElectricEIR.iddObjectType).first.to_ChillerElectricEIR.get
|
468
|
+
puts "Chiller Plant loop '#{chiller_plant_loop.name}' has an electric chiller '#{chiller.name}' with condenser type '#{chiller.condenserType}'"
|
469
|
+
# Check directly if chiller has a secondaryPlantLoop (no need to check if chiller.condenserType == 'WaterCooled' first)
|
470
|
+
if chiller.secondaryPlantLoop.is_initialized
|
471
|
+
# Chiller is WaterCooled therefore should be connected to a condenser water loop
|
472
|
+
condenser_plant_loop = chiller.secondaryPlantLoop.get
|
473
|
+
condenser_plant_loops << condenser_plant_loop
|
474
|
+
runner.registerInfo("Chiller PlantLoop '#{chiller_plant_loop.name}' has a Water Cooled chiller connected to Condenser Plant Loop '#{condenser_plant_loop.name}'")
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
# Now we can delete and report.
|
479
|
+
chiller_plant_loop.remove
|
480
|
+
delete_existing_chiller_loops += 1
|
481
|
+
# Should I delete the chiller as well? It remains...
|
482
|
+
|
483
|
+
runner.registerInfo("DELETED: Chiller PlantLoop '#{chiller_plant_loop.name}' wasn't connected to any AirLoopHVAC nor WaterUseConnections and therefore was removed")
|
484
|
+
|
485
|
+
end # end of delete_flag
|
486
|
+
end # end of chiller_plant_loops.each do
|
487
|
+
|
488
|
+
# Display separator for clarity
|
489
|
+
runner.registerInfo('')
|
490
|
+
runner.registerInfo('===================== CLEAN-UP: CONDENSER PLANT LOOPS ====================')
|
491
|
+
# Second pass on plant loops: condenser water loops.
|
492
|
+
condenser_plant_loops.each do |condenser_plant_loop|
|
493
|
+
delete_flag = true
|
494
|
+
|
495
|
+
# If it has got a chiller as a demand component, it could still be empty
|
496
|
+
if !condenser_plant_loop.demandComponents(OpenStudio::Model::ChillerElectricEIR.iddObjectType).empty?
|
497
|
+
|
498
|
+
chiller = condenser_plant_loop.demandComponents(OpenStudio::Model::ChillerElectricEIR.iddObjectType).first.to_ChillerElectricEIR.get
|
499
|
+
|
500
|
+
# If chiller is actually connected to a chilled water node, then we shall not delete it
|
501
|
+
if !chiller.chilledWaterInletNodeName.empty?
|
502
|
+
runner.registerInfo("On Condenser PlantLoop '#{condenser_plant_loop.name}, there is a demand component of type Chiller '#{chiller.name}'" \
|
503
|
+
' that is connected to a chilled water loop and therefore cannot be deleted')
|
504
|
+
delete_flag = false
|
505
|
+
else
|
506
|
+
puts "Plant loop '#{condenser_plant_loop.name}, Chiller '#{chiller.name}' isn't connected to a chilled water loop"
|
507
|
+
end # end of if chiller.chilledWaterInletNodeName
|
508
|
+
end # end of plant_loop.demandComponents
|
509
|
+
|
510
|
+
# if deletion is warranted
|
511
|
+
if delete_flag
|
512
|
+
condenser_plant_loop.remove
|
513
|
+
delete_existing_condenser_loops += 1
|
514
|
+
runner.registerInfo("DELETED: Plant loop '#{condenser_plant_loop.name}' isn't connected to any chilled water loop")
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
runner.registerInfo('')
|
519
|
+
runner.registerInfo("For more information, go to 'Advanced Output'")
|
520
|
+
|
521
|
+
# This is the generic way of looping on all loops, checking if it's a condenser plant loop, and to delete it unless it's got a chiller that is connected to chilled water plant loop
|
522
|
+
# plant_loops.each do |plant_loop|
|
523
|
+
# # Skip the chiller_plant_loops
|
524
|
+
# #next if chiller_plant_loops.include? plant_loop
|
525
|
+
# if chiller_plant_loops.include? plant_loop
|
526
|
+
# runner.registerInfo("Skipping Plant loop '#{plant_loop.name}' because it is a chiller plant")
|
527
|
+
# next
|
528
|
+
# end
|
529
|
+
# runner.registerInfo("Plant loop '#{plant_loop.name}'")
|
530
|
+
#
|
531
|
+
# # Until we know that it is a condenser loop for sure, we assume we can't delete it
|
532
|
+
# delete_flag = false
|
533
|
+
#
|
534
|
+
# # If it has got a chiller as a demand component, it's a condenser water loop
|
535
|
+
# if not plant_loop.demandComponents(OpenStudio::Model::ChillerElectricEIR::iddObjectType).empty?
|
536
|
+
# # Now, we assume we'll delete the loop unless it's actually connected and therefore usefull
|
537
|
+
# delete_flag = true
|
538
|
+
# chiller = plant_loop.demandComponents(OpenStudio::Model::ChillerElectricEIR::iddObjectType).first.to_ChillerElectricEIR.get
|
539
|
+
# # If chiller is actually connected to a chilled water node, then we shall not delete it
|
540
|
+
# if not chiller.chilledWaterInletNodeName.empty?
|
541
|
+
# runner.registerInfo("On Condenser PlantLoop '#{plant_loop.name}, there is a demand component of type Chiller '#{chiller.name}'" +
|
542
|
+
# " that is connected to a chilled water loop and therefore cannot be deleted")
|
543
|
+
# delete_flag = false
|
544
|
+
# else
|
545
|
+
# runner.registerInfo("Plant loop '#{plant_loop.name}, Chiller '#{chiller.name}' isn't connected to a chilled water loop")
|
546
|
+
# end #end of if chiller.chilledWaterInletNodeName
|
547
|
+
# end #end of plant_loop.demandComponents
|
548
|
+
#
|
549
|
+
# # if deletion is warranted
|
550
|
+
# if delete_flag
|
551
|
+
# plant_loop.remove
|
552
|
+
# delete_existing_condenser_loops += 1
|
553
|
+
# runner.registerInfo("DELETED: Plant loop '#{plant_loop.name}'")
|
554
|
+
# end
|
555
|
+
#
|
556
|
+
# end #end of plant_loops.each do
|
557
|
+
|
558
|
+
# Third pass on plant loops: boiler water loops.
|
559
|
+
# TO WRITE
|
560
|
+
|
561
|
+
end # end of if delete_existing
|
562
|
+
|
563
|
+
# Report Initial Condition
|
564
|
+
if delete_existing
|
565
|
+
air_loop_str = "\n #{delete_existing_air_loops} existing AirLoopHVACs have been deleted"
|
566
|
+
chiller_plant_loop_str = "\n #{delete_existing_chiller_loops} existing Chiller PlantLoops have been deleted"
|
567
|
+
condenser_plant_loop_str = "\n #{delete_existing_condenser_loops} existing Condenser PlantLoops have been deleted"
|
568
|
+
else
|
569
|
+
air_loop_str = ''
|
570
|
+
chiller_plant_loop_str = ''
|
571
|
+
condenser_plant_loop_str = ''
|
572
|
+
end # end of delete_existing
|
573
|
+
|
574
|
+
runner.registerInitialCondition("Initially #{initial_num_air_loops_demand_control} air loops had demand controlled ventilation enabled" +
|
575
|
+
air_loop_str + chiller_plant_loop_str + condenser_plant_loop_str + "\n")
|
576
|
+
|
577
|
+
# Report final condition
|
578
|
+
base_str = "There are #{OpenStudio.toNeatString(affected_loops, 0, true)} zones for which a PSZ-HP system was " \
|
579
|
+
"created with a Cooling COP (SI) of #{OpenStudio.toNeatString(cop_cooling, 2, true)} " \
|
580
|
+
"and a Heating COP (SI) of #{OpenStudio.toNeatString(cop_heating, 2, true)}."
|
581
|
+
|
582
|
+
if has_electric_coil
|
583
|
+
elec_str = 'Supplementary electric heating coils were added.'
|
584
|
+
else
|
585
|
+
elec_str = 'Supplementary electrical heating coils were NOT included.'
|
586
|
+
end # end of has_electric_coil
|
587
|
+
|
588
|
+
if has_vfd
|
589
|
+
fan_str = "Fan type was changed to be Variable Volume (VFD) for #{final_num_fan_VFD} fans."
|
590
|
+
else
|
591
|
+
fan_str = 'Fan type was chosen to be Constant Volume.'
|
592
|
+
end # end of has_vfd
|
593
|
+
|
594
|
+
if final_num_air_loops_demand_control == 0
|
595
|
+
dcv_str = "Demand Controlled Ventilation wasn't enabled for the new air loops"
|
596
|
+
else
|
597
|
+
dcv_str = "#{final_num_air_loops_demand_control} air loops now have demand controlled ventilation enabled"
|
598
|
+
end
|
599
|
+
|
600
|
+
runner.registerFinalCondition(base_str + "\n" + elec_str + "\n" + fan_str + "\n" + dcv_str + "\n \n")
|
601
|
+
|
602
|
+
return true
|
603
|
+
end # end the run method
|
604
|
+
end # end the measure
|
605
|
+
|
606
|
+
# this allows the measure to be used by the application
|
607
|
+
AddAPSZHPToEachZone.new.registerWithApplication
|