openstudio-ee 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/Rakefile +2 -0
  4. data/lib/measures/ImproveFanTotalEfficiencybyPercentage/measure.rb +333 -0
  5. data/lib/measures/ImproveFanTotalEfficiencybyPercentage/measure.xml +150 -0
  6. data/lib/measures/ReplaceFanTotalEfficiency/measure.rb +330 -0
  7. data/lib/measures/ReplaceFanTotalEfficiency/measure.xml +150 -0
  8. data/lib/measures/add_apszhp_to_each_zone/measure.rb +607 -0
  9. data/lib/measures/add_apszhp_to_each_zone/measure.xml +184 -0
  10. data/lib/measures/add_energy_recovery_ventilator/measure.rb +354 -0
  11. data/lib/measures/add_energy_recovery_ventilator/measure.xml +78 -0
  12. data/lib/measures/improve_simple_glazing_by_percentage/measure.rb +81 -0
  13. data/lib/measures/improve_simple_glazing_by_percentage/measure.xml +70 -0
  14. data/lib/measures/reduce_water_use_by_percentage/measure.rb +61 -0
  15. data/lib/measures/reduce_water_use_by_percentage/measure.xml +62 -0
  16. data/lib/measures/replace_hvac_with_gshp_and_doas/measure.rb +511 -0
  17. data/lib/measures/replace_hvac_with_gshp_and_doas/measure.xml +375 -0
  18. data/lib/measures/replace_hvac_with_gshp_and_doas/resources/OsLib_AedgMeasures.rb +454 -0
  19. data/lib/measures/replace_hvac_with_gshp_and_doas/resources/OsLib_Constructions.rb +221 -0
  20. data/lib/measures/replace_hvac_with_gshp_and_doas/resources/OsLib_Geometry.rb +41 -0
  21. data/lib/measures/replace_hvac_with_gshp_and_doas/resources/OsLib_HVAC.rb +1682 -0
  22. data/lib/measures/replace_hvac_with_gshp_and_doas/resources/OsLib_HelperMethods.rb +114 -0
  23. data/lib/measures/replace_hvac_with_gshp_and_doas/resources/OsLib_LightingAndEquipment.rb +99 -0
  24. data/lib/measures/replace_hvac_with_gshp_and_doas/resources/OsLib_Schedules.rb +142 -0
  25. data/lib/measures/replace_simple_glazing/measure.rb +86 -0
  26. data/lib/measures/replace_simple_glazing/measure.xml +78 -0
  27. data/lib/measures/set_boiler_thermal_efficiency/measure.rb +520 -0
  28. data/lib/measures/set_boiler_thermal_efficiency/measure.xml +78 -0
  29. data/lib/measures/set_water_heater_efficiency_heat_lossand_peak_water_flow_rate/measure.rb +207 -0
  30. data/lib/measures/set_water_heater_efficiency_heat_lossand_peak_water_flow_rate/measure.xml +78 -0
  31. data/lib/measures/tenant_star_internal_loads/measure.rb +134 -0
  32. data/lib/measures/tenant_star_internal_loads/measure.xml +67 -0
  33. data/lib/measures/tenant_star_internal_loads/resources/os_lib_helper_methods.rb +401 -0
  34. data/lib/measures/vr_fwith_doas/measure.rb +468 -0
  35. data/lib/measures/vr_fwith_doas/measure.xml +298 -0
  36. data/lib/measures/vr_fwith_doas/resources/OsLib_AedgMeasures.rb +454 -0
  37. data/lib/measures/vr_fwith_doas/resources/OsLib_Constructions.rb +221 -0
  38. data/lib/measures/vr_fwith_doas/resources/OsLib_Geometry.rb +41 -0
  39. data/lib/measures/vr_fwith_doas/resources/OsLib_HVAC.rb +1516 -0
  40. data/lib/measures/vr_fwith_doas/resources/OsLib_HelperMethods.rb +114 -0
  41. data/lib/measures/vr_fwith_doas/resources/OsLib_LightingAndEquipment.rb +99 -0
  42. data/lib/measures/vr_fwith_doas/resources/OsLib_Schedules.rb +142 -0
  43. data/lib/openstudio/ee_measures/version.rb +1 -1
  44. data/openstudio-ee.gemspec +7 -5
  45. metadata +48 -9
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OsLib_Constructions
4
+ # infer insulation layer from a construction
5
+ def self.inferInsulationLayer(construction, minThermalResistance)
6
+ construction_layers = construction.layers
7
+ counter = 0
8
+ max_resistance = 0
9
+ thermal_resistance_array = []
10
+
11
+ # loop through construction layers and infer insulation layer/material
12
+ construction_layers.each do |construction_layer|
13
+ construction_thermal_resistance = construction_layer.to_OpaqueMaterial.get.thermalResistance
14
+ if !thermal_resistance_array.empty?
15
+ if construction_thermal_resistance > max_resistance
16
+ thermal_resistance_array = [construction_layer, counter, construction_thermal_resistance]
17
+ max_resistance = construction_thermal_resistance
18
+ end
19
+ else
20
+ thermal_resistance_array = [construction_layer, counter, construction_thermal_resistance]
21
+ end
22
+ counter += 1
23
+ end
24
+
25
+ # test against minimum
26
+ if max_resistance > minThermalResistance
27
+ minTestPass = true
28
+ else
29
+ minTestPass = false
30
+ end
31
+
32
+ result = {
33
+ 'construction' => construction,
34
+ 'construction_layer' => thermal_resistance_array[0],
35
+ 'layer_index' => thermal_resistance_array[1],
36
+ 'construction_thermal_resistance' => thermal_resistance_array[2],
37
+ 'insulationFound' => minTestPass
38
+ }
39
+
40
+ return result
41
+ end
42
+
43
+ # change thermal resistance of opaque materials
44
+ def self.setMaterialThermalResistance(material, thermalResistance, options = {})
45
+ # set defaults to use if user inputs not passed in
46
+ defaults = {
47
+ 'cloneMaterial' => true, # in future give user option to clone or change live material
48
+ 'name' => "#{material.name} - adj"
49
+ }
50
+
51
+ # merge user inputs with defaults
52
+ options = defaults.merge(options)
53
+
54
+ # clone input material
55
+ new_material = material.clone(material.model)
56
+ new_material = new_material.to_OpaqueMaterial.get
57
+ new_material.setName(options['name'])
58
+
59
+ # edit insulation material
60
+ new_material_matt = new_material.to_Material
61
+ if !new_material_matt.empty?
62
+ starting_thickness = new_material_matt.get.thickness
63
+ target_thickness = starting_thickness * thermalResistance / material.to_OpaqueMaterial.get.thermalResistance
64
+ final_thickness = new_material_matt.get.setThickness(target_thickness)
65
+ end
66
+ new_material_massless = new_material.to_MasslessOpaqueMaterial
67
+ if !new_material_massless.empty?
68
+ final_thermal_resistance = new_material_massless.get.setThermalResistance(thermalResistance)
69
+ end
70
+ new_material_airgap = new_material.to_AirGap
71
+ if !new_material_airgap.empty?
72
+ final_thermal_resistance = new_material_airgap.get.setThermalResistance(thermalResistance)
73
+ end
74
+
75
+ result = new_material
76
+ return result
77
+ end
78
+
79
+ # add new material to outside of a construction
80
+ def self.addNewLayerToConstruction(construction, options = {})
81
+ # set defaults to use if user inputs not passed in
82
+ defaults = {
83
+ 'cloneConstruction' => false, # in future give user option to clone or change live construction
84
+ 'layerIndex' => 0, # 0 will be outside. Measure writer should validate any non 0 layerIndex passed in.
85
+ 'name' => "#{construction.name} - new material",
86
+ 'roughness' => nil,
87
+ 'thickness' => nil,
88
+ 'conductivity' => nil,
89
+ 'density' => nil,
90
+ 'specificHeat' => nil,
91
+ 'thermalAbsorptance' => nil,
92
+ 'solarAbsorptance' => nil,
93
+ 'visibleAbsorptance' => nil
94
+ }
95
+
96
+ # merge user inputs with defaults
97
+ options = defaults.merge(options)
98
+
99
+ # TODO: - would be nice to grab surface properties from previous exposed material
100
+
101
+ # make new material
102
+ exposedMaterialNew = OpenStudio::Model::StandardOpaqueMaterial.new(construction.model)
103
+ exposedMaterialNew.setName(options['name'])
104
+
105
+ # set requested material properties
106
+ if !options['roughness'].nil? then exposedMaterialNew.setRoughness(options['roughness']) end
107
+ if !options['thickness'].nil? then exposedMaterialNew.setThickness(options['thickness']) end
108
+ if !options['conductivity'].nil? then exposedMaterialNew.setConductivity(options['conductivity']) end
109
+ if !options['density'].nil? then exposedMaterialNew.setDensity(options['density']) end
110
+ if !options['specificHeat'].nil? then exposedMaterialNew.setSpecificHeat(options['specificHeat']) end
111
+ if !options['thermalAbsorptance'].nil? then exposedMaterialNew.setThermalAbsorptance(options['thermalAbsorptance']) end
112
+ if !options['solarAbsorptance'].nil? then exposedMaterialNew.setSolarAbsorptance(options['solarAbsorptance']) end
113
+ if !options['visibleAbsorptance'].nil? then exposedMaterialNew.setVisibleAbsorptance(options['visibleAbsorptance']) end
114
+
115
+ # add material to construction
116
+ construction.insertLayer(options['layerIndex'], exposedMaterialNew)
117
+
118
+ result = exposedMaterialNew
119
+ return result
120
+ end
121
+
122
+ # set material surface properties for specific layer in construction. this should work on OS:Material and OS:MasslessOpaqueMaterial
123
+ def self.setConstructionSurfaceProperties(construction, options = {})
124
+ # set defaults to use if user inputs not passed in
125
+ defaults = {
126
+ 'cloneConstruction' => false, # in future give user option to clone or change live construction
127
+ 'nameConstruction' => "#{construction.name} - adj", # not currently used
128
+ 'cloneMaterial' => true,
129
+ 'roughness' => nil,
130
+ 'thermalAbsorptance' => nil,
131
+ 'solarAbsorptance' => nil,
132
+ 'visibleAbsorptance' => nil
133
+ }
134
+
135
+ # merge user inputs with defaults
136
+ options = defaults.merge(options)
137
+
138
+ exposedMaterial = construction.to_LayeredConstruction.get.getLayer(0)
139
+ if options['cloneMaterial']
140
+
141
+ # clone material
142
+ exposedMaterialNew = exposedMaterial.clone(construction.model).to_StandardOpaqueMaterial.get # to_StandardOpaqueMaterial is needed to access roughness, otherwise to_OpaqueMaterial would have been fine.
143
+ exposedMaterialNew.setName("#{exposedMaterial.name} - adj")
144
+
145
+ # connect new material to original construction
146
+ construction.eraseLayer(0)
147
+ construction.insertLayer(0, exposedMaterialNew)
148
+
149
+ else
150
+ exposedMaterialNew = exposedMaterial.to_StandardOpaqueMaterial.get # not being cloned but still want to rename
151
+ exposedMaterialNew.setName("#{exposedMaterial.name} - adj")
152
+ end
153
+
154
+ # TODO: - need to test with MasslessOpaqueMaterial. Add test if doesn't return anything when use to_StandardOpaqueMaterial.get
155
+
156
+ # set requested material properties
157
+ if !options['roughness'].nil? then exposedMaterialNew.setRoughness(options['roughness']) end
158
+ if !options['thermalAbsorptance'].nil? then exposedMaterialNew.setThermalAbsorptance(options['thermalAbsorptance']) end
159
+ if !options['solarAbsorptance'].nil? then exposedMaterialNew.setSolarAbsorptance(options['solarAbsorptance']) end
160
+ if !options['visibleAbsorptance'].nil? then exposedMaterialNew.setVisibleAbsorptance(options['visibleAbsorptance']) end
161
+
162
+ result = { 'exposedMaterial' => exposedMaterial, 'exposedMaterialNew' => exposedMaterialNew }
163
+ return result
164
+ end # end of OsLib_Constructions.setMaterialSurfaceProperties
165
+
166
+ # similar to setMaterialSurfaceProperties but I just pass a material in. Needed this to set material properties for interior walls and both sides of interior partitions.
167
+ def self.setMaterialSurfaceProperties(material, options = {})
168
+ # set defaults to use if user inputs not passed in
169
+ defaults = {
170
+ 'cloneMaterial' => true,
171
+ 'roughness' => nil,
172
+ 'thermalAbsorptance' => nil,
173
+ 'solarAbsorptance' => nil,
174
+ 'visibleAbsorptance' => nil
175
+ }
176
+
177
+ # merge user inputs with defaults
178
+ options = defaults.merge(options)
179
+
180
+ if options['cloneMaterial']
181
+ # clone material
182
+ materialNew = exposedMaterial.clone(construction.model).get
183
+ materialNew.setName("#{materialNew.name} - adj")
184
+ else
185
+ materialNew = material # not being cloned
186
+ materialNew.setName("#{materialNew.name} - adj")
187
+ end
188
+
189
+ # to_StandardOpaqueMaterial is needed to access roughness, otherwise to_OpaqueMaterial would have been fine.
190
+ if !materialNew.to_StandardOpaqueMaterial.empty?
191
+ materialNew = materialNew.to_StandardOpaqueMaterial.get
192
+ elsif !materialNew.to_MasslessOpaqueMaterial.empty?
193
+ materialNew = materialNew.to_MasslessOpaqueMaterial.get
194
+ end
195
+
196
+ # set requested material properties
197
+ if !options['roughness'].nil? then materialNew.setRoughness(options['roughness']) end
198
+ if !options['thermalAbsorptance'].nil? then materialNew.setThermalAbsorptance(options['thermalAbsorptance']) end
199
+ if !options['solarAbsorptance'].nil? then materialNew.setSolarAbsorptance(options['solarAbsorptance']) end
200
+ if !options['visibleAbsorptance'].nil? then materialNew.setVisibleAbsorptance(options['visibleAbsorptance']) end
201
+
202
+ result = { 'material' => material, 'materialNew' => materialNew }
203
+ return result
204
+ end # end of OsLib_Constructions.setMaterialSurfaceProperties
205
+
206
+ # sri of exposed surface of a construction (calculation from K-12 AEDG, derived from ASTM E1980 assuming medium wind speed)
207
+ def self.getConstructionSRI(construction)
208
+ exposedMaterial = construction.to_LayeredConstruction.get.getLayer(0)
209
+ solarAbsorptance = exposedMaterial.to_OpaqueMaterial.get.solarAbsorptance
210
+ thermalEmissivity = exposedMaterial.to_OpaqueMaterial.get.thermalAbsorptance
211
+ # lines below just for testing
212
+ # solarAbsorptance = 1 - 0.65
213
+ # thermalEmissivity = 0.86
214
+
215
+ x = (20.797 * solarAbsorptance - 0.603 * thermalEmissivity) / (9.5205 * thermalEmissivity + 12.0)
216
+ sri = 123.97 - 141.35 * x + 9.6555 * x * x
217
+
218
+ result = sri
219
+ return result
220
+ end # end of OsLib_Constructions.getConstructionSRI
221
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OsLib_Geometry
4
+ # lower z value of vertices with starting value above x to new value of y
5
+ def self.lowerSurfaceZvalue(surfaceArray, zValueTarget)
6
+ counter = 0
7
+
8
+ # loop over all surfaces
9
+ surfaceArray.each do |surface|
10
+ # create a new set of vertices
11
+ newVertices = OpenStudio::Point3dVector.new
12
+
13
+ # get the existing vertices for this interior partition
14
+ vertices = surface.vertices
15
+ flag = false
16
+ vertices.each do |vertex|
17
+ # initialize new vertex to old vertex
18
+ x = vertex.x
19
+ y = vertex.y
20
+ z = vertex.z
21
+
22
+ # if this z vertex is not on the z = 0 plane
23
+ if z > zValueTarget
24
+ z = zValueTarget
25
+ flag = true
26
+ end
27
+
28
+ # add point to new vertices
29
+ newVertices << OpenStudio::Point3d.new(x, y, z)
30
+ end
31
+
32
+ # set vertices to new vertices
33
+ surface.setVertices(newVertices) # todo check if this was made, and issue warning if it was not. Could happen if resulting surface not planer.
34
+
35
+ if flag then counter += 1 end
36
+ end # end of surfaceArray.each do
37
+
38
+ result = counter
39
+ return result
40
+ end
41
+ end
@@ -0,0 +1,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