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,221 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OsLib_Constructions
|
4
|
+
# infer insulation layer from a construction
|
5
|
+
def self.inferInsulationLayer(construction, minThermalResistance)
|
6
|
+
construction_layers = construction.layers
|
7
|
+
counter = 0
|
8
|
+
max_resistance = 0
|
9
|
+
thermal_resistance_array = []
|
10
|
+
|
11
|
+
# loop through construction layers and infer insulation layer/material
|
12
|
+
construction_layers.each do |construction_layer|
|
13
|
+
construction_thermal_resistance = construction_layer.to_OpaqueMaterial.get.thermalResistance
|
14
|
+
if !thermal_resistance_array.empty?
|
15
|
+
if construction_thermal_resistance > max_resistance
|
16
|
+
thermal_resistance_array = [construction_layer, counter, construction_thermal_resistance]
|
17
|
+
max_resistance = construction_thermal_resistance
|
18
|
+
end
|
19
|
+
else
|
20
|
+
thermal_resistance_array = [construction_layer, counter, construction_thermal_resistance]
|
21
|
+
end
|
22
|
+
counter += 1
|
23
|
+
end
|
24
|
+
|
25
|
+
# test against minimum
|
26
|
+
if max_resistance > minThermalResistance
|
27
|
+
minTestPass = true
|
28
|
+
else
|
29
|
+
minTestPass = false
|
30
|
+
end
|
31
|
+
|
32
|
+
result = {
|
33
|
+
'construction' => construction,
|
34
|
+
'construction_layer' => thermal_resistance_array[0],
|
35
|
+
'layer_index' => thermal_resistance_array[1],
|
36
|
+
'construction_thermal_resistance' => thermal_resistance_array[2],
|
37
|
+
'insulationFound' => minTestPass
|
38
|
+
}
|
39
|
+
|
40
|
+
return result
|
41
|
+
end
|
42
|
+
|
43
|
+
# change thermal resistance of opaque materials
|
44
|
+
def self.setMaterialThermalResistance(material, thermalResistance, options = {})
|
45
|
+
# set defaults to use if user inputs not passed in
|
46
|
+
defaults = {
|
47
|
+
'cloneMaterial' => true, # in future give user option to clone or change live material
|
48
|
+
'name' => "#{material.name} - adj"
|
49
|
+
}
|
50
|
+
|
51
|
+
# merge user inputs with defaults
|
52
|
+
options = defaults.merge(options)
|
53
|
+
|
54
|
+
# clone input material
|
55
|
+
new_material = material.clone(material.model)
|
56
|
+
new_material = new_material.to_OpaqueMaterial.get
|
57
|
+
new_material.setName(options['name'])
|
58
|
+
|
59
|
+
# edit insulation material
|
60
|
+
new_material_matt = new_material.to_Material
|
61
|
+
if !new_material_matt.empty?
|
62
|
+
starting_thickness = new_material_matt.get.thickness
|
63
|
+
target_thickness = starting_thickness * thermalResistance / material.to_OpaqueMaterial.get.thermalResistance
|
64
|
+
final_thickness = new_material_matt.get.setThickness(target_thickness)
|
65
|
+
end
|
66
|
+
new_material_massless = new_material.to_MasslessOpaqueMaterial
|
67
|
+
if !new_material_massless.empty?
|
68
|
+
final_thermal_resistance = new_material_massless.get.setThermalResistance(thermalResistance)
|
69
|
+
end
|
70
|
+
new_material_airgap = new_material.to_AirGap
|
71
|
+
if !new_material_airgap.empty?
|
72
|
+
final_thermal_resistance = new_material_airgap.get.setThermalResistance(thermalResistance)
|
73
|
+
end
|
74
|
+
|
75
|
+
result = new_material
|
76
|
+
return result
|
77
|
+
end
|
78
|
+
|
79
|
+
# add new material to outside of a construction
|
80
|
+
def self.addNewLayerToConstruction(construction, options = {})
|
81
|
+
# set defaults to use if user inputs not passed in
|
82
|
+
defaults = {
|
83
|
+
'cloneConstruction' => false, # in future give user option to clone or change live construction
|
84
|
+
'layerIndex' => 0, # 0 will be outside. Measure writer should validate any non 0 layerIndex passed in.
|
85
|
+
'name' => "#{construction.name} - new material",
|
86
|
+
'roughness' => nil,
|
87
|
+
'thickness' => nil,
|
88
|
+
'conductivity' => nil,
|
89
|
+
'density' => nil,
|
90
|
+
'specificHeat' => nil,
|
91
|
+
'thermalAbsorptance' => nil,
|
92
|
+
'solarAbsorptance' => nil,
|
93
|
+
'visibleAbsorptance' => nil
|
94
|
+
}
|
95
|
+
|
96
|
+
# merge user inputs with defaults
|
97
|
+
options = defaults.merge(options)
|
98
|
+
|
99
|
+
# TODO: - would be nice to grab surface properties from previous exposed material
|
100
|
+
|
101
|
+
# make new material
|
102
|
+
exposedMaterialNew = OpenStudio::Model::StandardOpaqueMaterial.new(construction.model)
|
103
|
+
exposedMaterialNew.setName(options['name'])
|
104
|
+
|
105
|
+
# set requested material properties
|
106
|
+
if !options['roughness'].nil? then exposedMaterialNew.setRoughness(options['roughness']) end
|
107
|
+
if !options['thickness'].nil? then exposedMaterialNew.setThickness(options['thickness']) end
|
108
|
+
if !options['conductivity'].nil? then exposedMaterialNew.setConductivity(options['conductivity']) end
|
109
|
+
if !options['density'].nil? then exposedMaterialNew.setDensity(options['density']) end
|
110
|
+
if !options['specificHeat'].nil? then exposedMaterialNew.setSpecificHeat(options['specificHeat']) end
|
111
|
+
if !options['thermalAbsorptance'].nil? then exposedMaterialNew.setThermalAbsorptance(options['thermalAbsorptance']) end
|
112
|
+
if !options['solarAbsorptance'].nil? then exposedMaterialNew.setSolarAbsorptance(options['solarAbsorptance']) end
|
113
|
+
if !options['visibleAbsorptance'].nil? then exposedMaterialNew.setVisibleAbsorptance(options['visibleAbsorptance']) end
|
114
|
+
|
115
|
+
# add material to construction
|
116
|
+
construction.insertLayer(options['layerIndex'], exposedMaterialNew)
|
117
|
+
|
118
|
+
result = exposedMaterialNew
|
119
|
+
return result
|
120
|
+
end
|
121
|
+
|
122
|
+
# set material surface properties for specific layer in construction. this should work on OS:Material and OS:MasslessOpaqueMaterial
|
123
|
+
def self.setConstructionSurfaceProperties(construction, options = {})
|
124
|
+
# set defaults to use if user inputs not passed in
|
125
|
+
defaults = {
|
126
|
+
'cloneConstruction' => false, # in future give user option to clone or change live construction
|
127
|
+
'nameConstruction' => "#{construction.name} - adj", # not currently used
|
128
|
+
'cloneMaterial' => true,
|
129
|
+
'roughness' => nil,
|
130
|
+
'thermalAbsorptance' => nil,
|
131
|
+
'solarAbsorptance' => nil,
|
132
|
+
'visibleAbsorptance' => nil
|
133
|
+
}
|
134
|
+
|
135
|
+
# merge user inputs with defaults
|
136
|
+
options = defaults.merge(options)
|
137
|
+
|
138
|
+
exposedMaterial = construction.to_LayeredConstruction.get.getLayer(0)
|
139
|
+
if options['cloneMaterial']
|
140
|
+
|
141
|
+
# clone material
|
142
|
+
exposedMaterialNew = exposedMaterial.clone(construction.model).to_StandardOpaqueMaterial.get # to_StandardOpaqueMaterial is needed to access roughness, otherwise to_OpaqueMaterial would have been fine.
|
143
|
+
exposedMaterialNew.setName("#{exposedMaterial.name} - adj")
|
144
|
+
|
145
|
+
# connect new material to original construction
|
146
|
+
construction.eraseLayer(0)
|
147
|
+
construction.insertLayer(0, exposedMaterialNew)
|
148
|
+
|
149
|
+
else
|
150
|
+
exposedMaterialNew = exposedMaterial.to_StandardOpaqueMaterial.get # not being cloned but still want to rename
|
151
|
+
exposedMaterialNew.setName("#{exposedMaterial.name} - adj")
|
152
|
+
end
|
153
|
+
|
154
|
+
# TODO: - need to test with MasslessOpaqueMaterial. Add test if doesn't return anything when use to_StandardOpaqueMaterial.get
|
155
|
+
|
156
|
+
# set requested material properties
|
157
|
+
if !options['roughness'].nil? then exposedMaterialNew.setRoughness(options['roughness']) end
|
158
|
+
if !options['thermalAbsorptance'].nil? then exposedMaterialNew.setThermalAbsorptance(options['thermalAbsorptance']) end
|
159
|
+
if !options['solarAbsorptance'].nil? then exposedMaterialNew.setSolarAbsorptance(options['solarAbsorptance']) end
|
160
|
+
if !options['visibleAbsorptance'].nil? then exposedMaterialNew.setVisibleAbsorptance(options['visibleAbsorptance']) end
|
161
|
+
|
162
|
+
result = { 'exposedMaterial' => exposedMaterial, 'exposedMaterialNew' => exposedMaterialNew }
|
163
|
+
return result
|
164
|
+
end # end of OsLib_Constructions.setMaterialSurfaceProperties
|
165
|
+
|
166
|
+
# similar to setMaterialSurfaceProperties but I just pass a material in. Needed this to set material properties for interior walls and both sides of interior partitions.
|
167
|
+
def self.setMaterialSurfaceProperties(material, options = {})
|
168
|
+
# set defaults to use if user inputs not passed in
|
169
|
+
defaults = {
|
170
|
+
'cloneMaterial' => true,
|
171
|
+
'roughness' => nil,
|
172
|
+
'thermalAbsorptance' => nil,
|
173
|
+
'solarAbsorptance' => nil,
|
174
|
+
'visibleAbsorptance' => nil
|
175
|
+
}
|
176
|
+
|
177
|
+
# merge user inputs with defaults
|
178
|
+
options = defaults.merge(options)
|
179
|
+
|
180
|
+
if options['cloneMaterial']
|
181
|
+
# clone material
|
182
|
+
materialNew = exposedMaterial.clone(construction.model).get
|
183
|
+
materialNew.setName("#{materialNew.name} - adj")
|
184
|
+
else
|
185
|
+
materialNew = material # not being cloned
|
186
|
+
materialNew.setName("#{materialNew.name} - adj")
|
187
|
+
end
|
188
|
+
|
189
|
+
# to_StandardOpaqueMaterial is needed to access roughness, otherwise to_OpaqueMaterial would have been fine.
|
190
|
+
if !materialNew.to_StandardOpaqueMaterial.empty?
|
191
|
+
materialNew = materialNew.to_StandardOpaqueMaterial.get
|
192
|
+
elsif !materialNew.to_MasslessOpaqueMaterial.empty?
|
193
|
+
materialNew = materialNew.to_MasslessOpaqueMaterial.get
|
194
|
+
end
|
195
|
+
|
196
|
+
# set requested material properties
|
197
|
+
if !options['roughness'].nil? then materialNew.setRoughness(options['roughness']) end
|
198
|
+
if !options['thermalAbsorptance'].nil? then materialNew.setThermalAbsorptance(options['thermalAbsorptance']) end
|
199
|
+
if !options['solarAbsorptance'].nil? then materialNew.setSolarAbsorptance(options['solarAbsorptance']) end
|
200
|
+
if !options['visibleAbsorptance'].nil? then materialNew.setVisibleAbsorptance(options['visibleAbsorptance']) end
|
201
|
+
|
202
|
+
result = { 'material' => material, 'materialNew' => materialNew }
|
203
|
+
return result
|
204
|
+
end # end of OsLib_Constructions.setMaterialSurfaceProperties
|
205
|
+
|
206
|
+
# sri of exposed surface of a construction (calculation from K-12 AEDG, derived from ASTM E1980 assuming medium wind speed)
|
207
|
+
def self.getConstructionSRI(construction)
|
208
|
+
exposedMaterial = construction.to_LayeredConstruction.get.getLayer(0)
|
209
|
+
solarAbsorptance = exposedMaterial.to_OpaqueMaterial.get.solarAbsorptance
|
210
|
+
thermalEmissivity = exposedMaterial.to_OpaqueMaterial.get.thermalAbsorptance
|
211
|
+
# lines below just for testing
|
212
|
+
# solarAbsorptance = 1 - 0.65
|
213
|
+
# thermalEmissivity = 0.86
|
214
|
+
|
215
|
+
x = (20.797 * solarAbsorptance - 0.603 * thermalEmissivity) / (9.5205 * thermalEmissivity + 12.0)
|
216
|
+
sri = 123.97 - 141.35 * x + 9.6555 * x * x
|
217
|
+
|
218
|
+
result = sri
|
219
|
+
return result
|
220
|
+
end # end of OsLib_Constructions.getConstructionSRI
|
221
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OsLib_Geometry
|
4
|
+
# lower z value of vertices with starting value above x to new value of y
|
5
|
+
def self.lowerSurfaceZvalue(surfaceArray, zValueTarget)
|
6
|
+
counter = 0
|
7
|
+
|
8
|
+
# loop over all surfaces
|
9
|
+
surfaceArray.each do |surface|
|
10
|
+
# create a new set of vertices
|
11
|
+
newVertices = OpenStudio::Point3dVector.new
|
12
|
+
|
13
|
+
# get the existing vertices for this interior partition
|
14
|
+
vertices = surface.vertices
|
15
|
+
flag = false
|
16
|
+
vertices.each do |vertex|
|
17
|
+
# initialize new vertex to old vertex
|
18
|
+
x = vertex.x
|
19
|
+
y = vertex.y
|
20
|
+
z = vertex.z
|
21
|
+
|
22
|
+
# if this z vertex is not on the z = 0 plane
|
23
|
+
if z > zValueTarget
|
24
|
+
z = zValueTarget
|
25
|
+
flag = true
|
26
|
+
end
|
27
|
+
|
28
|
+
# add point to new vertices
|
29
|
+
newVertices << OpenStudio::Point3d.new(x, y, z)
|
30
|
+
end
|
31
|
+
|
32
|
+
# set vertices to new vertices
|
33
|
+
surface.setVertices(newVertices) # todo check if this was made, and issue warning if it was not. Could happen if resulting surface not planer.
|
34
|
+
|
35
|
+
if flag then counter += 1 end
|
36
|
+
end # end of surfaceArray.each do
|
37
|
+
|
38
|
+
result = counter
|
39
|
+
return result
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,1682 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OsLib_HVAC
|
4
|
+
# do something
|
5
|
+
def self.doSomething(input)
|
6
|
+
# do something
|
7
|
+
output = input
|
8
|
+
|
9
|
+
result = output
|
10
|
+
return result
|
11
|
+
end # end of def
|
12
|
+
|
13
|
+
# validate and make plenum zones
|
14
|
+
def self.validateAndAddPlenumZonesToSystem(model, runner, options = {})
|
15
|
+
# set defaults to use if user inputs not passed in
|
16
|
+
defaults = {
|
17
|
+
'zonesPlenum' => nil,
|
18
|
+
'zonesPrimary' => nil,
|
19
|
+
'type' => 'ceilingReturn'
|
20
|
+
}
|
21
|
+
|
22
|
+
# merge user inputs with defaults
|
23
|
+
options = defaults.merge(options)
|
24
|
+
|
25
|
+
# array of valid ceiling plenums
|
26
|
+
zoneSurfaceHash = {}
|
27
|
+
zonePlenumHash = {}
|
28
|
+
|
29
|
+
if options['zonesPlenum'].nil?
|
30
|
+
runner.registerWarning('No plenum zones were passed in, validateAndAddPlenumZonesToSystem will not alter the model.')
|
31
|
+
else
|
32
|
+
options['zonesPlenum'].each do |zone|
|
33
|
+
# get spaces in zone
|
34
|
+
spaces = zone.spaces
|
35
|
+
# get adjacent spaces
|
36
|
+
spaces.each do |space|
|
37
|
+
# get surfaces
|
38
|
+
surfaces = space.surfaces
|
39
|
+
# loop through surfaces looking for floors with surface boundary condition, grab zone that surface's parent space is in.
|
40
|
+
surfaces.each do |surface|
|
41
|
+
if (surface.outsideBoundaryCondition == 'Surface') && (surface.surfaceType == 'Floor')
|
42
|
+
next unless surface.adjacentSurface.is_initialized
|
43
|
+
adjacentSurface = surface.adjacentSurface.get
|
44
|
+
next unless adjacentSurface.space.is_initialized
|
45
|
+
adjacentSurfaceSpace = adjacentSurface.space.get
|
46
|
+
next unless adjacentSurfaceSpace.thermalZone.is_initialized
|
47
|
+
adjacentSurfaceSpaceZone = adjacentSurfaceSpace.thermalZone.get
|
48
|
+
if options['zonesPrimary'].include? adjacentSurfaceSpaceZone
|
49
|
+
if zoneSurfaceHash[adjacentSurfaceSpaceZone].nil? || (surface.grossArea > zoneSurfaceHash[adjacentSurfaceSpaceZone])
|
50
|
+
adjacentSurfaceSpaceZone.setReturnPlenum(zone)
|
51
|
+
zoneSurfaceHash[adjacentSurfaceSpaceZone] = surface.grossArea
|
52
|
+
zonePlenumHash[adjacentSurfaceSpaceZone] = zone
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end # end of surfaces.each do
|
57
|
+
end # end of spaces.each do
|
58
|
+
end # end of zonesPlenum.each do
|
59
|
+
end # end of zonesPlenum == nil
|
60
|
+
|
61
|
+
# report out results of zone-plenum hash
|
62
|
+
zonePlenumHash.each do |zone, plenum|
|
63
|
+
runner.registerInfo("#{plenum.name} has been set as a return air plenum for #{zone.name}.")
|
64
|
+
end
|
65
|
+
|
66
|
+
# pass back zone-plenum hash
|
67
|
+
result = zonePlenumHash
|
68
|
+
return result
|
69
|
+
end # end of def
|
70
|
+
|
71
|
+
def self.sortZones(model, runner, options = {}, space_type_to_edits_hash = {})
|
72
|
+
# set defaults to use if user inputs not passed in
|
73
|
+
defaults = { 'standardBuildingTypeTest' => nil, # not used for now
|
74
|
+
'secondarySpaceTypeTest' => nil,
|
75
|
+
'ceilingReturnPlenumSpaceType' => nil }
|
76
|
+
|
77
|
+
# merge user inputs with defaults
|
78
|
+
options = defaults.merge(options)
|
79
|
+
|
80
|
+
# set up zone type arrays
|
81
|
+
zonesPrimary = []
|
82
|
+
zonesSecondary = []
|
83
|
+
zonesPlenum = []
|
84
|
+
zonesUnconditioned = []
|
85
|
+
|
86
|
+
# get thermal zones
|
87
|
+
zones = model.getThermalZones
|
88
|
+
zones.each do |zone|
|
89
|
+
# assign appropriate zones to zonesPlenum or zonesUnconditioned (those that don't have thermostats or zone HVAC equipment)
|
90
|
+
# if not conditioned then add to zonesPlenum or zonesUnconditioned
|
91
|
+
unless zone.thermostatSetpointDualSetpoint.is_initialized || !zone.equipment.empty?
|
92
|
+
# determine if zone is a plenum zone or general unconditioned zone
|
93
|
+
# assume it is a plenum if it has at least one plenum space
|
94
|
+
zone.spaces.each do |space|
|
95
|
+
# if a zone has already been assigned as a plenum, skip
|
96
|
+
next if zonesPlenum.include? zone
|
97
|
+
# if zone not assigned as a plenum, get space type if it exists
|
98
|
+
# compare to plenum space type if it has been assigned
|
99
|
+
if space.spaceType.is_initialized && (options['ceilingReturnPlenumSpaceType'].nil? == false)
|
100
|
+
spaceType = space.spaceType.get
|
101
|
+
if spaceType == options['ceilingReturnPlenumSpaceType']
|
102
|
+
zonesPlenum << zone # zone has a plenum space; assign it as a plenum
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
# if zone not assigned as a plenum, assign it as unconditioned
|
107
|
+
unless zonesPlenum.include? zone
|
108
|
+
zonesUnconditioned << zone
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
# zone is conditioned. check if its space type is secondary or primary
|
113
|
+
spaces = model.getSpaces
|
114
|
+
spaces.each do |space|
|
115
|
+
spaceType = space.spaceType.get
|
116
|
+
if space_type_to_edits_hash[spaceType] == true
|
117
|
+
zonesPrimary << space.thermalZone.get
|
118
|
+
end
|
119
|
+
end # end of spaces each do
|
120
|
+
# if zone is conditioned and is of space type true, assign it to primary zones
|
121
|
+
|
122
|
+
zonesSorted = { 'zonesPrimary' => zonesPrimary,
|
123
|
+
'zonesSecondary' => zonesSecondary,
|
124
|
+
'zonesPlenum' => zonesPlenum,
|
125
|
+
'zonesUnconditioned' => zonesUnconditioned }
|
126
|
+
# pass back zonesSorted hash
|
127
|
+
result = zonesSorted
|
128
|
+
return result
|
129
|
+
end # end of def
|
130
|
+
|
131
|
+
def self.reportConditions(model, runner, condition)
|
132
|
+
airloops = model.getAirLoopHVACs
|
133
|
+
plantLoops = model.getPlantLoops
|
134
|
+
zones = model.getThermalZones
|
135
|
+
|
136
|
+
# count up zone equipment (not counting zone exhaust fans)
|
137
|
+
zoneHasEquip = false
|
138
|
+
zonesWithEquipCounter = 0
|
139
|
+
|
140
|
+
zones.each do |zone|
|
141
|
+
if !zone.equipment.empty?
|
142
|
+
zone.equipment.each do |equip|
|
143
|
+
unless equip.to_FanZoneExhaust.is_initialized
|
144
|
+
zonesWithEquipCounter += 1
|
145
|
+
break
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
if condition == 'initial'
|
152
|
+
runner.registerInitialCondition("The building started with #{airloops.size} air loops and #{plantLoops.size} plant loops. #{zonesWithEquipCounter} zones were conditioned with zone equipment.")
|
153
|
+
elsif condition == 'final'
|
154
|
+
runner.registerFinalCondition("The building finished with #{airloops.size} air loops and #{plantLoops.size} plant loops. #{zonesWithEquipCounter} zones are conditioned with zone equipment.")
|
155
|
+
end
|
156
|
+
end # end of def
|
157
|
+
|
158
|
+
def self.removeEquipment(model, runner, options)
|
159
|
+
airloops = model.getAirLoopHVACs
|
160
|
+
plantLoops = model.getPlantLoops
|
161
|
+
zones = model.getThermalZones
|
162
|
+
|
163
|
+
# remove all zone equipment except zone exhaust fans
|
164
|
+
zones.each do |zone|
|
165
|
+
# runner.registerInfo("primary zones values are #{value.name}")
|
166
|
+
if options['zonesPrimary'].include? zone
|
167
|
+
zone.equipment.each do |equip|
|
168
|
+
if equip.to_FanZoneExhaust.is_initialized # or (equip.to_ZoneHVACUnitHeater.is_initialized and zone.get.equipment.size == 1)
|
169
|
+
else
|
170
|
+
equip.remove
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# remove an air loop if it's empty
|
177
|
+
airloops.each do |air_loop|
|
178
|
+
air_loop.thermalZones.each do |airZone|
|
179
|
+
if options['zonesPrimary'].include? airZone
|
180
|
+
air_loop.removeBranchForZone(airZone)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
if air_loop.thermalZones.empty?
|
184
|
+
air_loop.remove
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# remove plant loops
|
189
|
+
plantLoops.each do |plantLoop|
|
190
|
+
# get the demand components and see if water use connection, then save it
|
191
|
+
# notify user with info statement if supply side of plant loop had heat exchanger for refrigeration
|
192
|
+
usedForSWHOrRefrigeration = false
|
193
|
+
usedForZoneHCCoils = false
|
194
|
+
plantLoop.demandComponents.each do |comp| # AP code to check your comments above
|
195
|
+
runner.registerInfo("plant loops component is #{comp.name}")
|
196
|
+
if comp.to_WaterUseConnections.is_initialized || comp.to_CoilWaterHeatingDesuperheater.is_initialized
|
197
|
+
usedForSWHOrRefrigeration = true
|
198
|
+
runner.registerWarning("#{plantLoop.name} is used for SWH or refrigeration. Loop will not be deleted.")
|
199
|
+
elsif comp.name.to_s.include?('Coil') && (comp.name.to_s != 'Coil Heating Water 1') && (comp.name.to_s != 'Coil Cooling Water 1') # to_CoilWaterHeatingDesuperheater.is_initialized or comp.name.to_s.include? "coil"
|
200
|
+
runner.registerWarning("#{plantLoop.name} has coils used by Zone HVAC components. Loop will not be deleted.")
|
201
|
+
usedForZoneHCCoils = true
|
202
|
+
end
|
203
|
+
end
|
204
|
+
# runner.registerInfo("Used for ZoneHCCoils Value is #{usedForZoneHCCoils}")
|
205
|
+
# runner.registerInfo("Used for SWH or refrigeration is #{usedForSWHOrRefrigeration}")
|
206
|
+
if usedForSWHOrRefrigeration == false # and usedForZoneHCCoils == false # <-- Sang Hoon Lee: "and usedForZoneHCCoils == false" Treated as comment for BRICR Medium office to remove Coil:Heating:Water
|
207
|
+
plantLoop.remove
|
208
|
+
runner.registerInfo("Plant Loop #{plantLoop.name} is removed")
|
209
|
+
end
|
210
|
+
end # end of plantloop components
|
211
|
+
end # end of def
|
212
|
+
|
213
|
+
def self.assignHVACSchedules(model, runner, options = {})
|
214
|
+
require "#{File.dirname(__FILE__)}/OsLib_Schedules"
|
215
|
+
|
216
|
+
schedulesHVAC = {}
|
217
|
+
airloops = model.getAirLoopHVACs
|
218
|
+
|
219
|
+
# find airloop with most primary spaces
|
220
|
+
max_primary_spaces = 0
|
221
|
+
representative_airloop = false
|
222
|
+
building_HVAC_schedule = false
|
223
|
+
building_ventilation_schedule = false
|
224
|
+
unless options['remake_schedules']
|
225
|
+
# if remake schedules not selected, get relevant schedules from model if they exist
|
226
|
+
airloops.each do |air_loop|
|
227
|
+
primary_spaces = 0
|
228
|
+
air_loop.thermalZones.each do |thermal_zone|
|
229
|
+
thermal_zone.spaces.each do |space|
|
230
|
+
if space.spaceType.is_initialized
|
231
|
+
if space.spaceType.get.name.is_initialized
|
232
|
+
if space.spaceType.get.name.get.include? options['primarySpaceType']
|
233
|
+
primary_spaces += 1
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
if primary_spaces > max_primary_spaces
|
240
|
+
max_primary_spaces = primary_spaces
|
241
|
+
representative_airloop = air_loop
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
if representative_airloop
|
246
|
+
building_HVAC_schedule = representative_airloop.availabilitySchedule
|
247
|
+
if representative_airloop.airLoopHVACOutdoorAirSystem.is_initialized
|
248
|
+
building_ventilation_schedule_optional = representative_airloop.airLoopHVACOutdoorAirSystem.get.getControllerOutdoorAir.maximumFractionofOutdoorAirSchedule
|
249
|
+
if building_ventilation_schedule_optional.is_initialized
|
250
|
+
building_ventilation_schedule = building_ventilation_schedule.get
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
# build new airloop schedules if existing model doesn't have them
|
255
|
+
if options['primarySpaceType'] == 'Classroom'
|
256
|
+
# ventilation schedule
|
257
|
+
unless building_ventilation_schedule
|
258
|
+
runner.registerInfo('Baseline does not have minimum OA ventilation schedule. A new K-12 Ventilation schedule is created')
|
259
|
+
ruleset_name = 'New K-12 Ventilation Schedule'
|
260
|
+
winter_design_day = [[24, 1]]
|
261
|
+
summer_design_day = [[24, 1]]
|
262
|
+
default_day = ['Weekday', [6, 0], [18, 1], [24, 0]]
|
263
|
+
rules = []
|
264
|
+
rules << ['Weekend', '1/1-12/31', 'Sat/Sun', [24, 0]]
|
265
|
+
rules << ['Summer Weekday', '7/1-8/31', 'Mon/Tue/Wed/Thu/Fri', [8, 0], [13, 1], [24, 0]]
|
266
|
+
options_ventilation = { 'name' => ruleset_name,
|
267
|
+
'winter_design_day' => winter_design_day,
|
268
|
+
'summer_design_day' => summer_design_day,
|
269
|
+
'default_day' => default_day,
|
270
|
+
'rules' => rules }
|
271
|
+
building_ventilation_schedule = OsLib_Schedules.createComplexSchedule(model, options_ventilation)
|
272
|
+
end
|
273
|
+
# HVAC availability schedule
|
274
|
+
unless building_HVAC_schedule
|
275
|
+
runner.registerInfo('Baseline does not have HVAC availability schedule. A new K-12 HVAC availability schedule is created')
|
276
|
+
ruleset_name = 'New K-12 HVAC Availability Schedule'
|
277
|
+
winter_design_day = [[24, 1]]
|
278
|
+
summer_design_day = [[24, 1]]
|
279
|
+
default_day = ['Weekday', [6, 0], [18, 1], [24, 0]]
|
280
|
+
rules = []
|
281
|
+
rules << ['Weekend', '1/1-12/31', 'Sat/Sun', [24, 0]]
|
282
|
+
rules << ['Summer Weekday', '7/1-8/31', 'Mon/Tue/Wed/Thu/Fri', [8, 0], [13, 1], [24, 0]]
|
283
|
+
options_hvac = { 'name' => ruleset_name,
|
284
|
+
'winter_design_day' => winter_design_day,
|
285
|
+
'summer_design_day' => summer_design_day,
|
286
|
+
'default_day' => default_day,
|
287
|
+
'rules' => rules }
|
288
|
+
building_HVAC_schedule = OsLib_Schedules.createComplexSchedule(model, options_hvac)
|
289
|
+
end
|
290
|
+
elsif options['primarySpaceType'] == 'Office' # xf - leave as is
|
291
|
+
# ventilation schedule
|
292
|
+
unless building_ventilation_schedule
|
293
|
+
runner.registerInfo('Baseline does not have minimum OA ventilation schedule. A new Office Ventilation schedule is created.')
|
294
|
+
ruleset_name = 'New Office Ventilation Schedule'
|
295
|
+
winter_design_day = [[24, 1]] # ML These are not always on in PNNL model
|
296
|
+
summer_design_day = [[24, 1]] # ML These are not always on in PNNL model
|
297
|
+
default_day = ['Weekday', [7, 0], [22, 1], [24, 0]] # ML PNNL has a one hour ventilation offset
|
298
|
+
rules = []
|
299
|
+
rules << ['Saturday', '1/1-12/31', 'Sat', [7, 0], [18, 1], [24, 0]] # ML PNNL has a one hour ventilation offset
|
300
|
+
rules << ['Sunday', '1/1-12/31', 'Sun', [24, 0]]
|
301
|
+
options_ventilation = { 'name' => ruleset_name,
|
302
|
+
'winter_design_day' => winter_design_day,
|
303
|
+
'summer_design_day' => summer_design_day,
|
304
|
+
'default_day' => default_day,
|
305
|
+
'rules' => rules }
|
306
|
+
building_ventilation_schedule = OsLib_Schedules.createComplexSchedule(model, options_ventilation)
|
307
|
+
end
|
308
|
+
# HVAC availability schedule
|
309
|
+
unless building_HVAC_schedule
|
310
|
+
runner.registerInfo('Baseline does not have HVAC availability schedule. A new office HVAC availability schedule is created')
|
311
|
+
ruleset_name = 'New Office HVAC Availability Schedule'
|
312
|
+
winter_design_day = [[24, 1]] # ML These are not always on in PNNL model
|
313
|
+
summer_design_day = [[24, 1]] # ML These are not always on in PNNL model
|
314
|
+
default_day = ['Weekday', [6, 0], [22, 1], [24, 0]] # ML PNNL has a one hour ventilation offset
|
315
|
+
rules = []
|
316
|
+
rules << ['Saturday', '1/1-12/31', 'Sat', [6, 0], [18, 1], [24, 0]] # ML PNNL has a one hour ventilation offset
|
317
|
+
rules << ['Sunday', '1/1-12/31', 'Sun', [24, 0]]
|
318
|
+
options_hvac = { 'name' => ruleset_name,
|
319
|
+
'winter_design_day' => winter_design_day,
|
320
|
+
'summer_design_day' => summer_design_day,
|
321
|
+
'default_day' => default_day,
|
322
|
+
'rules' => rules }
|
323
|
+
building_HVAC_schedule = OsLib_Schedules.createComplexSchedule(model, options_hvac)
|
324
|
+
end
|
325
|
+
# special loops for radiant system (different temperature setpoints)
|
326
|
+
if options['allHVAC']['zone'] == 'Radiant'
|
327
|
+
# create hot water schedule for radiant heating loop
|
328
|
+
schedulesHVAC['radiant_hot_water'] = OsLib_Schedules.createComplexSchedule(model, 'name' => 'New HW-Radiant-Loop-Temp-Schedule',
|
329
|
+
'default_day' => ['All Days', [24, 45.0]])
|
330
|
+
# create hot water schedule for radiant cooling loop
|
331
|
+
schedulesHVAC['radiant_chilled_water'] = OsLib_Schedules.createComplexSchedule(model, 'name' => 'New CW-Radiant-Loop-Temp-Schedule',
|
332
|
+
'default_day' => ['All Days', [24, 15.0]])
|
333
|
+
# create mean radiant heating and cooling setpoint schedules
|
334
|
+
# ML ideally, should grab schedules tied to zone thermostat and make modified versions that follow the setback pattern
|
335
|
+
# for now, create new ones that match the recommended HVAC schedule
|
336
|
+
# mean radiant heating setpoint schedule (PNNL values)
|
337
|
+
ruleset_name = 'New Office Mean Radiant Heating Setpoint Schedule'
|
338
|
+
winter_design_day = [[24, 18.8]]
|
339
|
+
summer_design_day = [[6, 18.3], [22, 18.8], [24, 18.3]]
|
340
|
+
default_day = ['Weekday', [6, 18.3], [22, 18.8], [24, 18.3]]
|
341
|
+
rules = []
|
342
|
+
rules << ['Saturday', '1/1-12/31', 'Sat', [6, 18.3], [18, 18.8], [24, 18.3]]
|
343
|
+
rules << ['Sunday', '1/1-12/31', 'Sun', [24, 18.3]]
|
344
|
+
options_radiant_heating = { 'name' => ruleset_name,
|
345
|
+
'winter_design_day' => winter_design_day,
|
346
|
+
'summer_design_day' => summer_design_day,
|
347
|
+
'default_day' => default_day,
|
348
|
+
'rules' => rules }
|
349
|
+
mean_radiant_heating_schedule = OsLib_Schedules.createComplexSchedule(model, options_radiant_heating)
|
350
|
+
schedulesHVAC['mean_radiant_heating'] = mean_radiant_heating_schedule
|
351
|
+
# mean radiant cooling setpoint schedule (PNNL values)
|
352
|
+
ruleset_name = 'New Office Mean Radiant Cooling Setpoint Schedule'
|
353
|
+
winter_design_day = [[6, 26.7], [22, 24.0], [24, 26.7]]
|
354
|
+
summer_design_day = [[24, 24.0]]
|
355
|
+
default_day = ['Weekday', [6, 26.7], [22, 24.0], [24, 26.7]]
|
356
|
+
rules = []
|
357
|
+
rules << ['Saturday', '1/1-12/31', 'Sat', [6, 26.7], [18, 24.0], [24, 26.7]]
|
358
|
+
rules << ['Sunday', '1/1-12/31', 'Sun', [24, 26.7]]
|
359
|
+
options_radiant_cooling = { 'name' => ruleset_name,
|
360
|
+
'winter_design_day' => winter_design_day,
|
361
|
+
'summer_design_day' => summer_design_day,
|
362
|
+
'default_day' => default_day,
|
363
|
+
'rules' => rules }
|
364
|
+
mean_radiant_cooling_schedule = OsLib_Schedules.createComplexSchedule(model, options_radiant_cooling)
|
365
|
+
schedulesHVAC['mean_radiant_cooling'] = mean_radiant_cooling_schedule
|
366
|
+
end
|
367
|
+
end
|
368
|
+
# SAT schedule
|
369
|
+
if options['allHVAC']['primary']['doas']
|
370
|
+
# primary airloop is DOAS
|
371
|
+
schedulesHVAC['primary_sat'] = sch_ruleset_DOAS_setpoint = OsLib_Schedules.createComplexSchedule(model, 'name' => 'DOAS Temperature Setpoint Schedule',
|
372
|
+
'default_day' => ['All Days', [24, 21.111]])
|
373
|
+
else
|
374
|
+
# primary airloop is multizone VAV that cools
|
375
|
+
schedulesHVAC['primary_sat'] = sch_ruleset_DOAS_setpoint = OsLib_Schedules.createComplexSchedule(model, 'name' => 'Cold Deck Temperature Setpoint Schedule',
|
376
|
+
'default_day' => ['All Days', [24, 12.8]])
|
377
|
+
end
|
378
|
+
schedulesHVAC['ventilation'] = building_ventilation_schedule
|
379
|
+
schedulesHVAC['hvac'] = building_HVAC_schedule
|
380
|
+
# build new plant schedules as needed
|
381
|
+
zoneHVACHotWaterPlant = ['FanCoil', 'DualDuct', 'Baseboard'] # dual duct has fan coil and baseboard
|
382
|
+
zoneHVACChilledWaterPlant = ['FanCoil', 'DualDuct'] # dual duct has fan coil
|
383
|
+
# hot water
|
384
|
+
if (options['allHVAC']['primary']['heat'] == 'Water') || (options['allHVAC']['secondary']['heat'] == 'Water') || zoneHVACHotWaterPlant.include?(options['allHVAC']['zone'])
|
385
|
+
schedulesHVAC['hot_water'] = OsLib_Schedules.createComplexSchedule(model, 'name' => 'HW-Loop-Temp-Schedule',
|
386
|
+
'default_day' => ['All Days', [24, 67.0]])
|
387
|
+
end
|
388
|
+
# chilled water
|
389
|
+
if (options['allHVAC']['primary']['cool'] == 'Water') || (options['allHVAC']['secondary']['cool'] == 'Water') || zoneHVACChilledWaterPlant.include?(options['allHVAC']['zone'])
|
390
|
+
schedulesHVAC['chilled_water'] = OsLib_Schedules.createComplexSchedule(model, 'name' => 'CW-Loop-Temp-Schedule',
|
391
|
+
'default_day' => ['All Days', [24, 6.7]])
|
392
|
+
end
|
393
|
+
# heat pump condenser loop schedules
|
394
|
+
if options['allHVAC']['zone'] == 'GSHP'
|
395
|
+
# there will be a heat pump condenser loop
|
396
|
+
# loop setpoint schedule
|
397
|
+
schedulesHVAC['hp_loop'] = OsLib_Schedules.createComplexSchedule(model, 'name' => 'New HP-Loop-Temp-Schedule',
|
398
|
+
'default_day' => ['All Days', [24, 21]])
|
399
|
+
# cooling component schedule (#ML won't need this if a ground loop is actually modeled)
|
400
|
+
schedulesHVAC['hp_loop_cooling'] = OsLib_Schedules.createComplexSchedule(model, 'name' => 'New HP-Loop-Clg-Temp-Schedule',
|
401
|
+
'default_day' => ['All Days', [24, 21]])
|
402
|
+
# heating component schedule
|
403
|
+
schedulesHVAC['hp_loop_heating'] = OsLib_Schedules.createComplexSchedule(model, 'name' => 'New HP-Loop-Htg-Temp-Schedule',
|
404
|
+
'default_day' => ['All Days', [24, 5]])
|
405
|
+
end
|
406
|
+
if options['allHVAC']['zone'] == 'WSHP'
|
407
|
+
# there will be a heat pump condenser loop
|
408
|
+
# loop setpoint schedule
|
409
|
+
schedulesHVAC['hp_loop'] = OsLib_Schedules.createComplexSchedule(model, 'name' => 'New HP-Loop-Temp-Schedule',
|
410
|
+
'default_day' => ['All Days', [24, 30]]) # PNNL
|
411
|
+
# cooling component schedule (#ML won't need this if a ground loop is actually modeled)
|
412
|
+
schedulesHVAC['hp_loop_cooling'] = OsLib_Schedules.createComplexSchedule(model, 'name' => 'New HP-Loop-Clg-Temp-Schedule',
|
413
|
+
'default_day' => ['All Days', [24, 30]]) # PNNL
|
414
|
+
# heating component schedule
|
415
|
+
schedulesHVAC['hp_loop_heating'] = OsLib_Schedules.createComplexSchedule(model, 'name' => 'New HP-Loop-Htg-Temp-Schedule',
|
416
|
+
'default_day' => ['All Days', [24, 20]]) # PNNL
|
417
|
+
end
|
418
|
+
|
419
|
+
# pass back schedulesHVAC hash
|
420
|
+
result = schedulesHVAC
|
421
|
+
return result
|
422
|
+
end # end of def
|
423
|
+
|
424
|
+
def self.createHotWaterPlant(model, runner, hot_water_setpoint_schedule, loop_type, parameters)
|
425
|
+
hot_water_plant = OpenStudio::Model::PlantLoop.new(model)
|
426
|
+
hot_water_plant.setName("New #{loop_type} Loop")
|
427
|
+
hot_water_plant.setMaximumLoopTemperature(100)
|
428
|
+
hot_water_plant.setMinimumLoopTemperature(10)
|
429
|
+
loop_sizing = hot_water_plant.sizingPlant
|
430
|
+
loop_sizing.setLoopType('Heating')
|
431
|
+
if loop_type == 'Hot Water'
|
432
|
+
loop_sizing.setDesignLoopExitTemperature(82)
|
433
|
+
elsif loop_type == 'Radiant Hot Water'
|
434
|
+
loop_sizing.setDesignLoopExitTemperature(60) # ML follows convention of sizing temp being larger than supplu temp
|
435
|
+
end
|
436
|
+
loop_sizing.setLoopDesignTemperatureDifference(11)
|
437
|
+
# create a pump
|
438
|
+
pump = OpenStudio::Model::PumpVariableSpeed.new(model)
|
439
|
+
pump.setRatedPumpHead(119563) # Pa
|
440
|
+
pump.setMotorEfficiency(0.9)
|
441
|
+
pump.setCoefficient1ofthePartLoadPerformanceCurve(0)
|
442
|
+
pump.setCoefficient2ofthePartLoadPerformanceCurve(0.0216)
|
443
|
+
pump.setCoefficient3ofthePartLoadPerformanceCurve(-0.0325)
|
444
|
+
pump.setCoefficient4ofthePartLoadPerformanceCurve(1.0095)
|
445
|
+
# create a boiler
|
446
|
+
boiler = OpenStudio::Model::BoilerHotWater.new(model)
|
447
|
+
boiler.setNominalThermalEfficiency(0.9)
|
448
|
+
# create a scheduled setpoint manager
|
449
|
+
setpoint_manager_scheduled = OpenStudio::Model::SetpointManagerScheduled.new(model, hot_water_setpoint_schedule)
|
450
|
+
# create a supply bypass pipe
|
451
|
+
pipe_supply_bypass = OpenStudio::Model::PipeAdiabatic.new(model)
|
452
|
+
# create a supply outlet pipe
|
453
|
+
pipe_supply_outlet = OpenStudio::Model::PipeAdiabatic.new(model)
|
454
|
+
# create a demand bypass pipe
|
455
|
+
pipe_demand_bypass = OpenStudio::Model::PipeAdiabatic.new(model)
|
456
|
+
# create a demand inlet pipe
|
457
|
+
pipe_demand_inlet = OpenStudio::Model::PipeAdiabatic.new(model)
|
458
|
+
# create a demand outlet pipe
|
459
|
+
pipe_demand_outlet = OpenStudio::Model::PipeAdiabatic.new(model)
|
460
|
+
# connect components to plant loop
|
461
|
+
# supply side components
|
462
|
+
hot_water_plant.addSupplyBranchForComponent(boiler)
|
463
|
+
hot_water_plant.addSupplyBranchForComponent(pipe_supply_bypass)
|
464
|
+
pump.addToNode(hot_water_plant.supplyInletNode)
|
465
|
+
pipe_supply_outlet.addToNode(hot_water_plant.supplyOutletNode)
|
466
|
+
setpoint_manager_scheduled.addToNode(hot_water_plant.supplyOutletNode)
|
467
|
+
# demand side components (water coils are added as they are added to airloops and zoneHVAC)
|
468
|
+
hot_water_plant.addDemandBranchForComponent(pipe_demand_bypass)
|
469
|
+
pipe_demand_inlet.addToNode(hot_water_plant.demandInletNode)
|
470
|
+
pipe_demand_outlet.addToNode(hot_water_plant.demandOutletNode)
|
471
|
+
|
472
|
+
# pass back hot water plant
|
473
|
+
result = hot_water_plant
|
474
|
+
return result
|
475
|
+
end # end of def
|
476
|
+
|
477
|
+
def self.createChilledWaterPlant(model, runner, chilled_water_setpoint_schedule, loop_type, chillerType)
|
478
|
+
# chilled water plant
|
479
|
+
chilled_water_plant = OpenStudio::Model::PlantLoop.new(model)
|
480
|
+
chilled_water_plant.setName("New #{loop_type} Loop")
|
481
|
+
chilled_water_plant.setMaximumLoopTemperature(98)
|
482
|
+
chilled_water_plant.setMinimumLoopTemperature(1)
|
483
|
+
loop_sizing = chilled_water_plant.sizingPlant
|
484
|
+
loop_sizing.setLoopType('Cooling')
|
485
|
+
if loop_type == 'Chilled Water'
|
486
|
+
loop_sizing.setDesignLoopExitTemperature(6.7)
|
487
|
+
elsif loop_type == 'Radiant Chilled Water'
|
488
|
+
loop_sizing.setDesignLoopExitTemperature(15)
|
489
|
+
end
|
490
|
+
loop_sizing.setLoopDesignTemperatureDifference(6.7)
|
491
|
+
# create a pump
|
492
|
+
pump = OpenStudio::Model::PumpVariableSpeed.new(model)
|
493
|
+
pump.setRatedPumpHead(149453) # Pa
|
494
|
+
pump.setMotorEfficiency(0.9)
|
495
|
+
pump.setCoefficient1ofthePartLoadPerformanceCurve(0)
|
496
|
+
pump.setCoefficient2ofthePartLoadPerformanceCurve(0.0216)
|
497
|
+
pump.setCoefficient3ofthePartLoadPerformanceCurve(-0.0325)
|
498
|
+
pump.setCoefficient4ofthePartLoadPerformanceCurve(1.0095)
|
499
|
+
# create a chiller
|
500
|
+
if chillerType == 'WaterCooled'
|
501
|
+
# create clgCapFuncTempCurve
|
502
|
+
clgCapFuncTempCurve = OpenStudio::Model::CurveBiquadratic.new(model)
|
503
|
+
clgCapFuncTempCurve.setCoefficient1Constant(1.07E+00)
|
504
|
+
clgCapFuncTempCurve.setCoefficient2x(4.29E-02)
|
505
|
+
clgCapFuncTempCurve.setCoefficient3xPOW2(4.17E-04)
|
506
|
+
clgCapFuncTempCurve.setCoefficient4y(-8.10E-03)
|
507
|
+
clgCapFuncTempCurve.setCoefficient5yPOW2(-4.02E-05)
|
508
|
+
clgCapFuncTempCurve.setCoefficient6xTIMESY(-3.86E-04)
|
509
|
+
clgCapFuncTempCurve.setMinimumValueofx(0)
|
510
|
+
clgCapFuncTempCurve.setMaximumValueofx(20)
|
511
|
+
clgCapFuncTempCurve.setMinimumValueofy(0)
|
512
|
+
clgCapFuncTempCurve.setMaximumValueofy(50)
|
513
|
+
# create eirFuncTempCurve
|
514
|
+
eirFuncTempCurve = OpenStudio::Model::CurveBiquadratic.new(model)
|
515
|
+
eirFuncTempCurve.setCoefficient1Constant(4.68E-01)
|
516
|
+
eirFuncTempCurve.setCoefficient2x(-1.38E-02)
|
517
|
+
eirFuncTempCurve.setCoefficient3xPOW2(6.98E-04)
|
518
|
+
eirFuncTempCurve.setCoefficient4y(1.09E-02)
|
519
|
+
eirFuncTempCurve.setCoefficient5yPOW2(4.62E-04)
|
520
|
+
eirFuncTempCurve.setCoefficient6xTIMESY(-6.82E-04)
|
521
|
+
eirFuncTempCurve.setMinimumValueofx(0)
|
522
|
+
eirFuncTempCurve.setMaximumValueofx(20)
|
523
|
+
eirFuncTempCurve.setMinimumValueofy(0)
|
524
|
+
eirFuncTempCurve.setMaximumValueofy(50)
|
525
|
+
# create eirFuncPlrCurve
|
526
|
+
eirFuncPlrCurve = OpenStudio::Model::CurveQuadratic.new(model)
|
527
|
+
eirFuncPlrCurve.setCoefficient1Constant(1.41E-01)
|
528
|
+
eirFuncPlrCurve.setCoefficient2x(6.55E-01)
|
529
|
+
eirFuncPlrCurve.setCoefficient3xPOW2(2.03E-01)
|
530
|
+
eirFuncPlrCurve.setMinimumValueofx(0)
|
531
|
+
eirFuncPlrCurve.setMaximumValueofx(1.2)
|
532
|
+
# construct chiller
|
533
|
+
chiller = OpenStudio::Model::ChillerElectricEIR.new(model, clgCapFuncTempCurve, eirFuncTempCurve, eirFuncPlrCurve)
|
534
|
+
chiller.setReferenceCOP(6.1)
|
535
|
+
chiller.setCondenserType('WaterCooled')
|
536
|
+
chiller.setChillerFlowMode('ConstantFlow')
|
537
|
+
elsif chillerType == 'AirCooled'
|
538
|
+
# create clgCapFuncTempCurve
|
539
|
+
clgCapFuncTempCurve = OpenStudio::Model::CurveBiquadratic.new(model)
|
540
|
+
clgCapFuncTempCurve.setCoefficient1Constant(1.05E+00)
|
541
|
+
clgCapFuncTempCurve.setCoefficient2x(3.36E-02)
|
542
|
+
clgCapFuncTempCurve.setCoefficient3xPOW2(2.15E-04)
|
543
|
+
clgCapFuncTempCurve.setCoefficient4y(-5.18E-03)
|
544
|
+
clgCapFuncTempCurve.setCoefficient5yPOW2(-4.42E-05)
|
545
|
+
clgCapFuncTempCurve.setCoefficient6xTIMESY(-2.15E-04)
|
546
|
+
clgCapFuncTempCurve.setMinimumValueofx(0)
|
547
|
+
clgCapFuncTempCurve.setMaximumValueofx(20)
|
548
|
+
clgCapFuncTempCurve.setMinimumValueofy(0)
|
549
|
+
clgCapFuncTempCurve.setMaximumValueofy(50)
|
550
|
+
# create eirFuncTempCurve
|
551
|
+
eirFuncTempCurve = OpenStudio::Model::CurveBiquadratic.new(model)
|
552
|
+
eirFuncTempCurve.setCoefficient1Constant(5.83E-01)
|
553
|
+
eirFuncTempCurve.setCoefficient2x(-4.04E-03)
|
554
|
+
eirFuncTempCurve.setCoefficient3xPOW2(4.68E-04)
|
555
|
+
eirFuncTempCurve.setCoefficient4y(-2.24E-04)
|
556
|
+
eirFuncTempCurve.setCoefficient5yPOW2(4.81E-04)
|
557
|
+
eirFuncTempCurve.setCoefficient6xTIMESY(-6.82E-04)
|
558
|
+
eirFuncTempCurve.setMinimumValueofx(0)
|
559
|
+
eirFuncTempCurve.setMaximumValueofx(20)
|
560
|
+
eirFuncTempCurve.setMinimumValueofy(0)
|
561
|
+
eirFuncTempCurve.setMaximumValueofy(50)
|
562
|
+
# create eirFuncPlrCurve
|
563
|
+
eirFuncPlrCurve = OpenStudio::Model::CurveQuadratic.new(model)
|
564
|
+
eirFuncPlrCurve.setCoefficient1Constant(4.19E-02)
|
565
|
+
eirFuncPlrCurve.setCoefficient2x(6.25E-01)
|
566
|
+
eirFuncPlrCurve.setCoefficient3xPOW2(3.23E-01)
|
567
|
+
eirFuncPlrCurve.setMinimumValueofx(0)
|
568
|
+
eirFuncPlrCurve.setMaximumValueofx(1.2)
|
569
|
+
# construct chiller
|
570
|
+
chiller = OpenStudio::Model::ChillerElectricEIR.new(model, clgCapFuncTempCurve, eirFuncTempCurve, eirFuncPlrCurve)
|
571
|
+
chiller.setReferenceCOP(2.93)
|
572
|
+
chiller.setCondenserType('AirCooled')
|
573
|
+
chiller.setChillerFlowMode('ConstantFlow')
|
574
|
+
end
|
575
|
+
# create a scheduled setpoint manager
|
576
|
+
setpoint_manager_scheduled = OpenStudio::Model::SetpointManagerScheduled.new(model, chilled_water_setpoint_schedule)
|
577
|
+
# create a supply bypass pipe
|
578
|
+
pipe_supply_bypass = OpenStudio::Model::PipeAdiabatic.new(model)
|
579
|
+
# create a supply outlet pipe
|
580
|
+
pipe_supply_outlet = OpenStudio::Model::PipeAdiabatic.new(model)
|
581
|
+
# create a demand bypass pipe
|
582
|
+
pipe_demand_bypass = OpenStudio::Model::PipeAdiabatic.new(model)
|
583
|
+
# create a demand inlet pipe
|
584
|
+
pipe_demand_inlet = OpenStudio::Model::PipeAdiabatic.new(model)
|
585
|
+
# create a demand outlet pipe
|
586
|
+
pipe_demand_outlet = OpenStudio::Model::PipeAdiabatic.new(model)
|
587
|
+
# connect components to plant loop
|
588
|
+
# supply side components
|
589
|
+
chilled_water_plant.addSupplyBranchForComponent(chiller)
|
590
|
+
chilled_water_plant.addSupplyBranchForComponent(pipe_supply_bypass)
|
591
|
+
pump.addToNode(chilled_water_plant.supplyInletNode)
|
592
|
+
pipe_supply_outlet.addToNode(chilled_water_plant.supplyOutletNode)
|
593
|
+
setpoint_manager_scheduled.addToNode(chilled_water_plant.supplyOutletNode)
|
594
|
+
# demand side components (water coils are added as they are added to airloops and ZoneHVAC)
|
595
|
+
chilled_water_plant.addDemandBranchForComponent(pipe_demand_bypass)
|
596
|
+
pipe_demand_inlet.addToNode(chilled_water_plant.demandInletNode)
|
597
|
+
pipe_demand_outlet.addToNode(chilled_water_plant.demandOutletNode)
|
598
|
+
|
599
|
+
# pass back chilled water plant
|
600
|
+
result = chilled_water_plant
|
601
|
+
return result
|
602
|
+
end # end of def
|
603
|
+
|
604
|
+
def self.createCondenserLoop(model, runner, options, parameters)
|
605
|
+
condenserLoops = {}
|
606
|
+
# condLoopCoolingTemp_si = OpenStudio::convert(condLoopCoolingTemp,"F","C").get
|
607
|
+
# condLoopHeatingTemp_si = OpenStudio::convert(parameters["condLoopHeatingTemp"],"F","C").get
|
608
|
+
# coolingTowerWB_si = OpenStudio::convert(coolingTowerWB,"F","C").get
|
609
|
+
boilerHWST_si = OpenStudio.convert(parameters['boilerHWST'], 'F', 'C').get
|
610
|
+
# check for water-cooled chillers
|
611
|
+
waterCooledChiller = false
|
612
|
+
model.getChillerElectricEIRs.each do |chiller|
|
613
|
+
next if waterCooledChiller == true
|
614
|
+
if chiller.condenserType == 'WaterCooled'
|
615
|
+
waterCooledChiller = true
|
616
|
+
end
|
617
|
+
end
|
618
|
+
# create condenser loop for water-cooled chillers
|
619
|
+
if waterCooledChiller
|
620
|
+
# create condenser loop for water-cooled chiller(s)
|
621
|
+
condenser_loop = OpenStudio::Model::PlantLoop.new(model)
|
622
|
+
condenser_loop.setName('New Condenser Loop')
|
623
|
+
condenser_loop.setMaximumLoopTemperature(80)
|
624
|
+
condenser_loop.setMinimumLoopTemperature(5)
|
625
|
+
loop_sizing = condenser_loop.sizingPlant
|
626
|
+
loop_sizing.setLoopType('Condenser')
|
627
|
+
loop_sizing.setDesignLoopExitTemperature(29.4)
|
628
|
+
loop_sizing.setLoopDesignTemperatureDifference(5.6)
|
629
|
+
# create a pump
|
630
|
+
pump = OpenStudio::Model::PumpVariableSpeed.new(model)
|
631
|
+
pump.setRatedPumpHead(134508) # Pa
|
632
|
+
pump.setMotorEfficiency(0.9)
|
633
|
+
pump.setCoefficient1ofthePartLoadPerformanceCurve(0)
|
634
|
+
pump.setCoefficient2ofthePartLoadPerformanceCurve(0.0216)
|
635
|
+
pump.setCoefficient3ofthePartLoadPerformanceCurve(-0.0325)
|
636
|
+
pump.setCoefficient4ofthePartLoadPerformanceCurve(1.0095)
|
637
|
+
# create a cooling tower
|
638
|
+
tower = OpenStudio::Model::CoolingTowerVariableSpeed.new(model)
|
639
|
+
# create a supply bypass pipe
|
640
|
+
pipe_supply_bypass = OpenStudio::Model::PipeAdiabatic.new(model)
|
641
|
+
# create a supply outlet pipe
|
642
|
+
pipe_supply_outlet = OpenStudio::Model::PipeAdiabatic.new(model)
|
643
|
+
# create a demand bypass pipe
|
644
|
+
pipe_demand_bypass = OpenStudio::Model::PipeAdiabatic.new(model)
|
645
|
+
# create a demand inlet pipe
|
646
|
+
pipe_demand_inlet = OpenStudio::Model::PipeAdiabatic.new(model)
|
647
|
+
# create a demand outlet pipe
|
648
|
+
pipe_demand_outlet = OpenStudio::Model::PipeAdiabatic.new(model)
|
649
|
+
# create a setpoint manager
|
650
|
+
setpoint_manager_follow_oa = OpenStudio::Model::SetpointManagerFollowOutdoorAirTemperature.new(model)
|
651
|
+
setpoint_manager_follow_oa.setOffsetTemperatureDifference(0)
|
652
|
+
setpoint_manager_follow_oa.setMaximumSetpointTemperature(80)
|
653
|
+
setpoint_manager_follow_oa.setMinimumSetpointTemperature(5)
|
654
|
+
# connect components to plant loop
|
655
|
+
# supply side components
|
656
|
+
condenser_loop.addSupplyBranchForComponent(tower)
|
657
|
+
condenser_loop.addSupplyBranchForComponent(pipe_supply_bypass)
|
658
|
+
pump.addToNode(condenser_loop.supplyInletNode)
|
659
|
+
pipe_supply_outlet.addToNode(condenser_loop.supplyOutletNode)
|
660
|
+
setpoint_manager_follow_oa.addToNode(condenser_loop.supplyOutletNode)
|
661
|
+
# demand side components
|
662
|
+
model.getChillerElectricEIRs.each do |chiller|
|
663
|
+
if chiller.condenserType == 'WaterCooled' # works only if chillers not already connected to condenser loop(s)
|
664
|
+
condenser_loop.addDemandBranchForComponent(chiller)
|
665
|
+
end
|
666
|
+
end
|
667
|
+
condenser_loop.addDemandBranchForComponent(pipe_demand_bypass)
|
668
|
+
pipe_demand_inlet.addToNode(condenser_loop.demandInletNode)
|
669
|
+
pipe_demand_outlet.addToNode(condenser_loop.demandOutletNode)
|
670
|
+
condenserLoops['condenser_loop'] = condenser_loop
|
671
|
+
end
|
672
|
+
if (options['zoneHVAC'] == 'WSHP') || (options['zoneHVAC'] == 'GSHP') && options
|
673
|
+
# create condenser loop for heat pumps
|
674
|
+
condenser_loop = OpenStudio::Model::PlantLoop.new(model)
|
675
|
+
condenser_loop.setName('Heat Pump Loop')
|
676
|
+
condenser_loop.setMaximumLoopTemperature(80)
|
677
|
+
condenser_loop.setMinimumLoopTemperature(5)
|
678
|
+
loop_sizing = condenser_loop.sizingPlant
|
679
|
+
loop_sizing.setLoopType('Condenser')
|
680
|
+
|
681
|
+
if options['zoneHVAC'] == 'GSHP'
|
682
|
+
loop_sizing.setDesignLoopExitTemperature(32.2)
|
683
|
+
loop_sizing.setLoopDesignTemperatureDifference(5.5556)
|
684
|
+
elsif options['zoneHVAC'] == 'WSHP'
|
685
|
+
loop_sizing.setDesignLoopExitTemperature(32.2)
|
686
|
+
loop_sizing.setLoopDesignTemperatureDifference(5.5556)
|
687
|
+
end
|
688
|
+
# create a pump
|
689
|
+
pump = OpenStudio::Model::PumpVariableSpeed.new(model)
|
690
|
+
pump.setRatedPumpHead(134508) # Pa
|
691
|
+
pump.setMotorEfficiency(0.9)
|
692
|
+
pump.setCoefficient1ofthePartLoadPerformanceCurve(0)
|
693
|
+
pump.setCoefficient2ofthePartLoadPerformanceCurve(0.0216)
|
694
|
+
pump.setCoefficient3ofthePartLoadPerformanceCurve(-0.0325)
|
695
|
+
pump.setCoefficient4ofthePartLoadPerformanceCurve(1.0095)
|
696
|
+
# create a supply bypass pipe
|
697
|
+
pipe_supply_bypass = OpenStudio::Model::PipeAdiabatic.new(model)
|
698
|
+
# create a supply outlet pipe
|
699
|
+
pipe_supply_outlet = OpenStudio::Model::PipeAdiabatic.new(model)
|
700
|
+
# create a demand bypass pipe
|
701
|
+
pipe_demand_bypass = OpenStudio::Model::PipeAdiabatic.new(model)
|
702
|
+
# create a demand inlet pipe
|
703
|
+
pipe_demand_inlet = OpenStudio::Model::PipeAdiabatic.new(model)
|
704
|
+
# create a demand outlet pipe
|
705
|
+
pipe_demand_outlet = OpenStudio::Model::PipeAdiabatic.new(model)
|
706
|
+
# create setpoint managers
|
707
|
+
setpoint_manager_scheduled_loop = OpenStudio::Model::SetpointManagerScheduled.new(model, options['loop_setpoint_schedule'])
|
708
|
+
setpoint_manager_scheduled_cooling = OpenStudio::Model::SetpointManagerScheduled.new(model, options['cooling_setpoint_schedule'])
|
709
|
+
setpoint_manager_scheduled_heating = OpenStudio::Model::SetpointManagerScheduled.new(model, options['heating_setpoint_schedule'])
|
710
|
+
# connect components to plant loop
|
711
|
+
# supply side components
|
712
|
+
condenser_loop.addSupplyBranchForComponent(pipe_supply_bypass)
|
713
|
+
pump.addToNode(condenser_loop.supplyInletNode)
|
714
|
+
pipe_supply_outlet.addToNode(condenser_loop.supplyOutletNode)
|
715
|
+
setpoint_manager_scheduled_loop.addToNode(condenser_loop.supplyOutletNode)
|
716
|
+
# demand side components
|
717
|
+
condenser_loop.addDemandBranchForComponent(pipe_demand_bypass)
|
718
|
+
pipe_demand_inlet.addToNode(condenser_loop.demandInletNode)
|
719
|
+
pipe_demand_outlet.addToNode(condenser_loop.demandOutletNode)
|
720
|
+
# add additional components according to specific system type
|
721
|
+
if options['zoneHVAC'] == 'GSHP'
|
722
|
+
# add vertical ground heat exchanger
|
723
|
+
verticalGHX = OpenStudio::Model::GroundHeatExchangerVertical.new(model)
|
724
|
+
boreHoleLength_si = OpenStudio.convert(parameters['boreHoleLength'], 'ft', 'm').get
|
725
|
+
boreHoleRadius_si = OpenStudio.convert(parameters['boreHoleRadius'], 'in', 'm').get
|
726
|
+
groundKValue_si = OpenStudio.convert(parameters['groundKValue'], 'Btu/ft*h*R', 'W/m*K').get
|
727
|
+
groutKValue_si = OpenStudio.convert(parameters['groutKValue'], 'Btu/ft*h*R', 'W/m*K').get
|
728
|
+
verticalGHX.setNumberofBoreHoles(parameters['boreHoleNo'])
|
729
|
+
verticalGHX.setBoreHoleLength(boreHoleLength_si)
|
730
|
+
verticalGHX.setBoreHoleRadius(boreHoleRadius_si)
|
731
|
+
verticalGHX.setGroundThermalConductivity(groundKValue_si)
|
732
|
+
verticalGHX.setGroutThermalConductivity(groutKValue_si)
|
733
|
+
condenser_loop.addSupplyBranchForComponent(verticalGHX)
|
734
|
+
# setpoint_manager_scheduled_heating.addToNode(verticalGHX.outletModelObject.get.to_Node.get)
|
735
|
+
# add supplemental heating boiler
|
736
|
+
if parameters['supplementalBoiler'] == 'Yes'
|
737
|
+
boiler = OpenStudio::Model::BoilerHotWater.new(model)
|
738
|
+
boiler.setNominalCapacity(parameters['boilerCap'] * 1000000)
|
739
|
+
boiler.setNominalThermalEfficiency(parameters['boilerEff'])
|
740
|
+
boiler.setFuelType(parameters['boilerFuelType'])
|
741
|
+
boiler.setDesignWaterOutletTemperature(boilerHWST_si)
|
742
|
+
boiler.addToNode(verticalGHX.outletModelObject.get.to_Node.get)
|
743
|
+
# condenser_loop.addSupplyBranchForComponent(boiler)
|
744
|
+
# setpoint_manager_scheduled_heating.addToNode(boiler.outletModelObject.get.to_Node.get)
|
745
|
+
end
|
746
|
+
# add heat pumps to demand side after they get created
|
747
|
+
elsif options['zoneHVAC'] == 'WSHP'
|
748
|
+
# add a boiler and cooling tower to supply side
|
749
|
+
# create a boiler
|
750
|
+
boiler = OpenStudio::Model::BoilerHotWater.new(model)
|
751
|
+
boiler.setNominalThermalEfficiency(parameters['boilerEff'])
|
752
|
+
boiler.setFuelType(parameters['boilerFuelType'])
|
753
|
+
boiler.setDesignWaterOutletTemperature(parameters['boilerHWST'])
|
754
|
+
condenser_loop.addSupplyBranchForComponent(boiler)
|
755
|
+
setpoint_manager_scheduled_heating.addToNode(boiler.outletModelObject.get.to_Node.get)
|
756
|
+
# create a cooling tower
|
757
|
+
tower = OpenStudio::Model::CoolingTowerVariableSpeed.new(model)
|
758
|
+
# tower.setDesignInletAirWetBulbTemperature(coolingTowerWB_si)
|
759
|
+
# tower.setDesignApproachTemperature(coolingTowerApproach/1.8)
|
760
|
+
# tower.setDesignRangeTemperature(coolingTowerDeltaT/1.8)
|
761
|
+
tower.addToNode(boiler.outletModelObject.get.to_Node.get)
|
762
|
+
setpoint_manager_scheduled_cooling.addToNode(tower.outletModelObject.get.to_Node.get)
|
763
|
+
end
|
764
|
+
condenserLoops['heat_pump_loop'] = condenser_loop
|
765
|
+
end
|
766
|
+
|
767
|
+
# pass back condenser loop(s)
|
768
|
+
result = condenserLoops
|
769
|
+
return result
|
770
|
+
end # end of def
|
771
|
+
|
772
|
+
def self.createPrimaryAirLoops(model, runner, options, parameters)
|
773
|
+
primary_airloops = []
|
774
|
+
# create primary airloop for each story
|
775
|
+
assignedThermalZones = []
|
776
|
+
model.getBuildingStorys.sort.each do |building_story|
|
777
|
+
# ML stories need to be reordered from the ground up
|
778
|
+
thermalZonesToAdd = []
|
779
|
+
building_story.spaces.each do |space|
|
780
|
+
# make sure spaces are assigned to thermal zones
|
781
|
+
# otherwise might want to send a warning
|
782
|
+
if space.thermalZone.is_initialized
|
783
|
+
thermal_zone = space.thermalZone.get
|
784
|
+
# grab primary zones
|
785
|
+
if options['zonesPrimary'].include? thermal_zone
|
786
|
+
# make sure zone was not already assigned to another air loop
|
787
|
+
unless assignedThermalZones.include? thermal_zone
|
788
|
+
# make sure thermal zones are not duplicated (spaces can share thermal zones)
|
789
|
+
unless thermalZonesToAdd.include? thermal_zone
|
790
|
+
thermalZonesToAdd << thermal_zone
|
791
|
+
end
|
792
|
+
end
|
793
|
+
end
|
794
|
+
end
|
795
|
+
end
|
796
|
+
# make sure thermal zones don't get added to more than one air loop
|
797
|
+
assignedThermalZones << thermalZonesToAdd
|
798
|
+
|
799
|
+
# create new air loop if story contains primary zones
|
800
|
+
unless thermalZonesToAdd.empty?
|
801
|
+
airloop_primary = OpenStudio::Model::AirLoopHVAC.new(model)
|
802
|
+
airloop_primary.setName("DOAS - #{building_story.name}")
|
803
|
+
# modify system sizing properties
|
804
|
+
sizing_system = airloop_primary.sizingSystem
|
805
|
+
# set central heating and cooling temperatures for sizing
|
806
|
+
sizing_system.setCentralCoolingDesignSupplyAirTemperature(12.8)
|
807
|
+
sizing_system.setCentralHeatingDesignSupplyAirTemperature(40) # ML OS default is 16.7
|
808
|
+
# load specification
|
809
|
+
sizing_system.setSystemOutdoorAirMethod('VentilationRateProcedure') # ML OS default is ZoneSum
|
810
|
+
if options['primaryHVAC']['doas']
|
811
|
+
sizing_system.setTypeofLoadtoSizeOn('VentilationRequirement') # DOAS
|
812
|
+
sizing_system.setAllOutdoorAirinCooling(true) # DOAS
|
813
|
+
sizing_system.setAllOutdoorAirinHeating(true) # DOAS
|
814
|
+
else
|
815
|
+
sizing_system.setTypeofLoadtoSizeOn('Sensible') # VAV
|
816
|
+
sizing_system.setAllOutdoorAirinCooling(false) # VAV
|
817
|
+
sizing_system.setAllOutdoorAirinHeating(false) # VAV
|
818
|
+
end
|
819
|
+
|
820
|
+
air_loop_comps = []
|
821
|
+
# set availability schedule
|
822
|
+
airloop_primary.setAvailabilitySchedule(options['hvac_schedule'])
|
823
|
+
# create air loop fan
|
824
|
+
if options['primaryHVAC']['fan'] == 'Variable'
|
825
|
+
# create variable speed fan and set system sizing accordingly
|
826
|
+
sizing_system.setMinimumSystemAirFlowRatio(0.3) # DCV
|
827
|
+
# variable speed fan
|
828
|
+
fan = OpenStudio::Model::FanVariableVolume.new(model, model.alwaysOnDiscreteSchedule)
|
829
|
+
fan.setFanEfficiency(0.69)
|
830
|
+
fan.setPressureRise(1125) # Pa
|
831
|
+
fan.autosizeMaximumFlowRate
|
832
|
+
fan.setFanPowerMinimumFlowFraction(0.6)
|
833
|
+
fan.setMotorEfficiency(0.9)
|
834
|
+
fan.setMotorInAirstreamFraction(1.0)
|
835
|
+
air_loop_comps << fan
|
836
|
+
else
|
837
|
+
sizing_system.setMinimumSystemAirFlowRatio(1.0) # No DCV
|
838
|
+
# constant speed fan
|
839
|
+
fan = OpenStudio::Model::FanConstantVolume.new(model, model.alwaysOnDiscreteSchedule)
|
840
|
+
fan.setFanEfficiency(0.6)
|
841
|
+
fan.setPressureRise(500) # Pa
|
842
|
+
fan.autosizeMaximumFlowRate
|
843
|
+
fan.setMotorEfficiency(0.9)
|
844
|
+
fan.setMotorInAirstreamFraction(1.0)
|
845
|
+
air_loop_comps << fan
|
846
|
+
end
|
847
|
+
# create heating coil
|
848
|
+
if options['primaryHVAC']['heat'] == 'Water'
|
849
|
+
# water coil
|
850
|
+
heating_coil = OpenStudio::Model::CoilHeatingWater.new(model, model.alwaysOnDiscreteSchedule)
|
851
|
+
air_loop_comps << heating_coil
|
852
|
+
else
|
853
|
+
# gas coil
|
854
|
+
heating_coil = OpenStudio::Model::CoilHeatingGas.new(model, model.alwaysOnDiscreteSchedule)
|
855
|
+
air_loop_comps << heating_coil
|
856
|
+
end
|
857
|
+
# create cooling coil
|
858
|
+
if options['primaryHVAC']['cool'] == 'Water'
|
859
|
+
# water coil
|
860
|
+
cooling_coil = OpenStudio::Model::CoilCoolingWater.new(model, model.alwaysOnDiscreteSchedule)
|
861
|
+
air_loop_comps << cooling_coil
|
862
|
+
elsif options['primaryHVAC']['cool'] == 'SingleDX'
|
863
|
+
# single speed DX coil
|
864
|
+
# create cooling coil
|
865
|
+
# create clgCapFuncTempCurve
|
866
|
+
clgCapFuncTempCurve = OpenStudio::Model::CurveBiquadratic.new(model)
|
867
|
+
clgCapFuncTempCurve.setCoefficient1Constant(0.42415)
|
868
|
+
clgCapFuncTempCurve.setCoefficient2x(0.04426)
|
869
|
+
clgCapFuncTempCurve.setCoefficient3xPOW2(-0.00042)
|
870
|
+
clgCapFuncTempCurve.setCoefficient4y(0.00333)
|
871
|
+
clgCapFuncTempCurve.setCoefficient5yPOW2(-0.00008)
|
872
|
+
clgCapFuncTempCurve.setCoefficient6xTIMESY(-0.00021)
|
873
|
+
clgCapFuncTempCurve.setMinimumValueofx(17)
|
874
|
+
clgCapFuncTempCurve.setMaximumValueofx(22)
|
875
|
+
clgCapFuncTempCurve.setMinimumValueofy(13)
|
876
|
+
clgCapFuncTempCurve.setMaximumValueofy(46)
|
877
|
+
# create clgCapFuncFlowFracCurve
|
878
|
+
clgCapFuncFlowFracCurve = OpenStudio::Model::CurveQuadratic.new(model)
|
879
|
+
clgCapFuncFlowFracCurve.setCoefficient1Constant(0.77136)
|
880
|
+
clgCapFuncFlowFracCurve.setCoefficient2x(0.34053)
|
881
|
+
clgCapFuncFlowFracCurve.setCoefficient3xPOW2(-0.11088)
|
882
|
+
clgCapFuncFlowFracCurve.setMinimumValueofx(0.75918)
|
883
|
+
clgCapFuncFlowFracCurve.setMaximumValueofx(1.13877)
|
884
|
+
# create clgEirFuncTempCurve
|
885
|
+
clgEirFuncTempCurve = OpenStudio::Model::CurveBiquadratic.new(model)
|
886
|
+
clgEirFuncTempCurve.setCoefficient1Constant(1.23649)
|
887
|
+
clgEirFuncTempCurve.setCoefficient2x(-0.02431)
|
888
|
+
clgEirFuncTempCurve.setCoefficient3xPOW2(0.00057)
|
889
|
+
clgEirFuncTempCurve.setCoefficient4y(-0.01434)
|
890
|
+
clgEirFuncTempCurve.setCoefficient5yPOW2(0.00063)
|
891
|
+
clgEirFuncTempCurve.setCoefficient6xTIMESY(-0.00038)
|
892
|
+
clgEirFuncTempCurve.setMinimumValueofx(17)
|
893
|
+
clgEirFuncTempCurve.setMaximumValueofx(22)
|
894
|
+
clgEirFuncTempCurve.setMinimumValueofy(13)
|
895
|
+
clgEirFuncTempCurve.setMaximumValueofy(46)
|
896
|
+
# create clgEirFuncFlowFracCurve
|
897
|
+
clgEirFuncFlowFracCurve = OpenStudio::Model::CurveQuadratic.new(model)
|
898
|
+
clgEirFuncFlowFracCurve.setCoefficient1Constant(1.20550)
|
899
|
+
clgEirFuncFlowFracCurve.setCoefficient2x(-0.32953)
|
900
|
+
clgEirFuncFlowFracCurve.setCoefficient3xPOW2(0.12308)
|
901
|
+
clgEirFuncFlowFracCurve.setMinimumValueofx(0.75918)
|
902
|
+
clgEirFuncFlowFracCurve.setMaximumValueofx(1.13877)
|
903
|
+
# create clgPlrCurve
|
904
|
+
clgPlrCurve = OpenStudio::Model::CurveQuadratic.new(model)
|
905
|
+
clgPlrCurve.setCoefficient1Constant(0.77100)
|
906
|
+
clgPlrCurve.setCoefficient2x(0.22900)
|
907
|
+
clgPlrCurve.setCoefficient3xPOW2(0.0)
|
908
|
+
clgPlrCurve.setMinimumValueofx(0.0)
|
909
|
+
clgPlrCurve.setMaximumValueofx(1.0)
|
910
|
+
# cooling coil
|
911
|
+
cooling_coil = OpenStudio::Model::CoilCoolingDXSingleSpeed.new(model,
|
912
|
+
model.alwaysOnDiscreteSchedule,
|
913
|
+
clgCapFuncTempCurve,
|
914
|
+
clgCapFuncFlowFracCurve,
|
915
|
+
clgEirFuncTempCurve,
|
916
|
+
clgEirFuncFlowFracCurve,
|
917
|
+
clgPlrCurve)
|
918
|
+
cooling_coil.setRatedCOP(OpenStudio::OptionalDouble.new(parameters['doasDXEER'] / 3.412))
|
919
|
+
air_loop_comps << cooling_coil
|
920
|
+
else
|
921
|
+
# two speed DX coil (PNNL curves)
|
922
|
+
# create cooling coil
|
923
|
+
# create clgCapFuncTempCurve
|
924
|
+
clgCapFuncTempCurve = OpenStudio::Model::CurveBiquadratic.new(model)
|
925
|
+
clgCapFuncTempCurve.setCoefficient1Constant(1.39072)
|
926
|
+
clgCapFuncTempCurve.setCoefficient2x(-0.0529058)
|
927
|
+
clgCapFuncTempCurve.setCoefficient3xPOW2(0.0018423)
|
928
|
+
clgCapFuncTempCurve.setCoefficient4y(0.00058267)
|
929
|
+
clgCapFuncTempCurve.setCoefficient5yPOW2(-0.000186814)
|
930
|
+
clgCapFuncTempCurve.setCoefficient6xTIMESY(0.000265159)
|
931
|
+
clgCapFuncTempCurve.setMinimumValueofx(16.5556)
|
932
|
+
clgCapFuncTempCurve.setMaximumValueofx(22.1111)
|
933
|
+
clgCapFuncTempCurve.setMinimumValueofy(23.7778)
|
934
|
+
clgCapFuncTempCurve.setMaximumValueofy(47.66)
|
935
|
+
# create clgCapFuncFlowFracCurve
|
936
|
+
clgCapFuncFlowFracCurve = OpenStudio::Model::CurveQuadratic.new(model)
|
937
|
+
clgCapFuncFlowFracCurve.setCoefficient1Constant(0.718954)
|
938
|
+
clgCapFuncFlowFracCurve.setCoefficient2x(0.435436)
|
939
|
+
clgCapFuncFlowFracCurve.setCoefficient3xPOW2(-0.154193)
|
940
|
+
clgCapFuncFlowFracCurve.setMinimumValueofx(0.75)
|
941
|
+
clgCapFuncFlowFracCurve.setMaximumValueofx(1.25)
|
942
|
+
# create clgEirFuncTempCurve
|
943
|
+
clgEirFuncTempCurve = OpenStudio::Model::CurveBiquadratic.new(model)
|
944
|
+
clgEirFuncTempCurve.setCoefficient1Constant(-0.536161)
|
945
|
+
clgEirFuncTempCurve.setCoefficient2x(0.105138)
|
946
|
+
clgEirFuncTempCurve.setCoefficient3xPOW2(-0.00172659)
|
947
|
+
clgEirFuncTempCurve.setCoefficient4y(0.0149848)
|
948
|
+
clgEirFuncTempCurve.setCoefficient5yPOW2(0.000659948)
|
949
|
+
clgEirFuncTempCurve.setCoefficient6xTIMESY(-0.0017385)
|
950
|
+
clgEirFuncTempCurve.setMinimumValueofx(16.5556)
|
951
|
+
clgEirFuncTempCurve.setMaximumValueofx(22.1111)
|
952
|
+
clgEirFuncTempCurve.setMinimumValueofy(23.7778)
|
953
|
+
clgEirFuncTempCurve.setMaximumValueofy(47.66)
|
954
|
+
# create clgEirFuncFlowFracCurve
|
955
|
+
clgEirFuncFlowFracCurve = OpenStudio::Model::CurveQuadratic.new(model)
|
956
|
+
clgEirFuncFlowFracCurve.setCoefficient1Constant(1.19525)
|
957
|
+
clgEirFuncFlowFracCurve.setCoefficient2x(-0.306138)
|
958
|
+
clgEirFuncFlowFracCurve.setCoefficient3xPOW2(0.110973)
|
959
|
+
clgEirFuncFlowFracCurve.setMinimumValueofx(0.75)
|
960
|
+
clgEirFuncFlowFracCurve.setMaximumValueofx(1.25)
|
961
|
+
# create clgPlrCurve
|
962
|
+
clgPlrCurve = OpenStudio::Model::CurveQuadratic.new(model)
|
963
|
+
clgPlrCurve.setCoefficient1Constant(0.77100)
|
964
|
+
clgPlrCurve.setCoefficient2x(0.22900)
|
965
|
+
clgPlrCurve.setCoefficient3xPOW2(0.0)
|
966
|
+
clgPlrCurve.setMinimumValueofx(0.0)
|
967
|
+
clgPlrCurve.setMaximumValueofx(1.0)
|
968
|
+
# cooling coil
|
969
|
+
cooling_coil = OpenStudio::Model::CoilCoolingDXTwoSpeed.new(model,
|
970
|
+
model.alwaysOnDiscreteSchedule,
|
971
|
+
clgCapFuncTempCurve,
|
972
|
+
clgCapFuncFlowFracCurve,
|
973
|
+
clgEirFuncTempCurve,
|
974
|
+
clgEirFuncFlowFracCurve,
|
975
|
+
clgPlrCurve,
|
976
|
+
clgCapFuncTempCurve,
|
977
|
+
clgEirFuncTempCurve)
|
978
|
+
cooling_coil.setRatedHighSpeedCOP(parameters['doasDXEER'] / 3.412)
|
979
|
+
cooling_coil.setRatedLowSpeedCOP(parameters['doasDXEER'] / 3.412)
|
980
|
+
air_loop_comps << cooling_coil
|
981
|
+
end
|
982
|
+
unless options['zoneHVAC'] == 'DualDuct'
|
983
|
+
# create controller outdoor air
|
984
|
+
controller_OA = OpenStudio::Model::ControllerOutdoorAir.new(model)
|
985
|
+
controller_OA.autosizeMinimumOutdoorAirFlowRate
|
986
|
+
controller_OA.autosizeMaximumOutdoorAirFlowRate
|
987
|
+
# create ventilation schedules and assign to OA controller
|
988
|
+
if options['primaryHVAC']['doas']
|
989
|
+
controller_OA.setMinimumFractionofOutdoorAirSchedule(model.alwaysOnDiscreteSchedule)
|
990
|
+
controller_OA.setMaximumFractionofOutdoorAirSchedule(model.alwaysOnDiscreteSchedule)
|
991
|
+
else
|
992
|
+
# multizone VAV that ventilates
|
993
|
+
controller_OA.setMaximumFractionofOutdoorAirSchedule(options['ventilation_schedule'])
|
994
|
+
controller_OA.setEconomizerControlType('DifferentialEnthalpy')
|
995
|
+
# add night cycling (ML would people actually do this for a VAV system?))
|
996
|
+
airloop_primary.setNightCycleControlType('CycleOnAny') # ML Does this work with variable speed fans?
|
997
|
+
end
|
998
|
+
controller_OA.setHeatRecoveryBypassControlType('BypassWhenOAFlowGreaterThanMinimum')
|
999
|
+
# create outdoor air system
|
1000
|
+
system_OA = OpenStudio::Model::AirLoopHVACOutdoorAirSystem.new(model, controller_OA)
|
1001
|
+
air_loop_comps << system_OA
|
1002
|
+
# create Evaporative cooler
|
1003
|
+
unless parameters['doasEvap'] == 'none'
|
1004
|
+
evap_cooler = OpenStudio::Model::EvaporativeCoolerDirectResearchSpecial.new(model, model.alwaysOnDiscreteSchedule)
|
1005
|
+
evap_cooler.setCoolerEffectiveness(0.85)
|
1006
|
+
end
|
1007
|
+
# create ERV
|
1008
|
+
unless parameters['doasERV'] == 'none'
|
1009
|
+
heat_exchanger = OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent.new(model)
|
1010
|
+
heat_exchanger.setAvailabilitySchedule(model.alwaysOnDiscreteSchedule)
|
1011
|
+
if parameters['doasERV'] == 'rotary wheel w/o economizer lockout'
|
1012
|
+
sensible_eff = 0.75
|
1013
|
+
latent_eff = 0.69
|
1014
|
+
# heat_exchanger.setEconomizerLockout(false)
|
1015
|
+
heat_exchanger.setString(23, 'No')
|
1016
|
+
elsif parameters['doasERV'] == 'rotary wheel w/ economizer lockout'
|
1017
|
+
sensible_eff = 0.75
|
1018
|
+
latent_eff = 0.69
|
1019
|
+
# heat_exchanger.setEconomizerLockout(true)
|
1020
|
+
heat_exchanger.setString(23, 'Yes')
|
1021
|
+
elsif parameters['doasERV'] == 'plate w/o economizer lockout'
|
1022
|
+
sensible_eff = 0.52
|
1023
|
+
latent_eff = 0.50
|
1024
|
+
# heat_exchanger.setEconomizerLockout(false)
|
1025
|
+
heat_exchanger.setString(23, 'No')
|
1026
|
+
elsif parameters['doasERV'] == 'plate w/ economizer lockout'
|
1027
|
+
sensible_eff = 0.52
|
1028
|
+
latent_eff = 0.50
|
1029
|
+
# heat_exchanger.setEconomizerLockout(true)
|
1030
|
+
heat_exchanger.setString(23, 'Yes')
|
1031
|
+
end
|
1032
|
+
heat_exchanger.setSensibleEffectivenessat100CoolingAirFlow(sensible_eff)
|
1033
|
+
heat_exchanger.setSensibleEffectivenessat100HeatingAirFlow(sensible_eff)
|
1034
|
+
heat_exchanger.setSensibleEffectivenessat75CoolingAirFlow(sensible_eff)
|
1035
|
+
heat_exchanger.setSensibleEffectivenessat75HeatingAirFlow(sensible_eff)
|
1036
|
+
heat_exchanger.setLatentEffectivenessat100CoolingAirFlow(latent_eff)
|
1037
|
+
heat_exchanger.setLatentEffectivenessat100HeatingAirFlow(latent_eff)
|
1038
|
+
heat_exchanger.setLatentEffectivenessat75CoolingAirFlow(latent_eff)
|
1039
|
+
heat_exchanger.setLatentEffectivenessat75HeatingAirFlow(latent_eff)
|
1040
|
+
heat_exchanger.setFrostControlType('ExhaustOnly')
|
1041
|
+
heat_exchanger.setThresholdTemperature(-12.2)
|
1042
|
+
heat_exchanger.setInitialDefrostTimeFraction(0.1670)
|
1043
|
+
heat_exchanger.setRateofDefrostTimeFractionIncrease(0.0240)
|
1044
|
+
end
|
1045
|
+
|
1046
|
+
end
|
1047
|
+
# create scheduled setpoint manager for airloop
|
1048
|
+
if options['primaryHVAC']['doas'] || (options['zoneHVAC'] == 'DualDuct')
|
1049
|
+
# DOAS or VAV for cooling and not ventilation
|
1050
|
+
setpoint_manager = OpenStudio::Model::SetpointManagerScheduled.new(model, options['primary_sat_schedule'])
|
1051
|
+
else
|
1052
|
+
# VAV for cooling and ventilation
|
1053
|
+
setpoint_manager = OpenStudio::Model::SetpointManagerOutdoorAirReset.new(model)
|
1054
|
+
setpoint_manager.setSetpointatOutdoorLowTemperature(15.6)
|
1055
|
+
setpoint_manager.setOutdoorLowTemperature(14.4)
|
1056
|
+
setpoint_manager.setSetpointatOutdoorHighTemperature(12.8)
|
1057
|
+
setpoint_manager.setOutdoorHighTemperature(21.1)
|
1058
|
+
end
|
1059
|
+
# connect components to airloop
|
1060
|
+
# find the supply inlet node of the airloop
|
1061
|
+
airloop_supply_inlet = airloop_primary.supplyInletNode
|
1062
|
+
# add the components to the airloop
|
1063
|
+
air_loop_comps.each do |comp|
|
1064
|
+
comp.addToNode(airloop_supply_inlet)
|
1065
|
+
if comp.to_CoilHeatingWater.is_initialized
|
1066
|
+
options['hot_water_plant'].addDemandBranchForComponent(comp)
|
1067
|
+
comp.controllerWaterCoil.get.setMinimumActuatedFlow(0)
|
1068
|
+
elsif comp.to_CoilCoolingWater.is_initialized
|
1069
|
+
options['chilled_water_plant'].addDemandBranchForComponent(comp)
|
1070
|
+
comp.controllerWaterCoil.get.setMinimumActuatedFlow(0)
|
1071
|
+
end
|
1072
|
+
end
|
1073
|
+
unless (options['zoneHVAC'] == 'DualDuct') || (parameters['doasERV'] == 'none')
|
1074
|
+
heat_exchanger.addToNode(system_OA.outboardOANode.get)
|
1075
|
+
end
|
1076
|
+
|
1077
|
+
unless parameters['doasEvap'] == 'none'
|
1078
|
+
if parameters['doasERV'] == 'none'
|
1079
|
+
evap_cooler.addToNode(system_OA.outboardOANode.get)
|
1080
|
+
else
|
1081
|
+
hxPrimary_outlet_node = heat_exchanger.primaryAirOutletModelObject.get.to_Node.get
|
1082
|
+
evap_cooler.addToNode(hxPrimary_outlet_node)
|
1083
|
+
end
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
# add setpoint manager to supply equipment outlet node
|
1087
|
+
setpoint_manager.addToNode(airloop_primary.supplyOutletNode)
|
1088
|
+
# add thermal zones to airloop
|
1089
|
+
thermalZonesToAdd.each do |zone|
|
1090
|
+
# make an air terminal for the zone
|
1091
|
+
if options['primaryHVAC']['fan'] == 'Variable'
|
1092
|
+
air_terminal = OpenStudio::Model::AirTerminalSingleDuctVAVNoReheat.new(model, model.alwaysOnDiscreteSchedule)
|
1093
|
+
else
|
1094
|
+
air_terminal = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, model.alwaysOnDiscreteSchedule)
|
1095
|
+
end
|
1096
|
+
# attach new terminal to the zone and to the airloop
|
1097
|
+
airloop_primary.addBranchForZone(zone, air_terminal.to_StraightComponent)
|
1098
|
+
end
|
1099
|
+
primary_airloops << airloop_primary
|
1100
|
+
end
|
1101
|
+
end
|
1102
|
+
|
1103
|
+
# pass back primary airloops
|
1104
|
+
result = primary_airloops
|
1105
|
+
return result
|
1106
|
+
end # end of def
|
1107
|
+
|
1108
|
+
def self.createSecondaryAirLoops(model, runner, options)
|
1109
|
+
secondary_airloops = []
|
1110
|
+
# create secondary airloop for each secondary zone
|
1111
|
+
model.getThermalZones.each do |zone|
|
1112
|
+
if options['zonesSecondary'].include? zone
|
1113
|
+
# create secondary airloop
|
1114
|
+
airloop_secondary = OpenStudio::Model::AirLoopHVAC.new(model)
|
1115
|
+
airloop_secondary.setName("New Air Loop HVAC #{zone.name}")
|
1116
|
+
# modify system sizing properties
|
1117
|
+
sizing_system = airloop_secondary.sizingSystem
|
1118
|
+
# set central heating and cooling temperatures for sizing
|
1119
|
+
sizing_system.setCentralCoolingDesignSupplyAirTemperature(12.8)
|
1120
|
+
sizing_system.setCentralHeatingDesignSupplyAirTemperature(40) # ML OS default is 16.7
|
1121
|
+
# load specification
|
1122
|
+
sizing_system.setSystemOutdoorAirMethod('VentilationRateProcedure') # ML OS default is ZoneSum
|
1123
|
+
sizing_system.setTypeofLoadtoSizeOn('Sensible') # PSZ
|
1124
|
+
sizing_system.setAllOutdoorAirinCooling(false) # PSZ
|
1125
|
+
sizing_system.setAllOutdoorAirinHeating(false) # PSZ
|
1126
|
+
sizing_system.setMinimumSystemAirFlowRatio(1.0) # Constant volume fan
|
1127
|
+
air_loop_comps = []
|
1128
|
+
# set availability schedule (HVAC operation schedule)
|
1129
|
+
airloop_secondary.setAvailabilitySchedule(options['hvac_schedule'])
|
1130
|
+
if options['secondaryHVAC']['fan'] == 'Variable'
|
1131
|
+
# create variable speed fan and set system sizing accordingly
|
1132
|
+
sizing_system.setMinimumSystemAirFlowRatio(0.3) # DCV
|
1133
|
+
# variable speed fan
|
1134
|
+
fan = OpenStudio::Model::FanVariableVolume.new(model, model.alwaysOnDiscreteSchedule)
|
1135
|
+
fan.setFanEfficiency(0.69)
|
1136
|
+
fan.setPressureRise(1125) # Pa
|
1137
|
+
fan.autosizeMaximumFlowRate
|
1138
|
+
fan.setFanPowerMinimumFlowFraction(0.6)
|
1139
|
+
fan.setMotorEfficiency(0.9)
|
1140
|
+
fan.setMotorInAirstreamFraction(1.0)
|
1141
|
+
air_loop_comps << fan
|
1142
|
+
else
|
1143
|
+
sizing_system.setMinimumSystemAirFlowRatio(1.0) # No DCV
|
1144
|
+
# constant speed fan
|
1145
|
+
fan = OpenStudio::Model::FanConstantVolume.new(model, model.alwaysOnDiscreteSchedule)
|
1146
|
+
fan.setFanEfficiency(0.6)
|
1147
|
+
fan.setPressureRise(500) # Pa
|
1148
|
+
fan.autosizeMaximumFlowRate
|
1149
|
+
fan.setMotorEfficiency(0.9)
|
1150
|
+
fan.setMotorInAirstreamFraction(1.0)
|
1151
|
+
air_loop_comps << fan
|
1152
|
+
end
|
1153
|
+
# create cooling coil
|
1154
|
+
if options['secondaryHVAC']['cool'] == 'Water'
|
1155
|
+
# water coil
|
1156
|
+
cooling_coil = OpenStudio::Model::CoilCoolingWater.new(model, model.alwaysOnDiscreteSchedule)
|
1157
|
+
air_loop_comps << cooling_coil
|
1158
|
+
elsif options['secondaryHVAC']['cool'] == 'SingleDX'
|
1159
|
+
# single speed DX coil
|
1160
|
+
# create cooling coil
|
1161
|
+
# create clgCapFuncTempCurve
|
1162
|
+
clgCapFuncTempCurve = OpenStudio::Model::CurveBiquadratic.new(model)
|
1163
|
+
clgCapFuncTempCurve.setCoefficient1Constant(0.42415)
|
1164
|
+
clgCapFuncTempCurve.setCoefficient2x(0.04426)
|
1165
|
+
clgCapFuncTempCurve.setCoefficient3xPOW2(-0.00042)
|
1166
|
+
clgCapFuncTempCurve.setCoefficient4y(0.00333)
|
1167
|
+
clgCapFuncTempCurve.setCoefficient5yPOW2(-0.00008)
|
1168
|
+
clgCapFuncTempCurve.setCoefficient6xTIMESY(-0.00021)
|
1169
|
+
clgCapFuncTempCurve.setMinimumValueofx(17)
|
1170
|
+
clgCapFuncTempCurve.setMaximumValueofx(22)
|
1171
|
+
clgCapFuncTempCurve.setMinimumValueofy(13)
|
1172
|
+
clgCapFuncTempCurve.setMaximumValueofy(46)
|
1173
|
+
# create clgCapFuncFlowFracCurve
|
1174
|
+
clgCapFuncFlowFracCurve = OpenStudio::Model::CurveQuadratic.new(model)
|
1175
|
+
clgCapFuncFlowFracCurve.setCoefficient1Constant(0.77136)
|
1176
|
+
clgCapFuncFlowFracCurve.setCoefficient2x(0.34053)
|
1177
|
+
clgCapFuncFlowFracCurve.setCoefficient3xPOW2(-0.11088)
|
1178
|
+
clgCapFuncFlowFracCurve.setMinimumValueofx(0.75918)
|
1179
|
+
clgCapFuncFlowFracCurve.setMaximumValueofx(1.13877)
|
1180
|
+
# create clgEirFuncTempCurve
|
1181
|
+
clgEirFuncTempCurve = OpenStudio::Model::CurveBiquadratic.new(model)
|
1182
|
+
clgEirFuncTempCurve.setCoefficient1Constant(1.23649)
|
1183
|
+
clgEirFuncTempCurve.setCoefficient2x(-0.02431)
|
1184
|
+
clgEirFuncTempCurve.setCoefficient3xPOW2(0.00057)
|
1185
|
+
clgEirFuncTempCurve.setCoefficient4y(-0.01434)
|
1186
|
+
clgEirFuncTempCurve.setCoefficient5yPOW2(0.00063)
|
1187
|
+
clgEirFuncTempCurve.setCoefficient6xTIMESY(-0.00038)
|
1188
|
+
clgEirFuncTempCurve.setMinimumValueofx(17)
|
1189
|
+
clgEirFuncTempCurve.setMaximumValueofx(22)
|
1190
|
+
clgEirFuncTempCurve.setMinimumValueofy(13)
|
1191
|
+
clgEirFuncTempCurve.setMaximumValueofy(46)
|
1192
|
+
# create clgEirFuncFlowFracCurve
|
1193
|
+
clgEirFuncFlowFracCurve = OpenStudio::Model::CurveQuadratic.new(model)
|
1194
|
+
clgEirFuncFlowFracCurve.setCoefficient1Constant(1.20550)
|
1195
|
+
clgEirFuncFlowFracCurve.setCoefficient2x(-0.32953)
|
1196
|
+
clgEirFuncFlowFracCurve.setCoefficient3xPOW2(0.12308)
|
1197
|
+
clgEirFuncFlowFracCurve.setMinimumValueofx(0.75918)
|
1198
|
+
clgEirFuncFlowFracCurve.setMaximumValueofx(1.13877)
|
1199
|
+
# create clgPlrCurve
|
1200
|
+
clgPlrCurve = OpenStudio::Model::CurveQuadratic.new(model)
|
1201
|
+
clgPlrCurve.setCoefficient1Constant(0.77100)
|
1202
|
+
clgPlrCurve.setCoefficient2x(0.22900)
|
1203
|
+
clgPlrCurve.setCoefficient3xPOW2(0.0)
|
1204
|
+
clgPlrCurve.setMinimumValueofx(0.0)
|
1205
|
+
clgPlrCurve.setMaximumValueofx(1.0)
|
1206
|
+
# cooling coil
|
1207
|
+
cooling_coil = OpenStudio::Model::CoilCoolingDXSingleSpeed.new(model,
|
1208
|
+
model.alwaysOnDiscreteSchedule,
|
1209
|
+
clgCapFuncTempCurve,
|
1210
|
+
clgCapFuncFlowFracCurve,
|
1211
|
+
clgEirFuncTempCurve,
|
1212
|
+
clgEirFuncFlowFracCurve,
|
1213
|
+
clgPlrCurve)
|
1214
|
+
cooling_coil.setRatedCOP(OpenStudio::OptionalDouble.new(4))
|
1215
|
+
air_loop_comps << cooling_coil
|
1216
|
+
else
|
1217
|
+
# two speed DX coil (PNNL curves)
|
1218
|
+
# create cooling coil
|
1219
|
+
# create clgCapFuncTempCurve
|
1220
|
+
clgCapFuncTempCurve = OpenStudio::Model::CurveBiquadratic.new(model)
|
1221
|
+
clgCapFuncTempCurve.setCoefficient1Constant(1.39072)
|
1222
|
+
clgCapFuncTempCurve.setCoefficient2x(-0.0529058)
|
1223
|
+
clgCapFuncTempCurve.setCoefficient3xPOW2(0.0018423)
|
1224
|
+
clgCapFuncTempCurve.setCoefficient4y(0.00058267)
|
1225
|
+
clgCapFuncTempCurve.setCoefficient5yPOW2(-0.000186814)
|
1226
|
+
clgCapFuncTempCurve.setCoefficient6xTIMESY(0.000265159)
|
1227
|
+
clgCapFuncTempCurve.setMinimumValueofx(16.5556)
|
1228
|
+
clgCapFuncTempCurve.setMaximumValueofx(22.1111)
|
1229
|
+
clgCapFuncTempCurve.setMinimumValueofy(23.7778)
|
1230
|
+
clgCapFuncTempCurve.setMaximumValueofy(47.66)
|
1231
|
+
# create clgCapFuncFlowFracCurve
|
1232
|
+
clgCapFuncFlowFracCurve = OpenStudio::Model::CurveQuadratic.new(model)
|
1233
|
+
clgCapFuncFlowFracCurve.setCoefficient1Constant(0.718954)
|
1234
|
+
clgCapFuncFlowFracCurve.setCoefficient2x(0.435436)
|
1235
|
+
clgCapFuncFlowFracCurve.setCoefficient3xPOW2(-0.154193)
|
1236
|
+
clgCapFuncFlowFracCurve.setMinimumValueofx(0.75)
|
1237
|
+
clgCapFuncFlowFracCurve.setMaximumValueofx(1.25)
|
1238
|
+
# create clgEirFuncTempCurve
|
1239
|
+
clgEirFuncTempCurve = OpenStudio::Model::CurveBiquadratic.new(model)
|
1240
|
+
clgEirFuncTempCurve.setCoefficient1Constant(-0.536161)
|
1241
|
+
clgEirFuncTempCurve.setCoefficient2x(0.105138)
|
1242
|
+
clgEirFuncTempCurve.setCoefficient3xPOW2(-0.00172659)
|
1243
|
+
clgEirFuncTempCurve.setCoefficient4y(0.0149848)
|
1244
|
+
clgEirFuncTempCurve.setCoefficient5yPOW2(0.000659948)
|
1245
|
+
clgEirFuncTempCurve.setCoefficient6xTIMESY(-0.0017385)
|
1246
|
+
clgEirFuncTempCurve.setMinimumValueofx(16.5556)
|
1247
|
+
clgEirFuncTempCurve.setMaximumValueofx(22.1111)
|
1248
|
+
clgEirFuncTempCurve.setMinimumValueofy(23.7778)
|
1249
|
+
clgEirFuncTempCurve.setMaximumValueofy(47.66)
|
1250
|
+
# create clgEirFuncFlowFracCurve
|
1251
|
+
clgEirFuncFlowFracCurve = OpenStudio::Model::CurveQuadratic.new(model)
|
1252
|
+
clgEirFuncFlowFracCurve.setCoefficient1Constant(1.19525)
|
1253
|
+
clgEirFuncFlowFracCurve.setCoefficient2x(-0.306138)
|
1254
|
+
clgEirFuncFlowFracCurve.setCoefficient3xPOW2(0.110973)
|
1255
|
+
clgEirFuncFlowFracCurve.setMinimumValueofx(0.75)
|
1256
|
+
clgEirFuncFlowFracCurve.setMaximumValueofx(1.25)
|
1257
|
+
# create clgPlrCurve
|
1258
|
+
clgPlrCurve = OpenStudio::Model::CurveQuadratic.new(model)
|
1259
|
+
clgPlrCurve.setCoefficient1Constant(0.77100)
|
1260
|
+
clgPlrCurve.setCoefficient2x(0.22900)
|
1261
|
+
clgPlrCurve.setCoefficient3xPOW2(0.0)
|
1262
|
+
clgPlrCurve.setMinimumValueofx(0.0)
|
1263
|
+
clgPlrCurve.setMaximumValueofx(1.0)
|
1264
|
+
# cooling coil
|
1265
|
+
cooling_coil = OpenStudio::Model::CoilCoolingDXTwoSpeed.new(model,
|
1266
|
+
model.alwaysOnDiscreteSchedule,
|
1267
|
+
clgCapFuncTempCurve,
|
1268
|
+
clgCapFuncFlowFracCurve,
|
1269
|
+
clgEirFuncTempCurve,
|
1270
|
+
clgEirFuncFlowFracCurve,
|
1271
|
+
clgPlrCurve,
|
1272
|
+
clgCapFuncTempCurve,
|
1273
|
+
clgEirFuncTempCurve)
|
1274
|
+
cooling_coil.setRatedHighSpeedCOP(4)
|
1275
|
+
cooling_coil.setRatedLowSpeedCOP(4)
|
1276
|
+
air_loop_comps << cooling_coil
|
1277
|
+
end
|
1278
|
+
if options['secondaryHVAC']['heat'] == 'Water'
|
1279
|
+
# water coil
|
1280
|
+
heating_coil = OpenStudio::Model::CoilHeatingWater.new(model, model.alwaysOnDiscreteSchedule)
|
1281
|
+
air_loop_comps << heating_coil
|
1282
|
+
else
|
1283
|
+
# gas coil
|
1284
|
+
heating_coil = OpenStudio::Model::CoilHeatingGas.new(model, model.alwaysOnDiscreteSchedule)
|
1285
|
+
air_loop_comps << heating_coil
|
1286
|
+
end
|
1287
|
+
# create controller outdoor air
|
1288
|
+
controller_OA = OpenStudio::Model::ControllerOutdoorAir.new(model)
|
1289
|
+
controller_OA.autosizeMinimumOutdoorAirFlowRate
|
1290
|
+
controller_OA.autosizeMaximumOutdoorAirFlowRate
|
1291
|
+
controller_OA.setEconomizerControlType('DifferentialEnthalpy')
|
1292
|
+
controller_OA.setMaximumFractionofOutdoorAirSchedule(options['ventilation_schedule'])
|
1293
|
+
controller_OA.setHeatRecoveryBypassControlType('BypassWhenOAFlowGreaterThanMinimum')
|
1294
|
+
# create outdoor air system
|
1295
|
+
system_OA = OpenStudio::Model::AirLoopHVACOutdoorAirSystem.new(model, controller_OA)
|
1296
|
+
air_loop_comps << system_OA
|
1297
|
+
# create ERV
|
1298
|
+
heat_exchanger = OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent.new(model)
|
1299
|
+
heat_exchanger.setAvailabilitySchedule(model.alwaysOnDiscreteSchedule)
|
1300
|
+
sensible_eff = 0.75
|
1301
|
+
latent_eff = 0.69
|
1302
|
+
heat_exchanger.setSensibleEffectivenessat100CoolingAirFlow(sensible_eff)
|
1303
|
+
heat_exchanger.setSensibleEffectivenessat100HeatingAirFlow(sensible_eff)
|
1304
|
+
heat_exchanger.setSensibleEffectivenessat75CoolingAirFlow(sensible_eff)
|
1305
|
+
heat_exchanger.setSensibleEffectivenessat75HeatingAirFlow(sensible_eff)
|
1306
|
+
heat_exchanger.setLatentEffectivenessat100CoolingAirFlow(latent_eff)
|
1307
|
+
heat_exchanger.setLatentEffectivenessat100HeatingAirFlow(latent_eff)
|
1308
|
+
heat_exchanger.setLatentEffectivenessat75CoolingAirFlow(latent_eff)
|
1309
|
+
heat_exchanger.setLatentEffectivenessat75HeatingAirFlow(latent_eff)
|
1310
|
+
heat_exchanger.setFrostControlType('ExhaustOnly')
|
1311
|
+
heat_exchanger.setThresholdTemperature(-12.2)
|
1312
|
+
heat_exchanger.setInitialDefrostTimeFraction(0.1670)
|
1313
|
+
heat_exchanger.setRateofDefrostTimeFractionIncrease(0.0240)
|
1314
|
+
heat_exchanger.setEconomizerLockout(false)
|
1315
|
+
# create setpoint manager for airloop
|
1316
|
+
setpoint_manager = OpenStudio::Model::SetpointManagerSingleZoneReheat.new(model)
|
1317
|
+
setpoint_manager.setMinimumSupplyAirTemperature(10)
|
1318
|
+
setpoint_manager.setMaximumSupplyAirTemperature(50)
|
1319
|
+
setpoint_manager.setControlZone(zone)
|
1320
|
+
# connect components to airloop
|
1321
|
+
# find the supply inlet node of the airloop
|
1322
|
+
airloop_supply_inlet = airloop_secondary.supplyInletNode
|
1323
|
+
# add the components to the airloop
|
1324
|
+
air_loop_comps.each do |comp|
|
1325
|
+
comp.addToNode(airloop_supply_inlet)
|
1326
|
+
if comp.to_CoilHeatingWater.is_initialized
|
1327
|
+
options['hot_water_plant'].addDemandBranchForComponent(comp)
|
1328
|
+
comp.controllerWaterCoil.get.setMinimumActuatedFlow(0)
|
1329
|
+
elsif comp.to_CoilCoolingWater.is_initialized
|
1330
|
+
options['chilled_water_plant'].addDemandBranchForComponent(comp)
|
1331
|
+
comp.controllerWaterCoil.get.setMinimumActuatedFlow(0)
|
1332
|
+
end
|
1333
|
+
end
|
1334
|
+
# add erv to outdoor air system
|
1335
|
+
heat_exchanger.addToNode(system_OA.outboardOANode.get)
|
1336
|
+
# add setpoint manager to supply equipment outlet node
|
1337
|
+
setpoint_manager.addToNode(airloop_secondary.supplyOutletNode)
|
1338
|
+
# add thermal zone to airloop
|
1339
|
+
if options['secondaryHVAC']['fan'] == 'Variable'
|
1340
|
+
air_terminal = OpenStudio::Model::AirTerminalSingleDuctVAVNoReheat.new(model, model.alwaysOnDiscreteSchedule)
|
1341
|
+
else
|
1342
|
+
air_terminal = OpenStudio::Model::AirTerminalSingleDuctUncontrolled.new(model, model.alwaysOnDiscreteSchedule)
|
1343
|
+
end
|
1344
|
+
# attach new terminal to the zone and to the airloop
|
1345
|
+
airloop_secondary.addBranchForZone(zone, air_terminal.to_StraightComponent)
|
1346
|
+
# add night cycling
|
1347
|
+
airloop_secondary.setNightCycleControlType('CycleOnAny') # ML Does this work with variable speed fans?
|
1348
|
+
secondary_airloops << airloop_secondary
|
1349
|
+
end
|
1350
|
+
end
|
1351
|
+
|
1352
|
+
# pass back secondary airloops
|
1353
|
+
result = secondary_airloops
|
1354
|
+
return result
|
1355
|
+
end # end of def
|
1356
|
+
|
1357
|
+
def self.createPrimaryZoneEquipment(model, runner, options, parameters)
|
1358
|
+
model.getThermalZones.each do |zone|
|
1359
|
+
if options['zonesPrimary'].include? zone
|
1360
|
+
if options['zoneHVAC'] == 'FanCoil'
|
1361
|
+
# create fan coil
|
1362
|
+
# create fan
|
1363
|
+
fan = OpenStudio::Model::FanOnOff.new(model, model.alwaysOnDiscreteSchedule)
|
1364
|
+
fan.setFanEfficiency(0.5)
|
1365
|
+
fan.setPressureRise(75) # Pa
|
1366
|
+
fan.autosizeMaximumFlowRate
|
1367
|
+
fan.setMotorEfficiency(0.9)
|
1368
|
+
fan.setMotorInAirstreamFraction(1.0)
|
1369
|
+
# create cooling coil and connect to chilled water plant
|
1370
|
+
cooling_coil = OpenStudio::Model::CoilCoolingWater.new(model, model.alwaysOnDiscreteSchedule)
|
1371
|
+
options['chilled_water_plant'].addDemandBranchForComponent(cooling_coil)
|
1372
|
+
cooling_coil.controllerWaterCoil.get.setMinimumActuatedFlow(0)
|
1373
|
+
# create heating coil and connect to hot water plant
|
1374
|
+
heating_coil = OpenStudio::Model::CoilHeatingWater.new(model, model.alwaysOnDiscreteSchedule)
|
1375
|
+
options['hot_water_plant'].addDemandBranchForComponent(heating_coil)
|
1376
|
+
heating_coil.controllerWaterCoil.get.setMinimumActuatedFlow(0)
|
1377
|
+
# construct fan coil
|
1378
|
+
fan_coil = OpenStudio::Model::ZoneHVACFourPipeFanCoil.new(model,
|
1379
|
+
model.alwaysOnDiscreteSchedule,
|
1380
|
+
fan,
|
1381
|
+
cooling_coil,
|
1382
|
+
heating_coil)
|
1383
|
+
fan_coil.setMaximumOutdoorAirFlowRate(0)
|
1384
|
+
# add fan coil to thermal zone
|
1385
|
+
fan_coil.addToThermalZone(zone)
|
1386
|
+
elsif (options['zoneHVAC'] == 'WSHP') || (options['zoneHVAC'] == 'GSHP')
|
1387
|
+
# create water source heat pump and attach to heat pump loop
|
1388
|
+
# create fan
|
1389
|
+
fan = OpenStudio::Model::FanOnOff.new(model, model.alwaysOnDiscreteSchedule)
|
1390
|
+
fan.setFanEfficiency(0.75)
|
1391
|
+
fan_eff = fan.fanEfficiency
|
1392
|
+
fan.setMotorEfficiency(0.9)
|
1393
|
+
motor_eff = fan.motorEfficiency
|
1394
|
+
fan.autosizeMaximumFlowRate
|
1395
|
+
if parameters['gshpFanType'] == 'PSC' # use 0.3W/cfm, ECM - 0.2W/cfm
|
1396
|
+
watt_per_cfm = 0.30 # W/cfm
|
1397
|
+
else
|
1398
|
+
watt_per_cfm = 0.20 # W/cfm
|
1399
|
+
end
|
1400
|
+
pres_rise = OpenStudio.convert(watt_per_cfm * fan_eff * motor_eff / 0.1175, 'inH_{2}O', 'Pa').get
|
1401
|
+
fan.setPressureRise(pres_rise) # Pa
|
1402
|
+
fan.setMotorInAirstreamFraction(1.0)
|
1403
|
+
# create cooling coil and connect to heat pump loop
|
1404
|
+
cooling_coil = OpenStudio::Model::CoilCoolingWaterToAirHeatPumpEquationFit.new(model)
|
1405
|
+
cooling_coil.setRatedCoolingCoefficientofPerformance(parameters['gshpCoolingEER'] / 3.412) # xf 061014: need to change per fan power and pump power adjustment
|
1406
|
+
cooling_coil.setRatedCoolingCoefficientofPerformance(6.45)
|
1407
|
+
cooling_coil.setTotalCoolingCapacityCoefficient1(-9.149069561)
|
1408
|
+
cooling_coil.setTotalCoolingCapacityCoefficient2(10.87814026)
|
1409
|
+
cooling_coil.setTotalCoolingCapacityCoefficient3(-1.718780157)
|
1410
|
+
cooling_coil.setTotalCoolingCapacityCoefficient4(0.746414818)
|
1411
|
+
cooling_coil.setTotalCoolingCapacityCoefficient5(0.0)
|
1412
|
+
cooling_coil.setSensibleCoolingCapacityCoefficient1(-5.462690012)
|
1413
|
+
cooling_coil.setSensibleCoolingCapacityCoefficient2(17.95968138)
|
1414
|
+
cooling_coil.setSensibleCoolingCapacityCoefficient3(-11.87818402)
|
1415
|
+
cooling_coil.setSensibleCoolingCapacityCoefficient4(-0.980163419)
|
1416
|
+
cooling_coil.setSensibleCoolingCapacityCoefficient5(0.767285761)
|
1417
|
+
cooling_coil.setSensibleCoolingCapacityCoefficient6(0.0)
|
1418
|
+
cooling_coil.setCoolingPowerConsumptionCoefficient1(-3.205409884)
|
1419
|
+
cooling_coil.setCoolingPowerConsumptionCoefficient2(-0.976409399)
|
1420
|
+
cooling_coil.setCoolingPowerConsumptionCoefficient3(3.97892546)
|
1421
|
+
cooling_coil.setCoolingPowerConsumptionCoefficient4(0.938181818)
|
1422
|
+
cooling_coil.setCoolingPowerConsumptionCoefficient5(0.0)
|
1423
|
+
options['heat_pump_loop'].addDemandBranchForComponent(cooling_coil)
|
1424
|
+
# create heating coil and connect to heat pump loop
|
1425
|
+
heating_coil = OpenStudio::Model::CoilHeatingWaterToAirHeatPumpEquationFit.new(model)
|
1426
|
+
heating_coil.setRatedHeatingCoefficientofPerformance(parameters['gshpHeatingCOP']) # xf 061014: need to change per fan power and pump power adjustment
|
1427
|
+
heating_coil.setRatedHeatingCoefficientofPerformance(4.0)
|
1428
|
+
heating_coil.setHeatingCapacityCoefficient1(-1.361311959)
|
1429
|
+
heating_coil.setHeatingCapacityCoefficient2(-2.471798046)
|
1430
|
+
heating_coil.setHeatingCapacityCoefficient3(4.173164514)
|
1431
|
+
heating_coil.setHeatingCapacityCoefficient4(0.640757401)
|
1432
|
+
heating_coil.setHeatingCapacityCoefficient5(0.0)
|
1433
|
+
heating_coil.setHeatingPowerConsumptionCoefficient1(-2.176941116)
|
1434
|
+
heating_coil.setHeatingPowerConsumptionCoefficient2(0.832114286)
|
1435
|
+
heating_coil.setHeatingPowerConsumptionCoefficient3(1.570743399)
|
1436
|
+
heating_coil.setHeatingPowerConsumptionCoefficient4(0.690793651)
|
1437
|
+
heating_coil.setHeatingPowerConsumptionCoefficient5(0.0)
|
1438
|
+
options['heat_pump_loop'].addDemandBranchForComponent(heating_coil)
|
1439
|
+
# create supplemental heating coil
|
1440
|
+
supplemental_heating_coil = OpenStudio::Model::CoilHeatingElectric.new(model, model.alwaysOnDiscreteSchedule)
|
1441
|
+
# construct heat pump
|
1442
|
+
heat_pump = OpenStudio::Model::ZoneHVACWaterToAirHeatPump.new(model,
|
1443
|
+
model.alwaysOnDiscreteSchedule,
|
1444
|
+
fan,
|
1445
|
+
heating_coil,
|
1446
|
+
cooling_coil,
|
1447
|
+
supplemental_heating_coil)
|
1448
|
+
heat_pump.setSupplyAirFlowRateWhenNoCoolingorHeatingisNeeded(OpenStudio::OptionalDouble.new(0))
|
1449
|
+
heat_pump.setOutdoorAirFlowRateDuringCoolingOperation(OpenStudio::OptionalDouble.new(0))
|
1450
|
+
heat_pump.setOutdoorAirFlowRateDuringHeatingOperation(OpenStudio::OptionalDouble.new(0))
|
1451
|
+
heat_pump.setOutdoorAirFlowRateWhenNoCoolingorHeatingisNeeded(OpenStudio::OptionalDouble.new(0))
|
1452
|
+
# add heat pump to thermal zone
|
1453
|
+
heat_pump.addToThermalZone(zone)
|
1454
|
+
elsif options['zoneHVAC'] == 'ASHP'
|
1455
|
+
# create air source heat pump
|
1456
|
+
# create fan
|
1457
|
+
fan = OpenStudio::Model::FanOnOff.new(model, model.alwaysOnDiscreteSchedule)
|
1458
|
+
fan.setFanEfficiency(0.5)
|
1459
|
+
fan.setPressureRise(75) # Pa
|
1460
|
+
fan.autosizeMaximumFlowRate
|
1461
|
+
fan.setMotorEfficiency(0.9)
|
1462
|
+
fan.setMotorInAirstreamFraction(1.0)
|
1463
|
+
# create heating coil
|
1464
|
+
# create htgCapFuncTempCurve
|
1465
|
+
htgCapFuncTempCurve = OpenStudio::Model::CurveCubic.new(model)
|
1466
|
+
htgCapFuncTempCurve.setCoefficient1Constant(0.758746)
|
1467
|
+
htgCapFuncTempCurve.setCoefficient2x(0.027626)
|
1468
|
+
htgCapFuncTempCurve.setCoefficient3xPOW2(0.000148716)
|
1469
|
+
htgCapFuncTempCurve.setCoefficient4xPOW3(0.0000034992)
|
1470
|
+
htgCapFuncTempCurve.setMinimumValueofx(-20)
|
1471
|
+
htgCapFuncTempCurve.setMaximumValueofx(20)
|
1472
|
+
# create htgCapFuncFlowFracCurve
|
1473
|
+
htgCapFuncFlowFracCurve = OpenStudio::Model::CurveCubic.new(model)
|
1474
|
+
htgCapFuncFlowFracCurve.setCoefficient1Constant(0.84)
|
1475
|
+
htgCapFuncFlowFracCurve.setCoefficient2x(0.16)
|
1476
|
+
htgCapFuncFlowFracCurve.setCoefficient3xPOW2(0)
|
1477
|
+
htgCapFuncFlowFracCurve.setCoefficient4xPOW3(0)
|
1478
|
+
htgCapFuncFlowFracCurve.setMinimumValueofx(0.5)
|
1479
|
+
htgCapFuncFlowFracCurve.setMaximumValueofx(1.5)
|
1480
|
+
# create htgEirFuncTempCurve
|
1481
|
+
htgEirFuncTempCurve = OpenStudio::Model::CurveCubic.new(model)
|
1482
|
+
htgEirFuncTempCurve.setCoefficient1Constant(1.19248)
|
1483
|
+
htgEirFuncTempCurve.setCoefficient2x(-0.0300438)
|
1484
|
+
htgEirFuncTempCurve.setCoefficient3xPOW2(0.00103745)
|
1485
|
+
htgEirFuncTempCurve.setCoefficient4xPOW3(-0.000023328)
|
1486
|
+
htgEirFuncTempCurve.setMinimumValueofx(-20)
|
1487
|
+
htgEirFuncTempCurve.setMaximumValueofx(20)
|
1488
|
+
# create htgEirFuncFlowFracCurve
|
1489
|
+
htgEirFuncFlowFracCurve = OpenStudio::Model::CurveQuadratic.new(model)
|
1490
|
+
htgEirFuncFlowFracCurve.setCoefficient1Constant(1.3824)
|
1491
|
+
htgEirFuncFlowFracCurve.setCoefficient2x(-0.4336)
|
1492
|
+
htgEirFuncFlowFracCurve.setCoefficient3xPOW2(0.0512)
|
1493
|
+
htgEirFuncFlowFracCurve.setMinimumValueofx(0)
|
1494
|
+
htgEirFuncFlowFracCurve.setMaximumValueofx(1)
|
1495
|
+
# create htgPlrCurve
|
1496
|
+
htgPlrCurve = OpenStudio::Model::CurveQuadratic.new(model)
|
1497
|
+
htgPlrCurve.setCoefficient1Constant(0.75)
|
1498
|
+
htgPlrCurve.setCoefficient2x(0.25)
|
1499
|
+
htgPlrCurve.setCoefficient3xPOW2(0.0)
|
1500
|
+
htgPlrCurve.setMinimumValueofx(0.0)
|
1501
|
+
htgPlrCurve.setMaximumValueofx(1.0)
|
1502
|
+
# heating coil
|
1503
|
+
heating_coil = OpenStudio::Model::CoilHeatingDXSingleSpeed.new(model,
|
1504
|
+
model.alwaysOnDiscreteSchedule,
|
1505
|
+
htgCapFuncTempCurve,
|
1506
|
+
htgCapFuncFlowFracCurve,
|
1507
|
+
htgEirFuncTempCurve,
|
1508
|
+
htgEirFuncFlowFracCurve,
|
1509
|
+
htgPlrCurve)
|
1510
|
+
heating_coil.setRatedCOP(3.4)
|
1511
|
+
heating_coil.setCrankcaseHeaterCapacity(200)
|
1512
|
+
heating_coil.setMaximumOutdoorDryBulbTemperatureforCrankcaseHeaterOperation(8)
|
1513
|
+
heating_coil.autosizeResistiveDefrostHeaterCapacity
|
1514
|
+
# create cooling coil
|
1515
|
+
# create clgCapFuncTempCurve
|
1516
|
+
clgCapFuncTempCurve = OpenStudio::Model::CurveBiquadratic.new(model)
|
1517
|
+
clgCapFuncTempCurve.setCoefficient1Constant(0.942587793)
|
1518
|
+
clgCapFuncTempCurve.setCoefficient2x(0.009543347)
|
1519
|
+
clgCapFuncTempCurve.setCoefficient3xPOW2(0.0018423)
|
1520
|
+
clgCapFuncTempCurve.setCoefficient4y(-0.011042676)
|
1521
|
+
clgCapFuncTempCurve.setCoefficient5yPOW2(0.000005249)
|
1522
|
+
clgCapFuncTempCurve.setCoefficient6xTIMESY(-0.000009720)
|
1523
|
+
clgCapFuncTempCurve.setMinimumValueofx(17)
|
1524
|
+
clgCapFuncTempCurve.setMaximumValueofx(22)
|
1525
|
+
clgCapFuncTempCurve.setMinimumValueofy(13)
|
1526
|
+
clgCapFuncTempCurve.setMaximumValueofy(46)
|
1527
|
+
# create clgCapFuncFlowFracCurve
|
1528
|
+
clgCapFuncFlowFracCurve = OpenStudio::Model::CurveQuadratic.new(model)
|
1529
|
+
clgCapFuncFlowFracCurve.setCoefficient1Constant(0.718954)
|
1530
|
+
clgCapFuncFlowFracCurve.setCoefficient2x(0.435436)
|
1531
|
+
clgCapFuncFlowFracCurve.setCoefficient3xPOW2(-0.154193)
|
1532
|
+
clgCapFuncFlowFracCurve.setMinimumValueofx(0.75)
|
1533
|
+
clgCapFuncFlowFracCurve.setMaximumValueofx(1.25)
|
1534
|
+
# create clgEirFuncTempCurve
|
1535
|
+
clgEirFuncTempCurve = OpenStudio::Model::CurveBiquadratic.new(model)
|
1536
|
+
clgEirFuncTempCurve.setCoefficient1Constant(0.342414409)
|
1537
|
+
clgEirFuncTempCurve.setCoefficient2x(0.034885008)
|
1538
|
+
clgEirFuncTempCurve.setCoefficient3xPOW2(-0.000623700)
|
1539
|
+
clgEirFuncTempCurve.setCoefficient4y(0.004977216)
|
1540
|
+
clgEirFuncTempCurve.setCoefficient5yPOW2(0.000437951)
|
1541
|
+
clgEirFuncTempCurve.setCoefficient6xTIMESY(-0.000728028)
|
1542
|
+
clgEirFuncTempCurve.setMinimumValueofx(17)
|
1543
|
+
clgEirFuncTempCurve.setMaximumValueofx(22)
|
1544
|
+
clgEirFuncTempCurve.setMinimumValueofy(13)
|
1545
|
+
clgEirFuncTempCurve.setMaximumValueofy(46)
|
1546
|
+
# create clgEirFuncFlowFracCurve
|
1547
|
+
clgEirFuncFlowFracCurve = OpenStudio::Model::CurveQuadratic.new(model)
|
1548
|
+
clgEirFuncFlowFracCurve.setCoefficient1Constant(1.1552)
|
1549
|
+
clgEirFuncFlowFracCurve.setCoefficient2x(-0.1808)
|
1550
|
+
clgEirFuncFlowFracCurve.setCoefficient3xPOW2(0.0256)
|
1551
|
+
clgEirFuncFlowFracCurve.setMinimumValueofx(0.5)
|
1552
|
+
clgEirFuncFlowFracCurve.setMaximumValueofx(1.5)
|
1553
|
+
# create clgPlrCurve
|
1554
|
+
clgPlrCurve = OpenStudio::Model::CurveQuadratic.new(model)
|
1555
|
+
clgPlrCurve.setCoefficient1Constant(0.75)
|
1556
|
+
clgPlrCurve.setCoefficient2x(0.25)
|
1557
|
+
clgPlrCurve.setCoefficient3xPOW2(0.0)
|
1558
|
+
clgPlrCurve.setMinimumValueofx(0.0)
|
1559
|
+
clgPlrCurve.setMaximumValueofx(1.0)
|
1560
|
+
# cooling coil
|
1561
|
+
cooling_coil = OpenStudio::Model::CoilCoolingDXSingleSpeed.new(model,
|
1562
|
+
model.alwaysOnDiscreteSchedule,
|
1563
|
+
clgCapFuncTempCurve,
|
1564
|
+
clgCapFuncFlowFracCurve,
|
1565
|
+
clgEirFuncTempCurve,
|
1566
|
+
clgEirFuncFlowFracCurve,
|
1567
|
+
clgPlrCurve)
|
1568
|
+
cooling_coil.setRatedCOP(OpenStudio::OptionalDouble.new(4))
|
1569
|
+
# create supplemental heating coil
|
1570
|
+
supplemental_heating_coil = OpenStudio::Model::CoilHeatingElectric.new(model, model.alwaysOnDiscreteSchedule)
|
1571
|
+
# construct heat pump
|
1572
|
+
heat_pump = OpenStudio::Model::ZoneHVACPackagedTerminalHeatPump.new(model,
|
1573
|
+
model.alwaysOnDiscreteSchedule,
|
1574
|
+
fan,
|
1575
|
+
heating_coil,
|
1576
|
+
cooling_coil,
|
1577
|
+
supplemental_heating_coil)
|
1578
|
+
heat_pump.setSupplyAirFlowRateWhenNoCoolingorHeatingisNeeded(0)
|
1579
|
+
heat_pump.setOutdoorAirFlowRateDuringCoolingOperation(0)
|
1580
|
+
heat_pump.setOutdoorAirFlowRateDuringHeatingOperation(0)
|
1581
|
+
heat_pump.setOutdoorAirFlowRateWhenNoCoolingorHeatingisNeeded(0)
|
1582
|
+
# add heat pump to thermal zone
|
1583
|
+
heat_pump.addToThermalZone(zone)
|
1584
|
+
elsif options['zoneHVAC'] == 'Baseboard'
|
1585
|
+
# create baseboard heater add add to thermal zone and hot water loop
|
1586
|
+
baseboard_coil = OpenStudio::Model::CoilHeatingWaterBaseboard.new(model)
|
1587
|
+
baseboard_heater = OpenStudio::Model::ZoneHVACBaseboardConvectiveWater.new(model, model.alwaysOnDiscreteSchedule, baseboard_coil)
|
1588
|
+
baseboard_heater.addToThermalZone(zone)
|
1589
|
+
options['hot_water_plant'].addDemandBranchForComponent(baseboard_coil)
|
1590
|
+
elsif options['zoneHVAC'] == 'Radiant'
|
1591
|
+
# create low temperature radiant object and add to thermal zone and radiant plant loops
|
1592
|
+
# create hot water coil and attach to radiant hot water loop
|
1593
|
+
heating_coil = OpenStudio::Model::CoilHeatingLowTempRadiantVarFlow.new(model, options['mean_radiant_heating_setpoint_schedule'])
|
1594
|
+
options['radiant_hot_water_plant'].addDemandBranchForComponent(heating_coil)
|
1595
|
+
# create chilled water coil and attach to radiant chilled water loop
|
1596
|
+
cooling_coil = OpenStudio::Model::CoilCoolingLowTempRadiantVarFlow.new(model, options['mean_radiant_cooling_setpoint_schedule'])
|
1597
|
+
options['radiant_chilled_water_plant'].addDemandBranchForComponent(cooling_coil)
|
1598
|
+
low_temp_radiant = OpenStudio::Model::ZoneHVACLowTempRadiantVarFlow.new(model,
|
1599
|
+
model.alwaysOnDiscreteSchedule,
|
1600
|
+
heating_coil,
|
1601
|
+
cooling_coil)
|
1602
|
+
low_temp_radiant.setRadiantSurfaceType('Floors')
|
1603
|
+
low_temp_radiant.setHydronicTubingInsideDiameter(0.012)
|
1604
|
+
low_temp_radiant.setTemperatureControlType('MeanRadiantTemperature')
|
1605
|
+
low_temp_radiant.addToThermalZone(zone)
|
1606
|
+
# create radiant floor construction and substitute for existing floor (interior or exterior) constructions
|
1607
|
+
# create materials for radiant floor construction
|
1608
|
+
layers = []
|
1609
|
+
# ignore layer below insulation, which will depend on boundary condition
|
1610
|
+
layers << rigid_insulation_1in = OpenStudio::Model::StandardOpaqueMaterial.new(model, 'Rough', 0.0254, 0.02, 56.06, 1210)
|
1611
|
+
layers << concrete_2in = OpenStudio::Model::StandardOpaqueMaterial.new(model, 'MediumRough', 0.0508, 2.31, 2322, 832)
|
1612
|
+
layers << concrete_2in
|
1613
|
+
# create radiant floor construction from materials
|
1614
|
+
radiant_floor = OpenStudio::Model::ConstructionWithInternalSource.new(layers)
|
1615
|
+
radiant_floor.setSourcePresentAfterLayerNumber(2)
|
1616
|
+
radiant_floor.setSourcePresentAfterLayerNumber(2)
|
1617
|
+
# assign radiant construction to zone floor
|
1618
|
+
zone.spaces.each do |space|
|
1619
|
+
space.surfaces.each do |surface|
|
1620
|
+
if surface.surfaceType == 'Floor'
|
1621
|
+
surface.setConstruction(radiant_floor)
|
1622
|
+
end
|
1623
|
+
end
|
1624
|
+
end
|
1625
|
+
elsif options['zoneHVAC'] == 'DualDuct'
|
1626
|
+
# create baseboard heater add add to thermal zone and hot water loop
|
1627
|
+
baseboard_coil = OpenStudio::Model::CoilHeatingWaterBaseboard.new(model)
|
1628
|
+
baseboard_heater = OpenStudio::Model::ZoneHVACBaseboardConvectiveWater.new(model, model.alwaysOnDiscreteSchedule, baseboard_coil)
|
1629
|
+
baseboard_heater.addToThermalZone(zone)
|
1630
|
+
options['hot_water_plant'].addDemandBranchForComponent(baseboard_coil)
|
1631
|
+
# create fan coil (to mimic functionality of DOAS)
|
1632
|
+
# variable speed fan
|
1633
|
+
fan = OpenStudio::Model::FanVariableVolume.new(model, model.alwaysOnDiscreteSchedule)
|
1634
|
+
fan.setFanEfficiency(0.69)
|
1635
|
+
fan.setPressureRise(75) # Pa #ML This number is a guess; zone equipment pretending to be a DOAS
|
1636
|
+
fan.autosizeMaximumFlowRate
|
1637
|
+
fan.setFanPowerMinimumFlowFraction(0.6)
|
1638
|
+
fan.setMotorEfficiency(0.9)
|
1639
|
+
fan.setMotorInAirstreamFraction(1.0)
|
1640
|
+
# create chilled water coil and attach to chilled water loop
|
1641
|
+
cooling_coil = OpenStudio::Model::CoilCoolingWater.new(model, model.alwaysOnDiscreteSchedule)
|
1642
|
+
options['chilled_water_plant'].addDemandBranchForComponent(cooling_coil)
|
1643
|
+
cooling_coil.controllerWaterCoil.get.setMinimumActuatedFlow(0)
|
1644
|
+
# create hot water coil and attach to hot water loop
|
1645
|
+
heating_coil = OpenStudio::Model::CoilHeatingWater.new(model, model.alwaysOnDiscreteSchedule)
|
1646
|
+
options['hot_water_plant'].addDemandBranchForComponent(heating_coil)
|
1647
|
+
heating_coil.controllerWaterCoil.get.setMinimumActuatedFlow(0)
|
1648
|
+
# construct fan coil (DOAS) and attach to thermal zone
|
1649
|
+
fan_coil_doas = OpenStudio::Model::ZoneHVACFourPipeFanCoil.new(model,
|
1650
|
+
options['ventilation_schedule'],
|
1651
|
+
fan,
|
1652
|
+
cooling_coil,
|
1653
|
+
heating_coil)
|
1654
|
+
fan_coil_doas.setCapacityControlMethod('VariableFanVariableFlow')
|
1655
|
+
fan_coil_doas.addToThermalZone(zone)
|
1656
|
+
end
|
1657
|
+
end
|
1658
|
+
end
|
1659
|
+
end # end of def
|
1660
|
+
|
1661
|
+
def self.addDCV(model, runner, options)
|
1662
|
+
options['primary_airloops']&.each do |airloop|
|
1663
|
+
if options['allHVAC']['primary']['fan'] == 'Variable'
|
1664
|
+
if airloop.airLoopHVACOutdoorAirSystem.is_initialized
|
1665
|
+
controller_mv = airloop.airLoopHVACOutdoorAirSystem.get.getControllerOutdoorAir.controllerMechanicalVentilation
|
1666
|
+
controller_mv.setDemandControlledVentilation(true)
|
1667
|
+
runner.registerInfo("Enabling demand control ventilation for #{airloop.name}")
|
1668
|
+
end
|
1669
|
+
end
|
1670
|
+
end
|
1671
|
+
|
1672
|
+
options['secondary_airloops']&.each do |airloop|
|
1673
|
+
if options['allHVAC']['secondary']['fan'] == 'Variable'
|
1674
|
+
if airloop.airLoopHVACOutdoorAirSystem.is_initialized
|
1675
|
+
controller_mv = airloop.airLoopHVACOutdoorAirSystem.get.getControllerOutdoorAir.controllerMechanicalVentilation
|
1676
|
+
controller_mv.setDemandControlledVentilation(true)
|
1677
|
+
runner.registerInfo("Enabling demand control ventilation for #{airloop.name}")
|
1678
|
+
end
|
1679
|
+
end
|
1680
|
+
end
|
1681
|
+
end # end of def
|
1682
|
+
end
|