openstudio-ee 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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