openstudio-ee 0.2.0 → 0.2.1

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