openstudio-standards 0.2.8 → 0.2.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (22) hide show
  1. checksums.yaml +4 -4
  2. data/data/geometry/ASHRAEPrimarySchool.osm +36 -2
  3. data/data/geometry/ASHRAESecondarySchool.osm +19 -2
  4. data/data/standards/OpenStudio_Standards_elevators.json +10756 -0
  5. data/lib/openstudio-standards.rb +0 -2
  6. data/lib/openstudio-standards/hvac_sizing/Siz.HVACComponent.rb +36 -0
  7. data/lib/openstudio-standards/hvac_sizing/Siz.Model.rb +3 -0
  8. data/lib/openstudio-standards/prototypes/common/objects/Prototype.Model.elevators.rb +175 -164
  9. data/lib/openstudio-standards/prototypes/common/objects/Prototype.Model.swh.rb +268 -476
  10. data/lib/openstudio-standards/prototypes/common/objects/Prototype.ServiceWaterHeating.rb +625 -116
  11. data/lib/openstudio-standards/prototypes/common/objects/Prototype.hvac_systems.rb +4 -0
  12. data/lib/openstudio-standards/standards/Standards.AirLoopHVAC.rb +2 -19
  13. data/lib/openstudio-standards/standards/Standards.ThermalZone.rb +112 -68
  14. data/lib/openstudio-standards/standards/Standards.WaterHeaterMixed.rb +10 -2
  15. data/lib/openstudio-standards/standards/necb/necb_2011/data/space_types.json +224 -224
  16. data/lib/openstudio-standards/standards/necb/necb_2011/service_water_heating.rb +8 -16
  17. data/lib/openstudio-standards/standards/necb/necb_2015/data/space_types.json +318 -318
  18. data/lib/openstudio-standards/standards/standard.rb +1 -0
  19. data/lib/openstudio-standards/version.rb +1 -1
  20. metadata +4 -4
  21. data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_1980_2004/doe_ref_1980_2004.hvac_systems.rb +0 -15
  22. data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_pre_1980/doe_ref_pre_1980.hvac_systems.rb +0 -15
@@ -5,18 +5,21 @@ class Standard
5
5
  #
6
6
  # @param system_name [String] the name of the system, or nil in which case it will be defaulted
7
7
  # @param water_heater_thermal_zone [OpenStudio::Model::ThermalZone]
8
- # zones to place water heater in. If nil, will be assumed in 70F air for heat loss.
8
+ # zones to place water heater in. If nil, will be assumed in 70F air for heat loss.
9
9
  # @param service_water_temperature [Double] service water temperature, in C
10
10
  # @param service_water_pump_head [Double] service water pump head, in Pa
11
- # @param service_water_pump_motor_efficiency [Double]
12
- # service water pump motor efficiency, as decimal.
11
+ # @param service_water_pump_motor_efficiency [Double] service water pump motor efficiency, as decimal.
13
12
  # @param water_heater_capacity [Double] water heater heating capacity, in W
14
13
  # @param water_heater_volume [Double] water heater volume, in m^3
15
- # @param water_heater_fuel [String] water heater fuel.
16
- # Valid choices are Natural Gas, Electricity
17
- # @param parasitic_fuel_consumption_rate [Double] the parasitic fuel consumption
18
- # rate of the water heater, in W
19
- # @param building_type [String] the building type
14
+ # @param water_heater_fuel [String] water heater fuel. Valid choices are NaturalGas, Electricity
15
+ # @param parasitic_fuel_consumption_rate [Double] the parasitic fuel consumption rate of the water heater, in W
16
+ # @param add_pipe_losses [Bool] if true, add piping and associated heat losses to system. If false, add no pipe heat losses
17
+ # @param floor_area_served [Double] area served by the SWH loop, in m^2. Used for pipe loss piping length estimation
18
+ # @param number_of_stories [Integer] number of stories served by the SWH loop. Used for pipe loss piping length estimation
19
+ # @param pipe_insulation_thickness [Double] thickness of the fiberglass batt pipe insulation, in m. Use 0 for uninsulated pipes
20
+ # @param number_water_heaters [Double] the number of water heaters represented by the capacity and volume inputs.
21
+ # Used to modify efficiencies for water heaters based on individual component size while avoiding having to model
22
+ # lots of individual water heaters (for runtime sake).
20
23
  # @return [OpenStudio::Model::PlantLoop]
21
24
  # the resulting service water loop.
22
25
  def model_add_swh_loop(model,
@@ -29,14 +32,17 @@ class Standard
29
32
  water_heater_volume,
30
33
  water_heater_fuel,
31
34
  parasitic_fuel_consumption_rate,
32
- building_type = nil)
33
-
34
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', 'Adding service water loop')
35
+ add_pipe_losses = false,
36
+ floor_area_served = 465,
37
+ number_of_stories = 1,
38
+ pipe_insulation_thickness = 0.0127, # 1/2in
39
+ number_water_heaters = 1)
40
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.Model.Model', "In model_add_swh_loop, number_water_heaters = #{number_water_heaters}")
35
41
 
36
42
  # Service water heating loop
37
43
  service_water_loop = OpenStudio::Model::PlantLoop.new(model)
38
- service_water_loop.setMinimumLoopTemperature(10)
39
- service_water_loop.setMaximumLoopTemperature(60)
44
+ service_water_loop.setMinimumLoopTemperature(10.0)
45
+ service_water_loop.setMaximumLoopTemperature(60.0)
40
46
 
41
47
  if system_name.nil?
42
48
  service_water_loop.setName('Service Water Loop')
@@ -45,17 +51,17 @@ class Standard
45
51
  end
46
52
 
47
53
  # Temperature schedule type limits
48
- temp_sch_type_limits = model_add_schedule_type_limits(model,
49
- name: "Temperature Schedule Type Limits",
50
- lower_limit_value: 0.0,
51
- upper_limit_value: 100.0,
52
- numeric_type: "Continuous",
53
- unit_type: "Temperature")
54
+ temp_sch_type_limits = model_add_schedule_type_limits(model,
55
+ name: 'Temperature Schedule Type Limits',
56
+ lower_limit_value: 0.0,
57
+ upper_limit_value: 100.0,
58
+ numeric_type: 'Continuous',
59
+ unit_type: 'Temperature')
54
60
 
55
61
  # Service water heating loop controls
56
62
  swh_temp_c = service_water_temperature
57
63
  swh_temp_f = OpenStudio.convert(swh_temp_c, 'C', 'F').get
58
- swh_delta_t_r = 9 # 9F delta-T
64
+ swh_delta_t_r = 9.0 # 9F delta-T
59
65
  swh_delta_t_k = OpenStudio.convert(swh_delta_t_r, 'R', 'K').get
60
66
  swh_temp_sch = model_add_constant_schedule_ruleset(model,
61
67
  swh_temp_c,
@@ -69,25 +75,28 @@ class Standard
69
75
  sizing_plant.setDesignLoopExitTemperature(swh_temp_c)
70
76
  sizing_plant.setLoopDesignTemperatureDifference(swh_delta_t_k)
71
77
 
72
- # Service water heating pump
78
+ # Determine if circulating or non-circulating based on supplied head pressure
73
79
  swh_pump_head_press_pa = service_water_pump_head
74
- swh_pump_motor_efficiency = service_water_pump_motor_efficiency
75
- if swh_pump_head_press_pa.nil?
80
+ circulating = true
81
+ if swh_pump_head_press_pa.nil? || swh_pump_head_press_pa <= 1
76
82
  # As if there is no circulation pump
77
83
  swh_pump_head_press_pa = 0.001
78
- swh_pump_motor_efficiency = 1
84
+ service_water_pump_motor_efficiency = 1
85
+ circulating = false
79
86
  end
80
87
 
81
- swh_pump = case model_swh_pump_type(model, building_type)
82
- when 'ConstantSpeed'
83
- OpenStudio::Model::PumpConstantSpeed.new(model)
84
- when 'VariableSpeed'
85
- OpenStudio::Model::PumpVariableSpeed.new(model)
86
- end
87
- swh_pump.setName('Service Water Loop Pump')
88
+ # Service water heating pump
89
+ if circulating
90
+ swh_pump = OpenStudio::Model::PumpConstantSpeed.new(model)
91
+ swh_pump.setName("#{service_water_loop.name} Circulator Pump")
92
+ swh_pump.setPumpControlType('Intermittent')
93
+ else
94
+ swh_pump = OpenStudio::Model::PumpVariableSpeed.new(model)
95
+ swh_pump.setName("#{service_water_loop.name} Water Mains Pressure Driven")
96
+ swh_pump.setPumpControlType('Continuous')
97
+ end
88
98
  swh_pump.setRatedPumpHead(swh_pump_head_press_pa.to_f)
89
- swh_pump.setMotorEfficiency(swh_pump_motor_efficiency)
90
- swh_pump.setPumpControlType('Intermittent')
99
+ swh_pump.setMotorEfficiency(service_water_pump_motor_efficiency)
91
100
  swh_pump.addToNode(service_water_loop.supplyInletNode)
92
101
 
93
102
  water_heater = model_add_water_heater(model,
@@ -100,10 +109,21 @@ class Standard
100
109
  false,
101
110
  0.0,
102
111
  nil,
103
- water_heater_thermal_zone)
112
+ water_heater_thermal_zone,
113
+ number_water_heaters)
104
114
 
105
115
  service_water_loop.addSupplyBranchForComponent(water_heater)
106
116
 
117
+ # Pipe losses
118
+ if add_pipe_losses
119
+ model_add_piping_losses_to_swh_system(model,
120
+ service_water_loop,
121
+ circulating,
122
+ pipe_insulation_thickness: pipe_insulation_thickness,
123
+ floor_area_served: floor_area_served,
124
+ number_of_stories: number_of_stories)
125
+ end
126
+
107
127
  # Service water heating loop bypass pipes
108
128
  water_heater_bypass_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
109
129
  service_water_loop.addSupplyBranchForComponent(water_heater_bypass_pipe)
@@ -111,40 +131,35 @@ class Standard
111
131
  service_water_loop.addDemandBranchForComponent(coil_bypass_pipe)
112
132
  supply_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
113
133
  supply_outlet_pipe.addToNode(service_water_loop.supplyOutletNode)
114
- demand_inlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
115
- demand_inlet_pipe.addToNode(service_water_loop.demandInletNode)
116
134
  demand_outlet_pipe = OpenStudio::Model::PipeAdiabatic.new(model)
117
135
  demand_outlet_pipe.addToNode(service_water_loop.demandOutletNode)
118
136
 
119
- return service_water_loop
120
- end
137
+ if circulating
138
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Added circulating SWH loop called #{service_water_loop.name}")
139
+ else
140
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Added non-circulating SWH loop called #{service_water_loop.name}")
141
+ end
121
142
 
122
- # Determine the type of SWH pump that a model will have. Defaults to ConstantSpeed.
123
- # @return [String] the SWH pump type: ConstantSpeed, VariableSpeed
124
- def model_swh_pump_type(model, building_type)
125
- swh_pump_type = 'ConstantSpeed'
126
- return swh_pump_type
143
+ return service_water_loop
127
144
  end
128
145
 
129
146
  # Creates a water heater and attaches it to the supplied service water heating loop.
130
147
  #
131
148
  # @param water_heater_capacity [Double] water heater capacity, in W
132
149
  # @param water_heater_volume [Double] water heater volume, in m^3
133
- # @param water_heater_fuel [Double] valid choices are
134
- # Natural Gas, Electricity
150
+ # @param water_heater_fuel [Double] valid choices are NaturalGas, Electricity
135
151
  # @param service_water_temperature [Double] water heater temperature, in C
136
- # @param parasitic_fuel_consumption_rate [Double] water heater parasitic
137
- # fuel consumption rate, in W
138
- # @param swh_temp_sch [OpenStudio::Model::Schedule] the service water heating
139
- # schedule. If nil, will be defaulted.
140
- # @param set_peak_use_flowrate [Bool] if true, the peak flow rate
141
- # and flow rate schedule will be set.
152
+ # @param parasitic_fuel_consumption_rate [Double] water heater parasitic fuel consumption rate, in W
153
+ # @param swh_temp_sch [OpenStudio::Model::Schedule] the service water heating schedule. If nil, will be defaulted.
154
+ # @param set_peak_use_flowrate [Bool] if true, the peak flow rate and flow rate schedule will be set.
142
155
  # @param peak_flowrate [Double] in m^3/s
143
156
  # @param flowrate_schedule [String] name of the flow rate schedule
144
- # @param water_heater_thermal_zone [OpenStudio::Model::ThermalZone]
145
- # zones to place water heater in. If nil, will be assumed in 70F air for heat loss.
146
- # @return [OpenStudio::Model::WaterHeaterMixed]
147
- # the resulting water heater.
157
+ # @param water_heater_thermal_zone [OpenStudio::Model::ThermalZone] zone to place water heater in.
158
+ # If nil, will be assumed in 70F air for heat loss.
159
+ # @param number_water_heaters [Double] the number of water heaters represented by the capacity and volume inputs.
160
+ # Used to modify efficiencies for water heaters based on individual component size while avoiding having to model
161
+ # lots of individual water heaters (for runtime sake).
162
+ # @return [OpenStudio::Model::WaterHeaterMixed] the resulting water heater
148
163
  def model_add_water_heater(model,
149
164
  water_heater_capacity,
150
165
  water_heater_volume,
@@ -155,10 +170,9 @@ class Standard
155
170
  set_peak_use_flowrate,
156
171
  peak_flowrate,
157
172
  flowrate_schedule,
158
- water_heater_thermal_zone)
159
-
160
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', 'Adding water heater')
161
-
173
+ water_heater_thermal_zone,
174
+ number_water_heaters)
175
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.Model.Model', "In model_add_water_heater, number_water_heaters = #{number_water_heaters}")
162
176
  # Water heater
163
177
  # TODO Standards - Change water heater methodology to follow
164
178
  # 'Model Enhancements Appendix A.'
@@ -168,11 +182,11 @@ class Standard
168
182
 
169
183
  # Temperature schedule type limits
170
184
  temp_sch_type_limits = model_add_schedule_type_limits(model,
171
- name: "Temperature Schedule Type Limits",
185
+ name: 'Temperature Schedule Type Limits',
172
186
  lower_limit_value: 0.0,
173
187
  upper_limit_value: 100.0,
174
- numeric_type: "Continuous",
175
- unit_type: "Temperature")
188
+ numeric_type: 'Continuous',
189
+ unit_type: 'Temperature')
176
190
 
177
191
  if swh_temp_sch.nil?
178
192
  # Service water heating loop controls
@@ -189,9 +203,18 @@ class Standard
189
203
 
190
204
  # Water heater depends on the fuel type
191
205
  water_heater = OpenStudio::Model::WaterHeaterMixed.new(model)
192
- water_heater.setName("#{water_heater_vol_gal.round}gal #{water_heater_fuel} Water Heater - #{water_heater_capacity_kbtu_per_hr.round}kBtu/hr")
206
+
207
+ # Assign a quantity to the water heater if it represents multiple water heaters
208
+ if number_water_heaters > 1
209
+ water_heater.setName("#{number_water_heaters}X #{(water_heater_vol_gal/number_water_heaters).round}gal #{water_heater_fuel} Water Heater - #{(water_heater_capacity_kbtu_per_hr/number_water_heaters).round}kBtu/hr")
210
+ water_heater.set_component_quantity(number_water_heaters)
211
+ else
212
+ water_heater.setName("#{water_heater_vol_gal.round}gal #{water_heater_fuel} Water Heater - #{water_heater_capacity_kbtu_per_hr.round}kBtu/hr")
213
+ end
214
+
193
215
  water_heater.setTankVolume(OpenStudio.convert(water_heater_vol_gal, 'gal', 'm^3').get)
194
216
  water_heater.setSetpointTemperatureSchedule(swh_temp_sch)
217
+ water_heater.setDeadbandTemperatureDifference(2.0)
195
218
 
196
219
  if water_heater_thermal_zone.nil?
197
220
  # Assume the water heater is indoors at 70F or 72F
@@ -214,7 +237,7 @@ class Standard
214
237
  water_heater.resetAmbientTemperatureSchedule
215
238
  end
216
239
 
217
- water_heater.setMaximumTemperatureLimit(OpenStudio.convert(180, 'F', 'C').get)
240
+ water_heater.setMaximumTemperatureLimit(service_water_temperature)
218
241
  water_heater.setDeadbandTemperatureDifference(OpenStudio.convert(3.6, 'R', 'K').get)
219
242
  water_heater.setHeaterControlType('Cycle')
220
243
  water_heater.setHeaterMaximumCapacity(OpenStudio.convert(water_heater_capacity_btu_per_hr, 'Btu/hr', 'W').get)
@@ -229,7 +252,7 @@ class Standard
229
252
  water_heater.setOnCycleParasiticFuelType('Electricity')
230
253
  water_heater.setOffCycleLossCoefficienttoAmbientTemperature(1.053)
231
254
  water_heater.setOnCycleLossCoefficienttoAmbientTemperature(1.053)
232
- elsif water_heater_fuel == 'Natural Gas'
255
+ elsif water_heater_fuel == 'Natural Gas' || water_heater_fuel == 'NaturalGas'
233
256
  water_heater.setHeaterFuelType('Gas')
234
257
  water_heater.setHeaterThermalEfficiency(0.78)
235
258
  water_heater.setOffCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
@@ -238,6 +261,34 @@ class Standard
238
261
  water_heater.setOnCycleParasiticFuelType('Gas')
239
262
  water_heater.setOffCycleLossCoefficienttoAmbientTemperature(6.0)
240
263
  water_heater.setOnCycleLossCoefficienttoAmbientTemperature(6.0)
264
+ elsif water_heater_fuel == 'HeatPump'
265
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.Model.Model', 'Simple but crappy workaround to represent heat pump water heaters without incurring significant runtime penalty associated with using correct objects.')
266
+ # Make a part-load efficiency modifier curve with a value above 1, which
267
+ # is multiplied by the nominal efficiency of 100% to represent
268
+ # the COP of a HPWH.
269
+ # TODO could make this workaround better by using EMS
270
+ # to modify this curve output in realtime based on
271
+ # the OA temperature.
272
+ hpwh_cop = 2.8
273
+ eff_f_of_plr = OpenStudio::Model::CurveCubic.new(model)
274
+ eff_f_of_plr.setName("HPWH_COP_#{hpwh_cop}")
275
+ eff_f_of_plr.setCoefficient1Constant(hpwh_cop)
276
+ eff_f_of_plr.setCoefficient2x(0.0)
277
+ eff_f_of_plr.setCoefficient3xPOW2(0.0)
278
+ eff_f_of_plr.setCoefficient4xPOW3(0.0)
279
+ eff_f_of_plr.setMinimumValueofx(0.0)
280
+ eff_f_of_plr.setMaximumValueofx(1.0)
281
+ water_heater.setHeaterFuelType('Electricity')
282
+ water_heater.setHeaterThermalEfficiency(1.0)
283
+ water_heater.setPartLoadFactorCurve(eff_f_of_plr)
284
+ water_heater.setOffCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
285
+ water_heater.setOnCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
286
+ water_heater.setOffCycleParasiticFuelType('Electricity')
287
+ water_heater.setOnCycleParasiticFuelType('Electricity')
288
+ water_heater.setOffCycleLossCoefficienttoAmbientTemperature(1.053)
289
+ water_heater.setOnCycleLossCoefficienttoAmbientTemperature(1.053)
290
+ else
291
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.Model.Model', "#{water_heater_fuel} is not a valid water heater fuel. Valid choices are Electricity, NaturalGas, and HeatPump.")
241
292
  end
242
293
 
243
294
  if set_peak_use_flowrate
@@ -249,9 +300,284 @@ class Standard
249
300
  water_heater.setUseFlowRateFractionSchedule(schedule)
250
301
  end
251
302
 
303
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Added water heater called #{water_heater.name}")
304
+
252
305
  return water_heater
253
306
  end
254
307
 
308
+ # Creates a heatpump water heater and attaches it to the supplied service water heating loop.
309
+ #
310
+ # @param water_heater_capacity [Double] water heater capacity, in W
311
+ # @param water_heater_volume [Double] water heater volume, in m^3
312
+ # @param service_water_temperature [Double] water heater temperature, in C
313
+ # @param parasitic_fuel_consumption_rate [Double] water heater parasitic fuel consumption rate, in W
314
+ # @param swh_temp_sch [OpenStudio::Model::Schedule] the service water heating schedule. If nil, will be defaulted.
315
+ # @param set_peak_use_flowrate [Bool] if true, the peak flow rate and flow rate schedule will be set.
316
+ # @param peak_flowrate [Double] in m^3/s
317
+ # @param flowrate_schedule [String] name of the flow rate schedule
318
+ # @param water_heater_thermal_zone [OpenStudio::Model::ThermalZone] zone to place water heater in.
319
+ # If nil, will be assumed in 70F air for heat loss.
320
+ # @return [OpenStudio::Model::WaterHeaterMixed] the resulting water heater
321
+ def model_add_heatpump_water_heater(model,
322
+ type: 'PumpedCondenser',
323
+ water_heater_capacity: 500,
324
+ electric_backup_capacity: 4500,
325
+ water_heater_volume: OpenStudio.convert(80.0, 'gal', 'm^3').get,
326
+ service_water_temperature: OpenStudio.convert(125.0, 'F', 'C').get,
327
+ parasitic_fuel_consumption_rate: 3.0,
328
+ swh_temp_sch: nil,
329
+ cop: 2.8,
330
+ shr: 0.88,
331
+ tank_ua: 3.9,
332
+ set_peak_use_flowrate: false,
333
+ peak_flowrate: 0.0,
334
+ flowrate_schedule: nil,
335
+ water_heater_thermal_zone: nil)
336
+
337
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', 'Adding heat pump water heater')
338
+
339
+ # create heat pump water heater
340
+ if type == 'WrappedCondenser'
341
+ hpwh = OpenStudio::Model::WaterHeaterHeatPumpWrappedCondenser.new(model)
342
+ elsif type == 'PumpedCondenser'
343
+ hpwh = OpenStudio::Model::WaterHeaterHeatPump.new(model)
344
+ end
345
+
346
+ # calculate tank height and radius
347
+ water_heater_capacity_kbtu_per_hr = OpenStudio.convert(water_heater_capacity, 'W', 'kBtu/hr').get
348
+ hpwh_vol_gal = OpenStudio.convert(water_heater_volume, 'm^3', 'gal').get
349
+ tank_height = 0.0188 * hpwh_vol_gal + 0.0935 # linear relationship that gets GE height at 50 gal and AO Smith height at 80 gal
350
+ tank_radius = (0.9 * water_heater_volume / (Math::PI * tank_height))**0.5
351
+ tank_surface_area = 2.0 * Math::PI * tank_radius * (tank_radius + tank_height)
352
+ u_tank = (5.678 * tank_ua) / OpenStudio.convert(tank_surface_area, 'm^2', 'ft^2').get
353
+ hpwh.setName("#{hpwh_vol_gal.round}gal Heat Pump Water Heater - #{water_heater_capacity_kbtu_per_hr.round(0)}kBtu/hr")
354
+
355
+ if type == 'WrappedCondenser'
356
+ hpwh.setMinimumInletAirTemperatureforCompressorOperation(OpenStudio.convert(45.0, 'F', 'C').get)
357
+ hpwh.setMaximumInletAirTemperatureforCompressorOperation(OpenStudio.convert(120.0, 'F', 'C').get)
358
+ # set sensor heights
359
+ if hpwh_vol_gal <= 50.0
360
+ hpwh.setDeadBandTemperatureDifference(0.5)
361
+ h_UE = (1 - (3.5 / 12.0)) * tank_height # in the 4th node of the tank (counting from top)
362
+ h_LE = (1 - (10.5 / 12.0)) * tank_height # in the 11th node of the tank (counting from top)
363
+ h_condtop = (1 - (5.5 / 12.0)) * tank_height # in the 6th node of the tank (counting from top)
364
+ h_condbot = (1 - (10.99 / 12.0)) * tank_height # in the 11th node of the tank
365
+ h_hpctrl = (1 - (2.5 / 12.0)) * tank_height # in the 3rd node of the tank
366
+ hpwh.setControlSensor1HeightInStratifiedTank(h_hpctrl)
367
+ hpwh.setControlSensor1Weight(1.0)
368
+ hpwh.setControlSensor2HeightInStratifiedTank(h_hpctrl)
369
+ else
370
+ hpwh.setDeadBandTemperatureDifference(3.89)
371
+ h_UE = (1 - (3.5 / 12.0)) * tank_height # in the 3rd node of the tank (counting from top)
372
+ h_LE = (1 - (9.5 / 12.0)) * tank_height # in the 10th node of the tank (counting from top)
373
+ h_condtop = (1 - (5.5 / 12.0)) * tank_height # in the 6th node of the tank (counting from top)
374
+ h_condbot = 0.01 # bottom node
375
+ h_hpctrl_up = (1 - (2.5 / 12.0)) * tank_height # in the 3rd node of the tank
376
+ h_hpctrl_low = (1 - (8.5 / 12.0)) * tank_height # in the 9th node of the tank
377
+ hpwh.setControlSensor1HeightInStratifiedTank(h_hpctrl_up)
378
+ hpwh.setControlSensor1Weight(0.75)
379
+ hpwh.setControlSensor2HeightInStratifiedTank(h_hpctrl_low)
380
+ end
381
+ hpwh.setCondenserBottomLocation(h_condbot)
382
+ hpwh.setCondenserTopLocation(h_condtop)
383
+ hpwh.setTankElementControlLogic('MutuallyExclusive')
384
+ elsif type == 'PumpedCondenser'
385
+ hpwh.setDeadBandTemperatureDifference(3.89)
386
+ end
387
+
388
+ # set heat pump water heater properties
389
+ hpwh.setEvaporatorAirFlowRate(OpenStudio.convert(181.0, 'ft^3/min', 'm^3/s').get)
390
+ hpwh.setFanPlacement('DrawThrough')
391
+ hpwh.setOnCycleParasiticElectricLoad(0.0)
392
+ hpwh.setOffCycleParasiticElectricLoad(0.0)
393
+ hpwh.setParasiticHeatRejectionLocation('Outdoors')
394
+
395
+ # set temperature setpoint schedule
396
+ if swh_temp_sch.nil?
397
+ # temperature schedule type limits
398
+ temp_sch_type_limits = model_add_schedule_type_limits(model,
399
+ name: 'Temperature Schedule Type Limits',
400
+ lower_limit_value: 0.0,
401
+ upper_limit_value: 100.0,
402
+ numeric_type: 'Continuous',
403
+ unit_type: 'Temperature')
404
+ # service water heating loop controls
405
+ swh_temp_c = service_water_temperature
406
+ swh_temp_f = OpenStudio.convert(swh_temp_c, 'C', 'F').get
407
+ swh_delta_t_r = 9.0 # 9F delta-T
408
+ swh_temp_c = OpenStudio.convert(swh_temp_f, 'F', 'C').get
409
+ swh_delta_t_k = OpenStudio.convert(swh_delta_t_r, 'R', 'K').get
410
+ swh_temp_sch = model_add_constant_schedule_ruleset(model,
411
+ swh_temp_c,
412
+ name = "Heat Pump Water Heater Temp - #{swh_temp_f.round}F")
413
+ swh_temp_sch.setScheduleTypeLimits(temp_sch_type_limits)
414
+ end
415
+ hpwh.setCompressorSetpointTemperatureSchedule(swh_temp_sch)
416
+
417
+ # coil curves
418
+ hpwh_cap = OpenStudio::Model::CurveBiquadratic.new(model)
419
+ hpwh_cap.setName('HPWH-Cap-fT')
420
+ hpwh_cap.setCoefficient1Constant(0.563)
421
+ hpwh_cap.setCoefficient2x(0.0437)
422
+ hpwh_cap.setCoefficient3xPOW2(0.000039)
423
+ hpwh_cap.setCoefficient4y(0.0055)
424
+ hpwh_cap.setCoefficient5yPOW2(-0.000148)
425
+ hpwh_cap.setCoefficient6xTIMESY(-0.000145)
426
+ hpwh_cap.setMinimumValueofx(0.0)
427
+ hpwh_cap.setMaximumValueofx(100.0)
428
+ hpwh_cap.setMinimumValueofy(0.0)
429
+ hpwh_cap.setMaximumValueofy(100.0)
430
+
431
+ hpwh_cop = OpenStudio::Model::CurveBiquadratic.new(model)
432
+ hpwh_cop.setName('HPWH-COP-fT')
433
+ hpwh_cop.setCoefficient1Constant(1.1332)
434
+ hpwh_cop.setCoefficient2x(0.063)
435
+ hpwh_cop.setCoefficient3xPOW2(-0.0000979)
436
+ hpwh_cop.setCoefficient4y(-0.00972)
437
+ hpwh_cop.setCoefficient5yPOW2(-0.0000214)
438
+ hpwh_cop.setCoefficient6xTIMESY(-0.000686)
439
+ hpwh_cop.setMinimumValueofx(0.0)
440
+ hpwh_cop.setMaximumValueofx(100.0)
441
+ hpwh_cop.setMinimumValueofy(0.0)
442
+ hpwh_cop.setMaximumValueofy(100.0)
443
+
444
+ # create DX coil object
445
+ if type == 'WrappedCondenser'
446
+ coil = hpwh.dXCoil.to_CoilWaterHeatingAirToWaterHeatPumpWrapped.get
447
+ coil.setRatedCondenserWaterTemperature(48.89)
448
+ elsif type == 'PumpedCondenser'
449
+ coil = OpenStudio::Model::CoilWaterHeatingAirToWaterHeatPump.new(model)
450
+ hpwh.setDXCoil(coil)
451
+ end
452
+
453
+ # set coil properties
454
+ coil.setName("#{hpwh.name} Coil")
455
+ coil.setRatedHeatingCapacity(water_heater_capacity * cop)
456
+ coil.setRatedCOP(cop)
457
+ coil.setRatedSensibleHeatRatio(shr)
458
+ coil.setRatedEvaporatorInletAirDryBulbTemperature(OpenStudio.convert(67.5, 'F', 'C').get)
459
+ coil.setRatedEvaporatorInletAirWetBulbTemperature(OpenStudio.convert(56.426, 'F', 'C').get)
460
+ coil.setRatedEvaporatorAirFlowRate(OpenStudio.convert(181.0, 'ft^3/min', 'm^3/s').get)
461
+ coil.setEvaporatorFanPowerIncludedinRatedCOP(true)
462
+ coil.setEvaporatorAirTemperatureTypeforCurveObjects('WetBulbTemperature')
463
+ coil.setHeatingCapacityFunctionofTemperatureCurve(hpwh_cap)
464
+ coil.setHeatingCOPFunctionofTemperatureCurve(hpwh_cop)
465
+ coil.setMaximumAmbientTemperatureforCrankcaseHeaterOperation(0.0)
466
+
467
+ # set tank properties
468
+ if type == 'WrappedCondenser'
469
+ tank = hpwh.tank.to_WaterHeaterStratified.get
470
+ tank.setTankHeight(tank_height)
471
+ tank.setHeaterPriorityControl('MasterSlave')
472
+ if hpwh_vol_gal <= 50.0
473
+ tank.setHeater1DeadbandTemperatureDifference(25.0)
474
+ tank.setHeater2DeadbandTemperatureDifference(30.0)
475
+ else
476
+ tank.setHeater1DeadbandTemperatureDifference(18.5)
477
+ tank.setHeater2DeadbandTemperatureDifference(3.89)
478
+ end
479
+ hpwh_bottom_element_sp = OpenStudio::Model::ScheduleConstant.new(model)
480
+ hpwh_bottom_element_sp.setName("#{hpwh.name} BottomElementSetpoint")
481
+ hpwh_top_element_sp = OpenStudio::Model::ScheduleConstant.new(model)
482
+ hpwh_top_element_sp.setName("#{hpwh.name} TopElementSetpoint")
483
+ tank.setHeater1Capacity(electric_backup_capacity)
484
+ tank.setHeater1Height(h_UE)
485
+ tank.setHeater1SetpointTemperatureSchedule(hpwh_top_element_sp) # Overwritten later by EMS
486
+ tank.setHeater2Capacity(electric_backup_capacity)
487
+ tank.setHeater2Height(h_LE)
488
+ tank.setHeater2SetpointTemperatureSchedule(hpwh_bottom_element_sp)
489
+ tank.setUniformSkinLossCoefficientperUnitAreatoAmbientTemperature(u_tank)
490
+ tank.setNumberofNodes(12)
491
+ tank.setAdditionalDestratificationConductivity(0)
492
+ tank.setNode1AdditionalLossCoefficient(0)
493
+ tank.setNode2AdditionalLossCoefficient(0)
494
+ tank.setNode3AdditionalLossCoefficient(0)
495
+ tank.setNode4AdditionalLossCoefficient(0)
496
+ tank.setNode5AdditionalLossCoefficient(0)
497
+ tank.setNode6AdditionalLossCoefficient(0)
498
+ tank.setNode7AdditionalLossCoefficient(0)
499
+ tank.setNode8AdditionalLossCoefficient(0)
500
+ tank.setNode9AdditionalLossCoefficient(0)
501
+ tank.setNode10AdditionalLossCoefficient(0)
502
+ tank.setNode11AdditionalLossCoefficient(0)
503
+ tank.setNode12AdditionalLossCoefficient(0)
504
+ tank.setUseSideDesignFlowRate(0.9 * water_heater_volume / 60.1)
505
+ tank.setSourceSideDesignFlowRate(0)
506
+ tank.setSourceSideFlowControlMode('')
507
+ tank.setSourceSideInletHeight(0)
508
+ tank.setSourceSideOutletHeight(0)
509
+ elsif type == 'PumpedCondenser'
510
+ tank = OpenStudio::Model::WaterHeaterMixed.new(model)
511
+ hpwh.setTank(tank)
512
+ tank.setDeadbandTemperatureDifference(3.89)
513
+ tank.setHeaterControlType('Cycle')
514
+ tank.setHeaterMaximumCapacity(electric_backup_capacity)
515
+ end
516
+ tank.setName("#{hpwh.name} Tank")
517
+ tank.setEndUseSubcategory('Service Hot Water')
518
+ tank.setTankVolume(0.9 * water_heater_volume)
519
+ tank.setMaximumTemperatureLimit(90.0)
520
+ tank.setHeaterFuelType('Electricity')
521
+ tank.setHeaterThermalEfficiency(1.0)
522
+ tank.setOffCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
523
+ tank.setOffCycleParasiticFuelType('Electricity')
524
+ tank.setOnCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
525
+ tank.setOnCycleParasiticFuelType('Electricity')
526
+
527
+ # set fan properties
528
+ fan = hpwh.fan.to_FanOnOff.get
529
+ fan.setName("#{hpwh.name} Fan")
530
+ fan_power = 0.0462 # watts per cfm
531
+ if hpwh_vol_gal <= 50.0
532
+ fan.setFanEfficiency(23.0 / fan_power * OpenStudio.convert(1.0, 'ft^3/min', 'm^3/s').get)
533
+ fan.setPressureRise(23.0)
534
+ else
535
+ fan.setFanEfficiency(65.0 / fan_power * OpenStudio.convert(1.0, 'ft^3/min', 'm^3/s').get)
536
+ fan.setPressureRise(65.0)
537
+ end
538
+ fan.setMaximumFlowRate(OpenStudio.convert(181.0, 'ft^3/min', 'm^3/s').get)
539
+ fan.setMotorEfficiency(1.0)
540
+ fan.setMotorInAirstreamFraction(1.0)
541
+ fan.setEndUseSubcategory('Service Hot Water')
542
+
543
+ if water_heater_thermal_zone.nil?
544
+ # add in schedules for Tamb, RHamb, and the compressor
545
+ # assume the water heater is indoors at 70F for now
546
+ default_water_heater_ambient_temp_sch = model_add_constant_schedule_ruleset(model,
547
+ OpenStudio.convert(70.0, 'F', 'C').get,
548
+ name = 'Water Heater Ambient Temp Schedule - 70F')
549
+ default_water_heater_ambient_temp_sch.setScheduleTypeLimits(temp_sch_type_limits)
550
+ tank.setAmbientTemperatureIndicator('Schedule')
551
+ tank.setAmbientTemperatureSchedule(default_water_heater_ambient_temp_sch)
552
+ tank.resetAmbientTemperatureThermalZone
553
+ hpwh_rhamb = OpenStudio::Model::ScheduleConstant.new(model)
554
+ hpwh_rhamb.setName("#{hpwh.name} Ambient Humidity Schedule")
555
+ hpwh_rhamb.setValue(0.5)
556
+ hpwh.setInletAirConfiguration('Schedule')
557
+ hpwh.setInletAirTemperatureSchedule(default_water_heater_ambient_temp_sch)
558
+ hpwh.setInletAirHumiditySchedule(hpwh_rhamb)
559
+ hpwh.setCompressorLocation('Schedule')
560
+ hpwh.setCompressorAmbientTemperatureSchedule(default_water_heater_ambient_temp_sch)
561
+ else
562
+ hpwh.addToThermalZone(water_heater_thermal_zone)
563
+ hpwh.setInletAirConfiguration('ZoneAirOnly')
564
+ hpwh.setCompressorLocation('Zone')
565
+ tank.setAmbientTemperatureIndicator('ThermalZone')
566
+ tank.setAmbientTemperatureThermalZone(water_heater_thermal_zone)
567
+ tank.resetAmbientTemperatureSchedule
568
+ end
569
+
570
+ if set_peak_use_flowrate
571
+ rated_flow_rate_m3_per_s = peak_flowrate
572
+ rated_flow_rate_gal_per_min = OpenStudio.convert(rated_flow_rate_m3_per_s, 'm^3/s', 'gal/min').get
573
+ tank.setPeakUseFlowRate(rated_flow_rate_m3_per_s)
574
+ schedule = model_add_schedule(model, flowrate_schedule)
575
+ tank.setUseFlowRateFractionSchedule(schedule)
576
+ end
577
+
578
+ return hpwh
579
+ end
580
+
255
581
  # Creates a booster water heater and attaches it
256
582
  # to the supplied service water heating loop.
257
583
  #
@@ -266,7 +592,6 @@ class Standard
266
592
  # fuel consumption rate, in W
267
593
  # @param booster_water_heater_thermal_zone [OpenStudio::Model::ThermalZone]
268
594
  # zones to place water heater in. If nil, will be assumed in 70F air for heat loss.
269
- # @param building_type [String] the building type
270
595
  # @return [OpenStudio::Model::PlantLoop]
271
596
  # the resulting booster water loop.
272
597
  def model_add_swh_booster(model,
@@ -276,8 +601,7 @@ class Standard
276
601
  water_heater_fuel,
277
602
  booster_water_temperature,
278
603
  parasitic_fuel_consumption_rate,
279
- booster_water_heater_thermal_zone,
280
- building_type = nil)
604
+ booster_water_heater_thermal_zone)
281
605
 
282
606
  OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Adding booster water heater to #{main_service_water_loop.name}")
283
607
 
@@ -311,12 +635,13 @@ class Standard
311
635
  sizing_plant.setLoopDesignTemperatureDifference(swh_delta_t_k)
312
636
 
313
637
  # Booster water heating pump
314
- swh_pump = OpenStudio::Model::PumpConstantSpeed.new(model)
638
+ swh_pump = OpenStudio::Model::PumpVariableSpeed.new(model)
315
639
  swh_pump.setName('Booster Water Loop Pump')
316
- swh_pump_head_press_pa = 0.0 # As if there is no circulation pump
317
- swh_pump.setRatedPumpHead(swh_pump_head_press_pa)
640
+ swh_pump.setRatedPumpHead(0.0) # As if there is no circulation pump
641
+ swh_pump.setRatedPowerConsumption(0.0) # As if there is no circulation pump
318
642
  swh_pump.setMotorEfficiency(1)
319
- swh_pump.setPumpControlType('Intermittent')
643
+ swh_pump.setPumpControlType('Continuous')
644
+ swh_pump.setMinimumFlowRate(0.0)
320
645
  swh_pump.addToNode(booster_service_water_loop.supplyInletNode)
321
646
 
322
647
  # Water heater
@@ -331,6 +656,8 @@ class Standard
331
656
  water_heater.setName("#{water_heater_vol_gal}gal #{water_heater_fuel} Booster Water Heater - #{water_heater_capacity_kbtu_per_hr.round}kBtu/hr")
332
657
  water_heater.setTankVolume(OpenStudio.convert(water_heater_vol_gal, 'gal', 'm^3').get)
333
658
  water_heater.setSetpointTemperatureSchedule(swh_temp_sch)
659
+ water_heater.setDeadbandTemperatureDifference(2.0)
660
+ water_heater.setEndUseSubcategory('Booster')
334
661
 
335
662
  if booster_water_heater_thermal_zone.nil?
336
663
  # Assume the water heater is indoors at 70F or 72F
@@ -353,7 +680,7 @@ class Standard
353
680
  water_heater.resetAmbientTemperatureSchedule
354
681
  end
355
682
 
356
- water_heater.setMaximumTemperatureLimit(OpenStudio.convert(180, 'F', 'C').get)
683
+ water_heater.setMaximumTemperatureLimit(swh_temp_c)
357
684
  water_heater.setDeadbandTemperatureDifference(OpenStudio.convert(3.6, 'R', 'K').get)
358
685
  water_heater.setHeaterControlType('Cycle')
359
686
  water_heater.setHeaterMaximumCapacity(OpenStudio.convert(water_heater_capacity_btu_per_hr, 'Btu/hr', 'W').get)
@@ -368,7 +695,7 @@ class Standard
368
695
  water_heater.setOnCycleParasiticFuelType('Electricity')
369
696
  water_heater.setOffCycleLossCoefficienttoAmbientTemperature(1.053)
370
697
  water_heater.setOnCycleLossCoefficienttoAmbientTemperature(1.053)
371
- elsif water_heater_fuel == 'Natural Gas'
698
+ elsif water_heater_fuel == 'Natural Gas' || water_heater_fuel == 'NaturalGas'
372
699
  water_heater.setHeaterFuelType('Gas')
373
700
  water_heater.setHeaterThermalEfficiency(0.8)
374
701
  water_heater.setOffCycleParasiticFuelConsumptionRate(parasitic_fuel_consumption_rate)
@@ -379,15 +706,6 @@ class Standard
379
706
  water_heater.setOnCycleLossCoefficienttoAmbientTemperature(6.0)
380
707
  end
381
708
 
382
- if water_heater_fuel == 'Electricity'
383
- water_heater.setHeaterFuelType('Electricity')
384
- water_heater.setOffCycleParasiticFuelType('Electricity')
385
- water_heater.setOnCycleParasiticFuelType('Electricity')
386
- elsif water_heater_fuel == 'Natural Gas'
387
- water_heater.setHeaterFuelType('Gas')
388
- water_heater.setOffCycleParasiticFuelType('Gas')
389
- water_heater.setOnCycleParasiticFuelType('Gas')
390
- end
391
709
  booster_service_water_loop.addSupplyBranchForComponent(water_heater)
392
710
 
393
711
  # Service water heating loop bypass pipes
@@ -417,6 +735,43 @@ class Standard
417
735
  # the main service water loop.
418
736
  main_service_water_loop.addDemandBranchForComponent(hx)
419
737
 
738
+ # Add a plant component temperature source to the demand outlet
739
+ # of the HX to represent the fact that the water used by the booster
740
+ # would in reality be at the mains temperature.
741
+ mains_src = OpenStudio::Model::PlantComponentTemperatureSource.new(model)
742
+ mains_src.setName('Mains Water Makeup for SWH Booster')
743
+ mains_src.addToNode(hx.demandOutletModelObject.get.to_Node.get)
744
+
745
+ # Mains water temperature sensor
746
+ mains_water_temp_sen = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Site Mains Water Temperature')
747
+ mains_water_temp_sen.setName('Mains_Water_Temp_Sen')
748
+ mains_water_temp_sen.setKeyName('Environment')
749
+
750
+ # Schedule to actuate
751
+ water_mains_temp_sch = OpenStudio::Model::ScheduleConstant.new(model)
752
+ water_mains_temp_sch.setName('Mains Water Temperature')
753
+ water_mains_temp_sch.setValue(OpenStudio.convert(50, 'F', 'C').get)
754
+
755
+ # Actuator for mains water temperature schedule
756
+ mains_water_temp_sch_act = OpenStudio::Model::EnergyManagementSystemActuator.new(water_mains_temp_sch, 'Schedule:Constant', 'Schedule Value')
757
+ mains_water_temp_sch_act.setName('Mains_Water_Temp_Act')
758
+
759
+ # Program
760
+ mains_prg = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
761
+ mains_prg.setName('Mains_Water_Prg')
762
+ mains_prg_body = "SET #{mains_water_temp_sch_act.handle} = #{mains_water_temp_sen.handle}"
763
+ mains_prg.setBody(mains_prg_body)
764
+
765
+ # Program Calling Manager
766
+ mains_mgr = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model)
767
+ mains_mgr.setName("Mains_Water_Prg_Mgr")
768
+ mains_mgr.setCallingPoint('BeginTimestepBeforePredictor')
769
+ mains_mgr.addProgram(mains_prg)
770
+
771
+ # Make the plant component use the actuated schedule
772
+ mains_src.setTemperatureSpecificationType('Scheduled')
773
+ mains_src.setSourceTemperatureSchedule(water_mains_temp_sch)
774
+
420
775
  return booster_service_water_loop
421
776
  end
422
777
 
@@ -432,7 +787,6 @@ class Standard
432
787
  # @param water_use_temperature [Double] mixed water use temperature, in C
433
788
  # @param space_name [String] the name of the space to add the water fixture to,
434
789
  # or nil, in which case it will not be assigned to any particular space.
435
- # @param building_type [String] the building type
436
790
  # @return [OpenStudio::Model::WaterUseEquipment]
437
791
  # the resulting water fixture.
438
792
  def model_add_swh_end_uses(model,
@@ -442,10 +796,8 @@ class Standard
442
796
  flowrate_schedule,
443
797
  water_use_temperature,
444
798
  space_name,
445
- building_type = nil)
446
-
447
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Adding water fixture to #{swh_loop.name}.")
448
-
799
+ frac_sensible: 0.2,
800
+ frac_latent: 0.05)
449
801
  # Water use connection
450
802
  swh_connection = OpenStudio::Model::WaterUseConnections.new(model)
451
803
 
@@ -453,8 +805,7 @@ class Standard
453
805
  water_fixture_def = OpenStudio::Model::WaterUseEquipmentDefinition.new(model)
454
806
  rated_flow_rate_m3_per_s = peak_flowrate
455
807
  rated_flow_rate_gal_per_min = OpenStudio.convert(rated_flow_rate_m3_per_s, 'm^3/s', 'gal/min').get
456
- frac_sensible = 0.2
457
- frac_latent = 0.05
808
+
458
809
  water_use_sensible_frac_sch = model_add_constant_schedule_ruleset(model,
459
810
  frac_sensible,
460
811
  name = "Fraction Sensible - #{frac_sensible}",
@@ -466,7 +817,7 @@ class Standard
466
817
  water_fixture_def.setSensibleFractionSchedule(water_use_sensible_frac_sch)
467
818
  water_fixture_def.setLatentFractionSchedule(water_use_latent_frac_sch)
468
819
  water_fixture_def.setPeakFlowRate(rated_flow_rate_m3_per_s)
469
- water_fixture_def.setName("#{use_name.capitalize} Service Water Use Def #{rated_flow_rate_gal_per_min.round(2)}gal/min")
820
+ water_fixture_def.setName("#{use_name} Service Water Use Def #{rated_flow_rate_gal_per_min.round(2)}gpm")
470
821
  # Target mixed water temperature
471
822
  mixed_water_temp_f = OpenStudio.convert(water_use_temperature, 'C', 'F').get
472
823
  mixed_water_temp_sch = model_add_constant_schedule_ruleset(model,
@@ -480,9 +831,11 @@ class Standard
480
831
  water_fixture.setFlowRateFractionSchedule(schedule)
481
832
 
482
833
  if space_name.nil?
483
- water_fixture.setName("#{use_name.capitalize} Service Water Use #{rated_flow_rate_gal_per_min.round(2)}gal/min")
834
+ water_fixture.setName("#{use_name} Service Water Use #{rated_flow_rate_gal_per_min.round(2)}gpm at #{mixed_water_temp_f.round}F")
835
+ swh_connection.setName("#{use_name} WUC #{rated_flow_rate_gal_per_min.round(2)}gpm at #{mixed_water_temp_f.round}F")
484
836
  else
485
- water_fixture.setName("#{space_name.capitalize} Service Water Use #{rated_flow_rate_gal_per_min.round(2)}gal/min")
837
+ water_fixture.setName("#{space_name} Service Water Use #{rated_flow_rate_gal_per_min.round(2)}gpm at #{mixed_water_temp_f.round}F")
838
+ swh_connection.setName("#{space_name} WUC #{rated_flow_rate_gal_per_min.round(2)}gpm at #{mixed_water_temp_f.round}F")
486
839
  end
487
840
 
488
841
  unless space_name.nil?
@@ -494,31 +847,69 @@ class Standard
494
847
  swh_connection.addWaterUseEquipment(water_fixture)
495
848
 
496
849
  # Connect the water use connection to the SWH loop
497
- swh_loop.addDemandBranchForComponent(swh_connection)
850
+ unless swh_loop.nil?
851
+ swh_loop.addDemandBranchForComponent(swh_connection)
852
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Adding water fixture to #{swh_loop.name}.")
853
+ end
854
+
855
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Added #{water_fixture.name}.")
498
856
 
499
857
  return water_fixture
500
858
  end
501
859
 
502
- # This method will add an swh water fixture to the model for the space.
503
- # if the it will return a water fixture object, or NIL if there is no water load at all.
504
- def model_add_swh_end_uses_by_space(model, building_type, climate_zone, swh_loop, space_type_name, space_name, space_multiplier = nil, is_flow_per_area = true)
860
+ # This method will add a swh water fixture to the model for the space.
861
+ # It will return a water fixture object, or NIL if there is no water load at all.
862
+ #
863
+ # Adds a WaterUseEquipment object representing the SWH loads of the supplied Space.
864
+ # Attaches this WaterUseEquipment to the supplied PlantLoop via a new WaterUseConnections object.
865
+ #
866
+ # @param model [OpenStudio::Model::Model] the model
867
+ # @param swh_loop [OpenStudio::Model::PlantLoop] the SWH loop to connect the WaterUseEquipment to
868
+ # @space [OpenStudio::Model::Space] the Space to add a WaterUseEquipment for
869
+ # @space_multiplier [Double] the multiplier to use if the supplied Space actually represents
870
+ # more area than is shown in the model.
871
+ # @param is_flow_per_area [Bool] if true, use the value in the 'service_water_heating_peak_flow_per_area'
872
+ # field of the space_types JSON. If false, use the value in the 'service_water_heating_peak_flow_rate' field.
873
+ # @return [OpenStudio::Model::WaterUseEquipment] the WaterUseEquipment for the
874
+ def model_add_swh_end_uses_by_space(model,
875
+ swh_loop,
876
+ space,
877
+ space_multiplier = 1.0,
878
+ is_flow_per_area = true)
879
+ # SpaceType
880
+ if space.spaceType.empty?
881
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "Space #{space.name} does not have a Space Type assigned, cannot add SWH end uses.")
882
+ return nil
883
+ end
884
+ space_type = space.spaceType.get
885
+
886
+ # Standards Building Type
887
+ if space_type.standardsBuildingType.empty?
888
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "Space #{space.name}'s Space Type does not have a Standards Building Type assigned, cannot add SWH end uses.")
889
+ return nil
890
+ end
891
+ stds_bldg_type = space_type.standardsBuildingType.get
892
+ building_type = model_get_lookup_name(stds_bldg_type)
893
+
894
+ # Standards Space Type
895
+ if space_type.standardsSpaceType.empty?
896
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.Model', "Space #{space.name}'s Space Type does not have a Standards Space Type assigned, cannot add SWH end uses.")
897
+ return nil
898
+ end
899
+ stds_spc_type = space_type.standardsSpaceType.get
900
+
505
901
  # find the specific space_type properties from standard.json
506
902
  search_criteria = {
507
903
  'template' => template,
508
904
  'building_type' => building_type,
509
- 'space_type' => space_type_name
905
+ 'space_type' => stds_spc_type
510
906
  }
511
907
  data = model_find_object(standards_data['space_types'], search_criteria)
512
908
  if data.nil?
513
909
  OpenStudio.logFree(OpenStudio::Error, 'openstudio.Model.Model', "Could not find space type for: #{search_criteria}.")
514
910
  return nil
515
911
  end
516
- space = model.getSpaceByName(space_name)
517
- space = space.get
518
912
  space_area = OpenStudio.convert(space.floorArea, 'm^2', 'ft^2').get # ft2
519
- if space_multiplier.nil?
520
- space_multiplier = 1
521
- end
522
913
 
523
914
  # If there is no service hot water load.. Don't bother adding anything.
524
915
  if data['service_water_heating_peak_flow_per_area'].to_f == 0.0 &&
@@ -550,10 +941,10 @@ class Standard
550
941
  water_fixture_def.setSensibleFractionSchedule(water_use_sensible_frac_sch)
551
942
  water_fixture_def.setLatentFractionSchedule(water_use_latent_frac_sch)
552
943
  water_fixture_def.setPeakFlowRate(rated_flow_rate_m3_per_s)
553
- water_fixture_def.setName("#{space_name.capitalize} Service Water Use Def #{rated_flow_rate_gal_per_min.round(2)}gal/min")
944
+ water_fixture_def.setName("#{space.name.get} Service Water Use Def #{rated_flow_rate_gal_per_min.round(2)}gpm")
554
945
  # Target mixed water temperature
555
- mixed_water_temp_c = data['service_water_heating_target_temperature']
556
- mixed_water_temp_f = OpenStudio.convert(mixed_water_temp_c, 'C', 'F').get
946
+ mixed_water_temp_f = data['service_water_heating_target_temperature']
947
+ mixed_water_temp_c = OpenStudio.convert(mixed_water_temp_f, 'F', 'C').get
557
948
  mixed_water_temp_sch = model_add_constant_schedule_ruleset(model,
558
949
  mixed_water_temp_c,
559
950
  name = "Mixed Water At Faucet Temp - #{mixed_water_temp_f.round}F")
@@ -563,7 +954,7 @@ class Standard
563
954
  water_fixture = OpenStudio::Model::WaterUseEquipment.new(water_fixture_def)
564
955
  schedule = model_add_schedule(model, data['service_water_heating_schedule'])
565
956
  water_fixture.setFlowRateFractionSchedule(schedule)
566
- water_fixture.setName("#{space_name.capitalize} Service Water Use #{rated_flow_rate_gal_per_min.round(2)}gal/min")
957
+ water_fixture.setName("#{space.name.get} Service Water Use #{rated_flow_rate_gal_per_min.round(2)}gpm")
567
958
  swh_connection.addWaterUseEquipment(water_fixture)
568
959
  # Assign water fixture to a space
569
960
  water_fixture.setSpace(space) if model_attach_water_fixtures_to_spaces?(model)
@@ -586,17 +977,13 @@ class Standard
586
977
  # @param peak_flowrate [Double] in m^3/s
587
978
  # @param flowrate_schedule [String] name of the flow rate schedule
588
979
  # @param water_use_temperature [Double] mixed water use temperature, in C
589
- # @param building_type [String] the building type
590
980
  # @return [OpenStudio::Model::WaterUseEquipment]
591
981
  # the resulting water fixture.
592
982
  def model_add_booster_swh_end_uses(model,
593
983
  swh_booster_loop,
594
984
  peak_flowrate,
595
985
  flowrate_schedule,
596
- water_use_temperature,
597
- building_type = nil)
598
-
599
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Adding water fixture to #{swh_booster_loop.name}.")
986
+ water_use_temperature)
600
987
 
601
988
  # Water use connection
602
989
  swh_connection = OpenStudio::Model::WaterUseConnections.new(model)
@@ -605,10 +992,10 @@ class Standard
605
992
  water_fixture_def = OpenStudio::Model::WaterUseEquipmentDefinition.new(model)
606
993
  rated_flow_rate_m3_per_s = peak_flowrate
607
994
  rated_flow_rate_gal_per_min = OpenStudio.convert(rated_flow_rate_m3_per_s, 'm^3/s', 'gal/min').get
608
- water_fixture_def.setName("Water Fixture Def - #{rated_flow_rate_gal_per_min} gal/min")
995
+ water_fixture_def.setName("Booster Water Fixture Def - #{rated_flow_rate_gal_per_min.round(2)} gpm")
609
996
  water_fixture_def.setPeakFlowRate(rated_flow_rate_m3_per_s)
610
997
  # Target mixed water temperature
611
- mixed_water_temp_f = OpenStudio.convert(water_use_temperature, 'F', 'C').get
998
+ mixed_water_temp_f = OpenStudio.convert(water_use_temperature, 'C', 'F').get
612
999
  mixed_water_temp_sch = model_add_constant_schedule_ruleset(model,
613
1000
  OpenStudio.convert(mixed_water_temp_f, 'F', 'C').get,
614
1001
  name = "Mixed Water At Faucet Temp - #{mixed_water_temp_f.round}F")
@@ -616,15 +1003,137 @@ class Standard
616
1003
 
617
1004
  # Water use equipment
618
1005
  water_fixture = OpenStudio::Model::WaterUseEquipment.new(water_fixture_def)
619
- water_fixture.setName("Booster Water Fixture - #{rated_flow_rate_gal_per_min} gal/min at #{mixed_water_temp_f}F")
1006
+ water_fixture.setName("Booster Water Fixture - #{rated_flow_rate_gal_per_min.round(2)} gpm at #{mixed_water_temp_f.round}F")
620
1007
  schedule = model_add_schedule(model, flowrate_schedule)
621
1008
  water_fixture.setFlowRateFractionSchedule(schedule)
622
1009
  swh_connection.addWaterUseEquipment(water_fixture)
623
1010
 
624
1011
  # Connect the water use connection to the SWH loop
625
- swh_booster_loop.addDemandBranchForComponent(swh_connection)
1012
+ unless swh_booster_loop.nil?
1013
+ swh_booster_loop.addDemandBranchForComponent(swh_connection)
1014
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.Model.Model', "Adding water fixture to #{swh_booster_loop.name}.")
1015
+ end
626
1016
 
627
1017
  return water_fixture
628
1018
  end
629
1019
 
1020
+ # Adds insulated 0.75in copper piping to the model.
1021
+ # For circulating systems, assume length of piping is proportional
1022
+ # to the area and number of stories in the building.
1023
+ # For non-circulating systems, assume that the water heaters
1024
+ # are close to the point of use.
1025
+ # Assume that piping is located in a zone
1026
+ #
1027
+ # @param model [OpenStudio::Model::Model] the model
1028
+ # @param swh_loop [OpenStudio::Model::PlantLoop] the service water heating loop
1029
+ # @param floor_area_served [Double] the area of building served by the service water heating loop, in m^2
1030
+ # @param number_of_stories [Integer] the number of stories served by the service water heating loop
1031
+ # @param pipe_insulation_thickness [Double] the thickness of the pipe insulation, in m. Use 0 for no insulation
1032
+ # @param circulating [Bool] use true for circulating systems, false for non-circulating systems
1033
+ # @param air_temp_surrounding_piping [Double] the temperature of the air surrounding the piping, in C.
1034
+ def model_add_piping_losses_to_swh_system(model,
1035
+ swh_loop,
1036
+ circulating,
1037
+ pipe_insulation_thickness: 0,
1038
+ floor_area_served: 465,
1039
+ number_of_stories: 1,
1040
+ air_temp_surrounding_piping: 21.1111)
1041
+
1042
+ # Estimate pipe length
1043
+ if circulating
1044
+ # For circulating systems, get pipe length based on the size of the building.
1045
+ # Formula from A.3.1 PrototypeModelEnhancements_2014_0.pdf
1046
+ floor_area_ft2 = OpenStudio.convert(floor_area_served, 'm^2', 'ft^2').get
1047
+ pipe_length_ft = 2.0 * (Math.sqrt(floor_area_ft2 / number_of_stories) + (10.0 * (number_of_stories - 1.0)))
1048
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Pipe length #{pipe_length_ft.round}ft = 2.0 * ( (#{floor_area_ft2.round}ft2 / #{number_of_stories} stories)^0.5 + (10.0ft * (#{number_of_stories} stories - 1.0) ) )")
1049
+ else
1050
+ # For non-circulating systems, assume water heater is close to point of use
1051
+ pipe_length_ft = 20.0
1052
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Pipe length #{pipe_length_ft.round}ft. For non-circulating systems, assume water heater is close to point of use.")
1053
+ end
1054
+
1055
+ # For systems whose water heater object represents multiple pieces
1056
+ # of equipment, multiply the piping length by the number of pieces of equipment.
1057
+ swh_loop.supplyComponents('OS_WaterHeater_Mixed'.to_IddObjectType).each do |sc|
1058
+ next unless sc.to_WaterHeaterMixed.is_initialized
1059
+ water_heater = sc.to_WaterHeaterMixed.get
1060
+ comp_qty = water_heater.component_quantity
1061
+ if comp_qty > 1
1062
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Piping length has been multiplied by #{comp_qty}X because #{water_heater.name} represents #{comp_qty} pieces of equipment.")
1063
+ pipe_length_ft *= comp_qty
1064
+ break
1065
+ end
1066
+ end
1067
+
1068
+ # Service water heating piping heat loss scheduled air temperature
1069
+ swh_piping_air_temp_c = air_temp_surrounding_piping
1070
+ swh_piping_air_temp_f = OpenStudio.convert(swh_piping_air_temp_c, 'C', 'F').get
1071
+ swh_piping_air_temp_sch = model_add_constant_schedule_ruleset(model,
1072
+ swh_piping_air_temp_c,
1073
+ name = "#{swh_loop.name} Piping Air Temp - #{swh_piping_air_temp_f.round}F")
1074
+
1075
+ # Service water heating piping heat loss scheduled air velocity
1076
+ swh_piping_air_velocity_m_per_s = 0.3
1077
+ swh_piping_air_velocity_mph = OpenStudio.convert(swh_piping_air_velocity_m_per_s, 'm/s', 'mile/hr').get
1078
+ swh_piping_air_velocity_sch = model_add_constant_schedule_ruleset(model,
1079
+ swh_piping_air_velocity_m_per_s,
1080
+ name = "#{swh_loop.name} Piping Air Velocity - #{swh_piping_air_velocity_mph.round(2)}mph")
1081
+
1082
+ # Material for 3/4in type L (heavy duty) copper pipe
1083
+ copper_pipe = OpenStudio::Model::StandardOpaqueMaterial.new(model)
1084
+ copper_pipe.setName("Copper pipe 0.75in type L")
1085
+ copper_pipe.setRoughness('Smooth')
1086
+ copper_pipe.setThickness(OpenStudio.convert(0.045, 'in', 'm').get)
1087
+ copper_pipe.setConductivity(386.0)
1088
+ copper_pipe.setDensity(OpenStudio.convert(556, 'lb/ft^3', 'kg/m^3').get)
1089
+ copper_pipe.setSpecificHeat(OpenStudio.convert(0.092, 'Btu/lb*R', 'J/kg*K').get)
1090
+ copper_pipe.setThermalAbsorptance(0.9) # TODO: find reference for property
1091
+ copper_pipe.setSolarAbsorptance(0.7) # TODO: find reference for property
1092
+ copper_pipe.setVisibleAbsorptance(0.7) # TODO: find reference for property
1093
+
1094
+ # Construction for pipe
1095
+ pipe_construction = OpenStudio::Model::Construction.new(model)
1096
+
1097
+ # Add insulation material to insulated pipe
1098
+ if pipe_insulation_thickness > 0
1099
+ # Material for fiberglass insulation
1100
+ # R-value from Owens-Corning 1/2in fiberglass pipe insulation
1101
+ # https://www.grainger.com/product/OWENS-CORNING-1-2-Thick-40PP22
1102
+ # but modified until simulated heat loss = 17.7 Btu/hr/ft of pipe with 140F water and 70F air
1103
+ pipe_insulation_thickness_in = OpenStudio.convert(pipe_insulation_thickness, 'm', 'in').get
1104
+ insulation = OpenStudio::Model::StandardOpaqueMaterial.new(model)
1105
+ insulation.setName("Fiberglass batt #{pipe_insulation_thickness_in.round(2)}in")
1106
+ insulation.setRoughness('Smooth')
1107
+ insulation.setThickness(OpenStudio.convert(pipe_insulation_thickness_in, 'in', 'm').get)
1108
+ insulation.setConductivity(OpenStudio.convert(0.46, 'Btu*in/hr*ft^2*R', 'W/m*K').get)
1109
+ insulation.setDensity(OpenStudio.convert(0.7, 'lb/ft^3', 'kg/m^3').get)
1110
+ insulation.setSpecificHeat(OpenStudio.convert(0.2, 'Btu/lb*R', 'J/kg*K').get)
1111
+ insulation.setThermalAbsorptance(0.9) # Irrelevant for Pipe:Indoor; no radiation model is used
1112
+ insulation.setSolarAbsorptance(0.7) # Irrelevant for Pipe:Indoor; no radiation model is used
1113
+ insulation.setVisibleAbsorptance(0.7) # Irrelevant for Pipe:Indoor; no radiation model is used
1114
+
1115
+ pipe_construction.setName("Copper pipe 0.75in type L with #{pipe_insulation_thickness_in.round(2)}in fiberglass batt")
1116
+ pipe_construction.setLayers([insulation, copper_pipe])
1117
+ else
1118
+ pipe_construction.setName("Uninsulated copper pipe 0.75in type L")
1119
+ pipe_construction.setLayers([copper_pipe])
1120
+ end
1121
+
1122
+ heat_loss_pipe = OpenStudio::Model::PipeIndoor.new(model)
1123
+ heat_loss_pipe.setName("#{swh_loop.name} Pipe #{pipe_length_ft}ft")
1124
+ heat_loss_pipe.setEnvironmentType('Schedule')
1125
+ # heat_loss_pipe.setAmbientTemperatureSchedule(swh_piping_air_temp_sch) # TODO: schedule type registry error for this setter
1126
+ heat_loss_pipe.setPointer(7, swh_piping_air_temp_sch.handle)
1127
+ # heat_loss_pipe.setAmbientAirVelocitySchedule(model.alwaysOffDiscreteSchedule) # TODO: schedule type registry error for this setter
1128
+ heat_loss_pipe.setPointer(8, swh_piping_air_velocity_sch.handle)
1129
+ heat_loss_pipe.setConstruction(pipe_construction)
1130
+ heat_loss_pipe.setPipeInsideDiameter(OpenStudio.convert(0.785, 'in', 'm').get)
1131
+ heat_loss_pipe.setPipeLength(OpenStudio.convert(pipe_length_ft, 'ft', 'm').get)
1132
+
1133
+ heat_loss_pipe.addToNode(swh_loop.demandInletNode)
1134
+
1135
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Added #{pipe_length_ft.round}ft of #{pipe_construction.name} losing heat to #{swh_piping_air_temp_f.round}F air to #{swh_loop.name}.")
1136
+ return true
1137
+ end
1138
+
630
1139
  end