openstudio-ee 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/Rakefile +2 -0
  4. data/lib/measures/ImproveFanTotalEfficiencybyPercentage/measure.rb +333 -0
  5. data/lib/measures/ImproveFanTotalEfficiencybyPercentage/measure.xml +150 -0
  6. data/lib/measures/ReplaceFanTotalEfficiency/measure.rb +330 -0
  7. data/lib/measures/ReplaceFanTotalEfficiency/measure.xml +150 -0
  8. data/lib/measures/add_apszhp_to_each_zone/measure.rb +607 -0
  9. data/lib/measures/add_apszhp_to_each_zone/measure.xml +184 -0
  10. data/lib/measures/add_energy_recovery_ventilator/measure.rb +354 -0
  11. data/lib/measures/add_energy_recovery_ventilator/measure.xml +78 -0
  12. data/lib/measures/improve_simple_glazing_by_percentage/measure.rb +81 -0
  13. data/lib/measures/improve_simple_glazing_by_percentage/measure.xml +70 -0
  14. data/lib/measures/reduce_water_use_by_percentage/measure.rb +61 -0
  15. data/lib/measures/reduce_water_use_by_percentage/measure.xml +62 -0
  16. data/lib/measures/replace_hvac_with_gshp_and_doas/measure.rb +511 -0
  17. data/lib/measures/replace_hvac_with_gshp_and_doas/measure.xml +375 -0
  18. data/lib/measures/replace_hvac_with_gshp_and_doas/resources/OsLib_AedgMeasures.rb +454 -0
  19. data/lib/measures/replace_hvac_with_gshp_and_doas/resources/OsLib_Constructions.rb +221 -0
  20. data/lib/measures/replace_hvac_with_gshp_and_doas/resources/OsLib_Geometry.rb +41 -0
  21. data/lib/measures/replace_hvac_with_gshp_and_doas/resources/OsLib_HVAC.rb +1682 -0
  22. data/lib/measures/replace_hvac_with_gshp_and_doas/resources/OsLib_HelperMethods.rb +114 -0
  23. data/lib/measures/replace_hvac_with_gshp_and_doas/resources/OsLib_LightingAndEquipment.rb +99 -0
  24. data/lib/measures/replace_hvac_with_gshp_and_doas/resources/OsLib_Schedules.rb +142 -0
  25. data/lib/measures/replace_simple_glazing/measure.rb +86 -0
  26. data/lib/measures/replace_simple_glazing/measure.xml +78 -0
  27. data/lib/measures/set_boiler_thermal_efficiency/measure.rb +520 -0
  28. data/lib/measures/set_boiler_thermal_efficiency/measure.xml +78 -0
  29. data/lib/measures/set_water_heater_efficiency_heat_lossand_peak_water_flow_rate/measure.rb +207 -0
  30. data/lib/measures/set_water_heater_efficiency_heat_lossand_peak_water_flow_rate/measure.xml +78 -0
  31. data/lib/measures/tenant_star_internal_loads/measure.rb +134 -0
  32. data/lib/measures/tenant_star_internal_loads/measure.xml +67 -0
  33. data/lib/measures/tenant_star_internal_loads/resources/os_lib_helper_methods.rb +401 -0
  34. data/lib/measures/vr_fwith_doas/measure.rb +468 -0
  35. data/lib/measures/vr_fwith_doas/measure.xml +298 -0
  36. data/lib/measures/vr_fwith_doas/resources/OsLib_AedgMeasures.rb +454 -0
  37. data/lib/measures/vr_fwith_doas/resources/OsLib_Constructions.rb +221 -0
  38. data/lib/measures/vr_fwith_doas/resources/OsLib_Geometry.rb +41 -0
  39. data/lib/measures/vr_fwith_doas/resources/OsLib_HVAC.rb +1516 -0
  40. data/lib/measures/vr_fwith_doas/resources/OsLib_HelperMethods.rb +114 -0
  41. data/lib/measures/vr_fwith_doas/resources/OsLib_LightingAndEquipment.rb +99 -0
  42. data/lib/measures/vr_fwith_doas/resources/OsLib_Schedules.rb +142 -0
  43. data/lib/openstudio/ee_measures/version.rb +1 -1
  44. data/openstudio-ee.gemspec +7 -5
  45. 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