openstudio-standards 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/data/standards/OpenStudio_Standards.xlsx +0 -0
  3. data/data/standards/OpenStudio_Standards_boilers.json +62 -4
  4. data/data/standards/OpenStudio_Standards_chillers.json +778 -68
  5. data/data/standards/OpenStudio_Standards_construction_sets.json +52 -93
  6. data/data/standards/OpenStudio_Standards_curve_biquadratics.json +36 -36
  7. data/data/standards/OpenStudio_Standards_curve_quadratics.json +3 -3
  8. data/data/standards/OpenStudio_Standards_heat_pumps.json +840 -0
  9. data/data/standards/OpenStudio_Standards_heat_pumps_heating.json +352 -0
  10. data/data/standards/OpenStudio_Standards_heat_rejection.json +48 -0
  11. data/data/standards/OpenStudio_Standards_motors.json +270 -0
  12. data/data/standards/OpenStudio_Standards_space_types.json +10390 -2824
  13. data/data/standards/OpenStudio_Standards_unitary_acs.json +794 -18
  14. data/data/weather/USA_CO_Boulder-Broomfield-Jefferson.County.AP.724699_TMY3.ddy +538 -0
  15. data/data/weather/USA_CO_Boulder-Broomfield-Jefferson.County.AP.724699_TMY3.epw +8768 -0
  16. data/data/weather/USA_CO_Boulder-Broomfield-Jefferson.County.AP.724699_TMY3.stat +493 -0
  17. data/data/weather/USA_CO_Denver.Intl.AP.725650_TMY3.ddy +536 -0
  18. data/data/weather/USA_CO_Denver.Intl.AP.725650_TMY3.epw +8768 -0
  19. data/data/weather/USA_CO_Denver.Intl.AP.725650_TMY3.stat +554 -0
  20. data/data/weather/USA_CO_Fort.Collins.AWOS.724769_TMY3.ddy +536 -0
  21. data/data/weather/USA_CO_Fort.Collins.AWOS.724769_TMY3.epw +8768 -0
  22. data/data/weather/USA_CO_Fort.Collins.AWOS.724769_TMY3.stat +554 -0
  23. data/data/weather/envelope_info.csv +6 -0
  24. data/lib/openstudio-standards.rb +10 -11
  25. data/lib/openstudio-standards/btap/compliance.rb +251 -969
  26. data/lib/openstudio-standards/btap/envelope.rb +1 -1
  27. data/lib/openstudio-standards/btap/fileio.rb +37 -5
  28. data/lib/openstudio-standards/btap/geometry.rb +27 -17
  29. data/lib/openstudio-standards/btap/hvac.rb +80 -27
  30. data/lib/openstudio-standards/hvac_sizing/{HVACSizing.CoilHeatingDXMultiSpeed.rb → Siz.CoilHeatingDXMultiSpeed.rb} +0 -0
  31. data/lib/openstudio-standards/hvac_sizing/Siz.ControllerOutdoorAir.rb +30 -4
  32. data/lib/openstudio-standards/hvac_sizing/Siz.CoolingTowerTwoSpeed.rb +61 -5
  33. data/lib/openstudio-standards/hvac_sizing/Siz.CoolingTowerVariableSpeed.rb +37 -7
  34. data/lib/openstudio-standards/hvac_sizing/Siz.DistrictCooling.rb +27 -0
  35. data/lib/openstudio-standards/hvac_sizing/Siz.DistrictHeating.rb +27 -0
  36. data/lib/openstudio-standards/hvac_sizing/Siz.HeaderedPumpsConstantSpeed.rb +55 -0
  37. data/lib/openstudio-standards/hvac_sizing/Siz.HeaderedPumpsVariableSpeed.rb +55 -0
  38. data/lib/openstudio-standards/hvac_sizing/Siz.HeatingCoolingFuels.rb +51 -9
  39. data/lib/openstudio-standards/hvac_sizing/Siz.Model.rb +99 -17
  40. data/lib/openstudio-standards/hvac_sizing/Siz.PumpConstantSpeed.rb +1 -1
  41. data/lib/openstudio-standards/hvac_sizing/Siz.ThermalZone.rb +29 -6
  42. data/lib/openstudio-standards/hvac_sizing/Siz.WaterHeaterMixed.rb +16 -0
  43. data/lib/openstudio-standards/prototypes/Prototype.AirTerminalSingleDuctVAVReheat.rb +43 -48
  44. data/lib/openstudio-standards/prototypes/Prototype.ControllerWaterCoil.rb +5 -9
  45. data/lib/openstudio-standards/prototypes/Prototype.Fan.rb +68 -0
  46. data/lib/openstudio-standards/prototypes/Prototype.FanConstantVolume.rb +39 -43
  47. data/lib/openstudio-standards/prototypes/Prototype.FanOnOff.rb +49 -51
  48. data/lib/openstudio-standards/prototypes/Prototype.FanVariableVolume.rb +55 -61
  49. data/lib/openstudio-standards/prototypes/Prototype.FanZoneExhaust.rb +8 -10
  50. data/lib/openstudio-standards/prototypes/Prototype.HeatExchangerAirToAirSensibleAndLatent.rb +15 -20
  51. data/lib/openstudio-standards/prototypes/Prototype.Model.hvac.rb +330 -322
  52. data/lib/openstudio-standards/prototypes/Prototype.Model.rb +501 -446
  53. data/lib/openstudio-standards/prototypes/Prototype.Model.swh.rb +221 -230
  54. data/lib/openstudio-standards/prototypes/Prototype.add_objects.rb +0 -2
  55. data/lib/openstudio-standards/prototypes/Prototype.full_service_restaurant.rb +130 -137
  56. data/lib/openstudio-standards/prototypes/Prototype.high_rise_apartment.rb +374 -291
  57. data/lib/openstudio-standards/prototypes/Prototype.hospital.rb +146 -193
  58. data/lib/openstudio-standards/prototypes/Prototype.hvac_systems.rb +1315 -1113
  59. data/lib/openstudio-standards/prototypes/Prototype.large_hotel.rb +65 -88
  60. data/lib/openstudio-standards/prototypes/Prototype.large_office.rb +101 -156
  61. data/lib/openstudio-standards/prototypes/Prototype.medium_office.rb +46 -96
  62. data/lib/openstudio-standards/prototypes/Prototype.mid_rise_apartment.rb +113 -123
  63. data/lib/openstudio-standards/prototypes/Prototype.outpatient.rb +356 -345
  64. data/lib/openstudio-standards/prototypes/Prototype.primary_school.rb +48 -103
  65. data/lib/openstudio-standards/prototypes/Prototype.quick_service_restaurant.rb +115 -123
  66. data/lib/openstudio-standards/prototypes/Prototype.retail_standalone.rb +30 -39
  67. data/lib/openstudio-standards/prototypes/Prototype.retail_stripmall.rb +32 -45
  68. data/lib/openstudio-standards/prototypes/Prototype.secondary_school.rb +98 -258
  69. data/lib/openstudio-standards/prototypes/Prototype.small_hotel.rb +429 -474
  70. data/lib/openstudio-standards/prototypes/Prototype.small_office.rb +28 -36
  71. data/lib/openstudio-standards/prototypes/Prototype.strip_model.rb +7 -7
  72. data/lib/openstudio-standards/prototypes/Prototype.utilities.rb +172 -146
  73. data/lib/openstudio-standards/prototypes/Prototype.warehouse.rb +46 -53
  74. data/lib/openstudio-standards/standards/Standards.AirLoopHVAC.rb +885 -707
  75. data/lib/openstudio-standards/standards/Standards.AirTerminalSingleDuctParallelPIUReheat.rb +48 -57
  76. data/lib/openstudio-standards/standards/Standards.AirTerminalSingleDuctVAVReheat.rb +24 -31
  77. data/lib/openstudio-standards/standards/Standards.BoilerHotWater.rb +80 -93
  78. data/lib/openstudio-standards/standards/Standards.BuildingStory.rb +69 -0
  79. data/lib/openstudio-standards/standards/Standards.ChillerElectricEIR.rb +60 -72
  80. data/lib/openstudio-standards/standards/Standards.CoilCoolingDXMultiSpeed.rb +104 -108
  81. data/lib/openstudio-standards/standards/Standards.CoilCoolingDXSingleSpeed.rb +190 -198
  82. data/lib/openstudio-standards/standards/Standards.CoilCoolingDXTwoSpeed.rb +134 -146
  83. data/lib/openstudio-standards/standards/Standards.CoilHeatingDXMultiSpeed.rb +56 -60
  84. data/lib/openstudio-standards/standards/Standards.CoilHeatingDXSingleSpeed.rb +151 -161
  85. data/lib/openstudio-standards/standards/Standards.CoilHeatingGasMultiStage.rb +30 -34
  86. data/lib/openstudio-standards/standards/Standards.Construction.rb +116 -132
  87. data/lib/openstudio-standards/standards/Standards.CoolingTower.rb +138 -0
  88. data/lib/openstudio-standards/standards/Standards.CoolingTowerSingleSpeed.rb +11 -0
  89. data/lib/openstudio-standards/standards/Standards.CoolingTowerTwoSpeed.rb +11 -0
  90. data/lib/openstudio-standards/standards/Standards.CoolingTowerVariableSpeed.rb +16 -0
  91. data/lib/openstudio-standards/standards/Standards.Fan.rb +190 -236
  92. data/lib/openstudio-standards/standards/Standards.FanConstantVolume.rb +0 -2
  93. data/lib/openstudio-standards/standards/Standards.FanOnOff.rb +0 -2
  94. data/lib/openstudio-standards/standards/Standards.FanVariableVolume.rb +168 -14
  95. data/lib/openstudio-standards/standards/Standards.FanZoneExhaust.rb +0 -2
  96. data/lib/openstudio-standards/standards/Standards.HeaderedPumpsConstantSpeed.rb +33 -0
  97. data/lib/openstudio-standards/standards/Standards.HeaderedPumpsVariableSpeed.rb +83 -0
  98. data/lib/openstudio-standards/standards/Standards.HeatExchangerSensLat.rb +22 -0
  99. data/lib/openstudio-standards/standards/Standards.Model.rb +2385 -1622
  100. data/lib/openstudio-standards/standards/Standards.PlanarSurface.rb +83 -35
  101. data/lib/openstudio-standards/standards/Standards.PlantLoop.rb +805 -395
  102. data/lib/openstudio-standards/standards/Standards.Pump.rb +139 -119
  103. data/lib/openstudio-standards/standards/Standards.PumpConstantSpeed.rb +0 -2
  104. data/lib/openstudio-standards/standards/Standards.PumpVariableSpeed.rb +16 -15
  105. data/lib/openstudio-standards/standards/Standards.ScheduleCompact.rb +35 -0
  106. data/lib/openstudio-standards/standards/Standards.ScheduleConstant.rb +7 -13
  107. data/lib/openstudio-standards/standards/Standards.ScheduleRuleset.rb +144 -59
  108. data/lib/openstudio-standards/standards/Standards.Space.rb +1509 -1326
  109. data/lib/openstudio-standards/standards/Standards.SpaceType.rb +254 -262
  110. data/lib/openstudio-standards/standards/Standards.SubSurface.rb +105 -105
  111. data/lib/openstudio-standards/standards/Standards.Surface.rb +27 -31
  112. data/lib/openstudio-standards/standards/Standards.ThermalZone.rb +882 -157
  113. data/lib/openstudio-standards/standards/Standards.WaterHeaterMixed.rb +179 -69
  114. data/lib/openstudio-standards/standards/Standards.ZoneHVACComponent.rb +75 -0
  115. data/lib/openstudio-standards/utilities/logging.rb +31 -38
  116. data/lib/openstudio-standards/utilities/simulation.rb +118 -82
  117. data/lib/openstudio-standards/version.rb +1 -1
  118. data/lib/openstudio-standards/weather/Weather.Model.rb +382 -390
  119. data/lib/openstudio-standards/weather/Weather.stat_file.rb +159 -78
  120. metadata +59 -6
@@ -1,8 +1,7 @@
1
1
  # Reopen the OpenStudio class to add methods to apply standards to this object
2
2
  class OpenStudio::Model::PlanarSurface
3
-
4
- # If construction properties can be found
5
- # based on the template,
3
+ # If construction properties can be found
4
+ # based on the template,
6
5
  # the standards intended surface type,
7
6
  # the standards construction type,
8
7
  # the climate zone, and the occupancy type,
@@ -20,67 +19,116 @@ class OpenStudio::Model::PlanarSurface
20
19
  # [template, climate_zone, intended_surface_type, standards_construction_type, occ_type]
21
20
  # and the value is the newly created construction. This can be
22
21
  # used to avoid creating duplicate constructions.
22
+ # @todo Align the standard construction enumerations in the
23
+ # spreadsheet with the enumerations in OpenStudio (follow CBECC-Com).
23
24
  def apply_standard_construction(template, climate_zone, previous_construction_map = {})
24
-
25
25
  # Skip surfaces not in a space
26
- return previous_construction_map if self.space.empty?
26
+ return previous_construction_map if space.empty?
27
27
  space = self.space.get
28
-
28
+
29
29
  # Skip surfaces that don't have a construction
30
- return previous_construction_map if self.construction.empty?
30
+ return previous_construction_map if construction.empty?
31
31
  construction = self.construction.get
32
-
32
+
33
33
  # Determine if residential or nonresidential
34
34
  # based on the space type.
35
35
  occ_type = 'Nonresidential'
36
- if space.spaceType.is_initialized
37
- space_type = space.spaceType.get
38
- space_type_props = space_type.get_standards_data(template)
39
- if space_type_props['is_residential']
40
- occ_type = 'Residential'
41
- end
36
+ if space.residential?(template)
37
+ occ_type = 'Residential'
42
38
  end
43
-
39
+
44
40
  # Get the climate zone set
45
- climate_zone_set = self.model.find_climate_zone_set(climate_zone, template)
46
-
41
+ climate_zone_set = model.find_climate_zone_set(climate_zone, template)
42
+
47
43
  # Get the intended surface type
48
44
  standards_info = construction.standardsInformation
49
- intended_surface_type = standards_info.intendedSurfaceType
50
- standards_construction_type = standards_info.standardsConstructionType
51
- if intended_surface_type.empty? || standards_construction_type.empty?
52
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.PlanarSurface", "Could not determine one or more of the intended surface type or the standards construction type for #{self.name}. This surface will not have the standard applied.")
45
+ surf_type = standards_info.intendedSurfaceType
46
+ if surf_type.empty?
47
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.PlanarSurface', "Could not determine the intended surface type for #{name} from #{construction.name}. This surface will not have the standard applied.")
53
48
  return previous_construction_map
54
49
  end
50
+ surf_type = surf_type.get
51
+
52
+ # Get the standards type, which is based on different fields
53
+ # if is intended for a window, a skylight, or something else.
54
+ # Mapping is between standards-defined enumerations and the
55
+ # enumerations available in OpenStudio.
56
+ stds_type = nil
57
+ # Windows
58
+ if surf_type == 'ExteriorWindow'
59
+ stds_type = standards_info.fenestrationFrameType
60
+ if stds_type.is_initialized
61
+ stds_type = stds_type.get
62
+ case stds_type
63
+ when 'Metal Framing', 'Metal Framing with Thermal Break'
64
+ stds_type = 'Metal framing (all other)'
65
+ when 'Non-Metal Framing'
66
+ stds_type = 'Nonmetal framing (all)'
67
+ else
68
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.PlanarSurface', "The standards fenestration frame type #{stds_type} cannot be used on #{surf_type} in #{name}. This surface will not have the standard applied.")
69
+ return previous_construction_map
70
+ end
71
+ else
72
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.PlanarSurface', "Could not determine the standards fenestration frame type for #{name} from #{construction.name}. This surface will not have the standard applied.")
73
+ return previous_construction_map
74
+ end
75
+ # Skylights
76
+ elsif surf_type == 'Skylight'
77
+ stds_type = standards_info.fenestrationType
78
+ if stds_type.is_initialized
79
+ stds_type = stds_type.get
80
+ case stds_type
81
+ when 'Glass Skylight with Curb'
82
+ stds_type = 'Glass with Curb'
83
+ when 'Plastic Skylight with Curb'
84
+ stds_type = 'Plastic with Curb'
85
+ when 'Plastic Skylight without Curb', 'Glass Skylight without Curb'
86
+ stds_type = 'Without Curb'
87
+ else
88
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.PlanarSurface', "The standards fenestration type #{stds_type} cannot be used on #{surf_type} in #{name}. This surface will not have the standard applied.")
89
+ return previous_construction_map
90
+ end
91
+ else
92
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.PlanarSurface', "Could not determine the standards fenestration type for #{name} from #{construction.name}. This surface will not have the standard applied.")
93
+ return previous_construction_map
94
+ end
95
+ # All other surface types
96
+ else
97
+ stds_type = standards_info.standardsConstructionType
98
+ if stds_type.is_initialized
99
+ stds_type = stds_type.get
100
+ else
101
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.PlanarSurface', "Could not determine the standards construction type for #{name}. This surface will not have the standard applied.")
102
+ return previous_construction_map
103
+ end
104
+ end
55
105
 
56
106
  # Check if the construction type was already created.
57
107
  # If yes, use that construction. If no, make a new one.
58
108
  new_construction = nil
59
- type = [template, climate_zone, intended_surface_type.get, standards_construction_type.get, occ_type]
109
+ type = [template, climate_zone, surf_type, stds_type, occ_type]
60
110
  if previous_construction_map[type]
61
111
  new_construction = previous_construction_map[type]
62
112
  else
63
- new_construction = self.model.find_and_add_construction(template,
64
- climate_zone_set,
65
- intended_surface_type.get,
66
- standards_construction_type.get,
67
- occ_type)
113
+ new_construction = model.find_and_add_construction(template,
114
+ climate_zone_set,
115
+ surf_type,
116
+ stds_type,
117
+ occ_type)
68
118
  if !new_construction == false
69
119
  previous_construction_map[type] = new_construction
70
120
  end
71
121
  end
72
-
122
+
73
123
  # Assign the new construction to the surface
74
124
  if new_construction
75
- self.setConstruction(new_construction)
76
- OpenStudio::logFree(OpenStudio::Debug, "openstudio.model.PlanarSurface", "Set the construction for #{self.name} to #{new_construction.name}.")
125
+ setConstruction(new_construction)
126
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.model.PlanarSurface', "Set the construction for #{name} to #{new_construction.name}.")
77
127
  else
78
- OpenStudio::logFree(OpenStudio::Warn, "openstudio.model.PlanarSurface", "Could not generate a standard construction for #{self.name}.")
128
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.model.PlanarSurface', "Could not generate a standard construction for #{name}.")
79
129
  return previous_construction_map
80
130
  end
81
-
82
- return previous_construction_map
83
-
84
- end
85
131
 
132
+ return previous_construction_map
133
+ end
86
134
  end
@@ -1,95 +1,83 @@
1
1
 
2
2
  # Reopen the OpenStudio class to add methods to apply standards to this object
3
3
  class OpenStudio::Model::PlantLoop
4
-
5
4
  # Apply all standard required controls to the plantloop
6
5
  #
7
- # @param (see #is_economizer_required)
6
+ # @param (see #economizer_required?)
8
7
  # @return [Bool] returns true if successful, false if not
9
8
  def apply_standard_controls(template, climate_zone)
10
-
11
9
  # Variable flow system
12
- if self.is_variable_flow_required(template)
13
- self.enable_variable_flow(template)
14
- end
15
-
16
- # Supply water temperature reset
17
- if self.is_supply_water_temperature_reset_required(template)
18
- self.enable_supply_water_temperature_reset
19
- end
10
+ enable_variable_flow(template) if is_variable_flow_required(template)
20
11
 
21
- end
12
+ # Supply water temperature reset
13
+ enable_supply_water_temperature_reset if supply_water_temperature_reset_required?(template)
14
+ end
22
15
 
23
16
  def enable_variable_flow(template)
24
-
25
17
  end
26
-
27
- def is_variable_flow_system
28
-
18
+
19
+ def variable_flow_system?
29
20
  variable_flow = false
30
-
21
+
31
22
  # Modify all the primary pumps
32
- self.supplyComponents.each do |sc|
23
+ supplyComponents.each do |sc|
33
24
  if sc.to_PumpVariableSpeed.is_initialized
34
25
  variable_flow = true
35
26
  end
36
27
  end
37
-
28
+
38
29
  # Modify all the secondary pumps
39
- self.demandComponents.each do |sc|
30
+ demandComponents.each do |sc|
40
31
  if sc.to_PumpVariableSpeed.is_initialized
41
32
  variable_flow = true
42
33
  end
43
34
  end
44
-
35
+
45
36
  return variable_flow
46
-
47
37
  end
48
38
 
49
-
50
- # Todo: I think it makes more sense to sense the motor efficiency right there...
39
+ # TODO: I think it makes more sense to sense the motor efficiency right there...
51
40
  # But actually it's completely irrelevant... you could set at 0.9 and just calculate the pressurise rise to have your 19 W/GPM or whatever
52
- def apply_performance_rating_method_baseline_pump_power(template)
53
-
41
+ def apply_prm_baseline_pump_power(template)
54
42
  # Determine the pumping power per
55
43
  # flow based on loop type.
56
44
  pri_w_per_gpm = nil
57
45
  sec_w_per_gpm = nil
58
46
 
59
- sizing_plant = self.sizingPlant
60
- loop_type = sizing_plant.loopType
61
-
47
+ sizing_plant = sizingPlant
48
+ loop_type = sizing_plant.loopType
49
+
62
50
  case loop_type
63
51
  when 'Heating'
64
-
52
+
65
53
  has_district_heating = false
66
- self.supplyComponents.each do |sc|
54
+ supplyComponents.each do |sc|
67
55
  if sc.to_DistrictHeating.is_initialized
68
56
  has_district_heating = true
69
57
  end
70
58
  end
71
59
 
72
- if has_district_heating # District HW
73
- pri_w_per_gpm = 14.0
74
- else # HW
75
- pri_w_per_gpm = 19.0
76
- end
60
+ pri_w_per_gpm = if has_district_heating # District HW
61
+ 14.0
62
+ else # HW
63
+ 19.0
64
+ end
77
65
 
78
66
  when 'Cooling'
79
-
67
+
80
68
  has_district_cooling = false
81
- self.supplyComponents.each do |sc|
69
+ supplyComponents.each do |sc|
82
70
  if sc.to_DistrictCooling.is_initialized
83
71
  has_district_cooling = true
84
72
  end
85
73
  end
86
-
74
+
87
75
  has_secondary_pump = false
88
- self.demandComponents.each do |sc|
76
+ demandComponents.each do |sc|
89
77
  if sc.to_PumpConstantSpeed.is_initialized || sc.to_PumpVariableSpeed.is_initialized
90
78
  has_secondary_pump = true
91
79
  end
92
- end
80
+ end
93
81
 
94
82
  if has_district_cooling # District CHW
95
83
  pri_w_per_gpm = 16.0
@@ -101,73 +89,83 @@ class OpenStudio::Model::PlantLoop
101
89
  end
102
90
 
103
91
  when 'Condenser'
104
-
105
- # TODO prm condenser loop pump power
92
+
93
+ # TODO: prm condenser loop pump power
106
94
  pri_w_per_gpm = 19.0
107
95
 
108
96
  end
109
-
97
+
110
98
  # Modify all the primary pumps
111
- self.supplyComponents.each do |sc|
99
+ supplyComponents.each do |sc|
112
100
  if sc.to_PumpConstantSpeed.is_initialized
113
101
  pump = sc.to_PumpConstantSpeed.get
114
- pump.set_performance_rating_method_pressure_rise_and_motor_efficiency(pri_w_per_gpm, template)
102
+ pump.apply_prm_pressure_rise_and_motor_efficiency(pri_w_per_gpm, template)
115
103
  elsif sc.to_PumpVariableSpeed.is_initialized
116
104
  pump = sc.to_PumpVariableSpeed.get
117
- pump.set_performance_rating_method_pressure_rise_and_motor_efficiency(pri_w_per_gpm, template)
105
+ pump.apply_prm_pressure_rise_and_motor_efficiency(pri_w_per_gpm, template)
106
+ elsif sc.to_HeaderedPumpsConstantSpeed.is_initialized
107
+ pump = sc.to_HeaderedPumpsConstantSpeed.get
108
+ pump.apply_prm_pressure_rise_and_motor_efficiency(pri_w_per_gpm, template)
109
+ elsif sc.to_HeaderedPumpsVariableSpeed.is_initialized
110
+ pump = sc.to_HeaderedPumpsVariableSpeed.get
111
+ pump.apply_prm_pressure_rise_and_motor_efficiency(pri_w_per_gpm, template)
118
112
  end
119
113
  end
120
-
114
+
121
115
  # Modify all the secondary pumps
122
- self.demandComponents.each do |sc|
116
+ demandComponents.each do |sc|
123
117
  if sc.to_PumpConstantSpeed.is_initialized
124
118
  pump = sc.to_PumpConstantSpeed.get
125
- pump.set_performance_rating_method_pressure_rise_and_motor_efficiency(sec_w_per_gpm, template)
119
+ pump.apply_prm_pressure_rise_and_motor_efficiency(sec_w_per_gpm, template)
126
120
  elsif sc.to_PumpVariableSpeed.is_initialized
127
121
  pump = sc.to_PumpVariableSpeed.get
128
- pump.set_performance_rating_method_pressure_rise_and_motor_efficiency(sec_w_per_gpm, template)
122
+ pump.apply_prm_pressure_rise_and_motor_efficiency(sec_w_per_gpm, template)
123
+ elsif sc.to_HeaderedPumpsConstantSpeed.is_initialized
124
+ pump = sc.to_HeaderedPumpsConstantSpeed.get
125
+ pump.apply_prm_pressure_rise_and_motor_efficiency(pri_w_per_gpm, template)
126
+ elsif sc.to_HeaderedPumpsVariableSpeed.is_initialized
127
+ pump = sc.to_HeaderedPumpsVariableSpeed.get
128
+ pump.apply_prm_pressure_rise_and_motor_efficiency(pri_w_per_gpm, template)
129
129
  end
130
130
  end
131
-
131
+
132
132
  return true
133
-
134
133
  end
135
-
136
- def apply_performance_rating_method_baseline_temperatures(template)
137
-
138
- sizing_plant = self.sizingPlant
134
+
135
+ def apply_prm_baseline_temperatures(template)
136
+ sizing_plant = sizingPlant
139
137
  loop_type = sizing_plant.loopType
140
138
  case loop_type
141
139
  when 'Heating'
142
140
 
143
141
  # Loop properties
144
- # G3.1.3.3 - HW Supply at 180°F, return at 130°F
142
+ # G3.1.3.3 - HW Supply at 180F, return at 130F
145
143
  hw_temp_f = 180
146
144
  hw_delta_t_r = 50
147
145
  min_temp_f = 50
148
-
149
- hw_temp_c = OpenStudio.convert(hw_temp_f,'F','C').get
150
- hw_delta_t_k = OpenStudio.convert(hw_delta_t_r,'R','K').get
151
- min_temp_c = OpenStudio.convert(min_temp_f,'F','C').get
146
+
147
+ hw_temp_c = OpenStudio.convert(hw_temp_f, 'F', 'C').get
148
+ hw_delta_t_k = OpenStudio.convert(hw_delta_t_r, 'R', 'K').get
149
+ min_temp_c = OpenStudio.convert(min_temp_f, 'F', 'C').get
152
150
 
153
151
  sizing_plant.setDesignLoopExitTemperature(hw_temp_c)
154
152
  sizing_plant.setLoopDesignTemperatureDifference(hw_delta_t_k)
155
- self.setMinimumLoopTemperature(min_temp_c)
153
+ setMinimumLoopTemperature(min_temp_c)
156
154
 
157
155
  # ASHRAE Appendix G - G3.1.3.4 (for ASHRAE 90.1-2004, 2007 and 2010)
158
- # HW reset: 180°F at 20°F and below, 150°F at 50°F and above
159
- self.enable_supply_water_temperature_reset
156
+ # HW reset: 180F at 20F and below, 150F at 50F and above
157
+ enable_supply_water_temperature_reset
160
158
 
161
159
  # Boiler properties
162
- self.supplyComponents.each do |sc|
160
+ supplyComponents.each do |sc|
163
161
  if sc.to_BoilerHotWater.is_initialized
164
162
  boiler = sc.to_BoilerHotWater.get
165
163
  boiler.setDesignWaterOutletTemperature(hw_temp_c)
166
164
  end
167
- end
168
-
165
+ end
166
+
169
167
  when 'Cooling'
170
-
168
+
171
169
  # Loop properties
172
170
  # G3.1.3.8 - LWT 44 / EWT 56
173
171
  chw_temp_f = 44
@@ -177,231 +175,288 @@ class OpenStudio::Model::PlantLoop
177
175
  # For water-cooled chillers this is the water temperature entering the condenser (e.g., leaving the cooling tower).
178
176
  ref_cond_wtr_temp_f = 85
179
177
 
180
- chw_temp_c = OpenStudio.convert(chw_temp_f,'F','C').get
181
- chw_delta_t_k = OpenStudio.convert(chw_delta_t_r,'R','K').get
182
- min_temp_c = OpenStudio.convert(min_temp_f,'F','C').get
183
- max_temp_c = OpenStudio.convert(max_temp_f,'F','C').get
184
- ref_cond_wtr_temp_c = OpenStudio.convert(ref_cond_wtr_temp_f,'F','C').get
178
+ chw_temp_c = OpenStudio.convert(chw_temp_f, 'F', 'C').get
179
+ chw_delta_t_k = OpenStudio.convert(chw_delta_t_r, 'R', 'K').get
180
+ min_temp_c = OpenStudio.convert(min_temp_f, 'F', 'C').get
181
+ max_temp_c = OpenStudio.convert(max_temp_f, 'F', 'C').get
182
+ ref_cond_wtr_temp_c = OpenStudio.convert(ref_cond_wtr_temp_f, 'F', 'C').get
185
183
 
186
184
  sizing_plant.setDesignLoopExitTemperature(chw_temp_c)
187
185
  sizing_plant.setLoopDesignTemperatureDifference(chw_delta_t_k)
188
- self.setMinimumLoopTemperature(min_temp_c)
189
- self.setMaximumLoopTemperature(max_temp_c)
186
+ setMinimumLoopTemperature(min_temp_c)
187
+ setMaximumLoopTemperature(max_temp_c)
190
188
 
191
189
  # ASHRAE Appendix G - G3.1.3.9 (for ASHRAE 90.1-2004, 2007 and 2010)
192
- # ChW reset: 44°F at 80°F and above, 54°F at 60°F and below
193
- self.enable_supply_water_temperature_reset
194
-
190
+ # ChW reset: 44F at 80F and above, 54F at 60F and below
191
+ enable_supply_water_temperature_reset
192
+
195
193
  # Chiller properties
196
- self.supplyComponents.each do |sc|
194
+ supplyComponents.each do |sc|
197
195
  if sc.to_ChillerElectricEIR.is_initialized
198
196
  chiller = sc.to_ChillerElectricEIR.get
199
197
  chiller.setReferenceLeavingChilledWaterTemperature(chw_temp_c)
200
198
  chiller.setReferenceEnteringCondenserFluidTemperature(ref_cond_wtr_temp_c)
201
199
  end
202
200
  end
203
-
201
+
204
202
  when 'Condenser'
205
-
206
- # G3.1.3.11 - LCnWT 85°F or 10°F approaching design wet bulb temperature, whichever is lower
207
- # Design Temperature rise of 10°F => Range: 10°F
208
- lcnwt_f = 85 # See notes and proposed alternative below, if we want to actually check the design days...
209
- range_t_r = 10
210
- lcnwt_c = OpenStudio.convert(lcnwt_f,'F','C').get
211
- range_t_k = OpenStudio.convert(range_t_r,'R','K').get
212
-
213
- # Typical design of min temp is really around 40°F (that's what basin heaters, when used, are sized for usually)
214
- min_temp_f = 34
215
- max_temp_f = 200
216
- min_temp_c = OpenStudio.convert(min_temp_f,'F','C').get
217
- max_temp_c = OpenStudio.convert(max_temp_f,'F','C').get
218
203
 
219
- sizing_plant.setDesignLoopExitTemperature(lcnwt_c)
220
- sizing_plant.setLoopDesignTemperatureDifference(range_t_k)
221
- self.setMinimumLoopTemperature(min_temp_c)
222
- self.setMaximumLoopTemperature(max_temp_c)
204
+ # Much of the thought in this section
205
+ # came from @jmarrec
206
+
207
+ # Determine the design OATwb from the design days.
208
+ # Per https://unmethours.com/question/16698/which-cooling-design-day-is-most-common-for-sizing-rooftop-units/
209
+ # the WB=>MDB day is used to size cooling towers.
210
+ summer_oat_wbs_f = []
211
+ model.getDesignDays.each do |dd|
212
+ next unless dd.dayType == 'SummerDesignDay'
213
+ next unless dd.name.get.to_s.include?('WB=>MDB')
214
+ if dd.humidityIndicatingType == 'Wetbulb'
215
+ summer_oat_wb_c = dd.humidityIndicatingConditionsAtMaximumDryBulb
216
+ summer_oat_wbs_f << OpenStudio.convert(summer_oat_wb_c, 'C', 'F').get
217
+ else
218
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.PlantLoop', "For #{dd.name}, humidity is specified as #{dd.humidityIndicatingType}; cannot determine Twb.")
219
+ end
220
+ end
223
221
 
224
- # G3.1.3.11 - Tower shall be controlled to maintain a 70°F LCnWT where weather permits
225
- # Use a SetpointManager:FollowOutdoorAirTemperature
226
- float_down_to_f = 70
227
- float_down_to_c = OpenStudio.convert(float_down_to_f,'F','C').get
228
-
229
- # Todo: Problem is what to set for Offset Temperature Difference (=approach):
230
- # * if unreasonably low approach, fan runs full blast and energy consumption is penalized
231
- # * if too high, you don't get as much energy savings...
232
- # "LCnWT 85°F or 10°F approaching design wet bulb temperature, whichever is lower" ==> approach is maximum 10, could be less depending on design conditions
233
- # In most cases in the US a tower will be sized on CTI conditions, 78°F WB, so usually 7°F approach.
234
- # Could also check the design days, but begs the question of finding the right one to begin with if you have several...
235
- # You'll need to deal with potentially different 'Humidity Indicating Type'
236
- #
237
- # See https://unmethours.com/question/12530/appendix-g-condenser-water-temperature-reset-in-energyplus/
238
- # See http://www.comnet.org/mgp/content/cooling-towers?purpose=0#footnote1_do6jpuh
222
+ # Use the value from the design days or
223
+ # 78F, the CTI rating condition, if no
224
+ # design day information is available.
225
+ design_oat_wb_f = nil
226
+ if summer_oat_wbs_f.size.zero?
227
+ design_oat_wb_f = 78
228
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.PlantLoop', "For #{name}, no design day OATwb conditions were found. CTI rating condition of 78F OATwb will be used for sizing cooling towers.")
229
+ else
230
+ # Take worst case condition
231
+ design_oat_wb_f = summer_oat_wbs_f.max
232
+ end
239
233
 
240
- # This is an example of how jmarrec would implement checking the design days
241
- =begin
242
- summer_dday_wbs = []
243
- model.getDesignDays.each do |dd|
244
- model.getDesignDays.each do |dd|
245
- if dd.dayType == 'SummerDesignDay' && dd.humidityIndicatingType == 'Wetbulb'
246
- summer_dday_wbs << dd.humidityIndicatingConditionsAtMaximumDryBulb
247
- end
248
- end
249
- end
234
+ # There is an EnergyPlus model limitation
235
+ # that the design_oat_wb_f < 80F
236
+ # for cooling towers
237
+ ep_max_design_oat_wb_f = 80
238
+ if design_oat_wb_f > ep_max_design_oat_wb_f
239
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.PlantLoop', "For #{name}, reduced design OATwb from #{design_oat_wb_f} F to E+ model max input of #{ep_max_design_oat_wb_f} F.")
240
+ design_oat_wb_f = ep_max_design_oat_wb_f
241
+ end
242
+
243
+ # Determine the design CW temperature, approach, and range
244
+ leaving_cw_t_f = nil
245
+ approach_r = nil
246
+ range_r = nil
247
+ case template
248
+ when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004', '90.1-2004', '90.1-2007', '90.1-2010'
249
+ # G3.1.3.11 - CW supply temp = 85F or 10F approaching design wet bulb temperature,
250
+ # whichever is lower. Design range = 10F
251
+ # Design Temperature rise of 10F => Range: 10F
252
+ range_r = 10
253
+
254
+ # Determine the leaving CW temp
255
+ max_leaving_cw_t_f = 85
256
+ leaving_cw_t_10f_approach_f = design_oat_wb_f + 10
257
+ leaving_cw_t_f = [max_leaving_cw_t_f, leaving_cw_t_10f_approach_f].max
258
+
259
+ # Calculate the approach
260
+ approach_r = leaving_cw_t_f - design_oat_wb_f
261
+
262
+ when '90.1-2013'
263
+ # G3.1.3.11 - CW supply temp shall be evaluated at 0.4% evaporative design OATwb
264
+ # per the formulat approach_F = 25.72 - (0.24 * OATwb_F)
265
+ # 55F <= OATwb <= 90F
266
+ # Design range = 10F.
267
+ range_r = 10
268
+
269
+ # Limit the OATwb
270
+ if design_oat_wb_f < 55
271
+ design_oat_wb_f = 55
272
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{name}, a design OATwb of 55F will be used for sizing the cooling towers because the actual design value is below the limit in G3.1.3.11.")
273
+ elsif design_oat_wb_f > 90
274
+ design_oat_wb_f = 90
275
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{name}, a design OATwb of 90F will be used for sizing the cooling towers because the actual design value is above the limit in G3.1.3.11.")
276
+ end
250
277
 
251
- # Then take worst case condition (max), or the average?
252
- design_inlet_wb_c = summer_dday_wbs.max
253
- design_inlet_wb_f = OpenStudio.convert(design_inlet_wb_c,'C','F').get
254
- lcnwt_f = 85
255
- lcnwt_10f_approach = design_inlet_wb_f+10
256
- lcnwt_f = lcnwt_10f_approach if lcnwt_10f_approach < 85
278
+ # Calculate the approach
279
+ approach_r = 25.72 - (0.24 * design_oat_wb_f)
257
280
 
281
+ # Calculate the leaving CW temp
282
+ leaving_cw_t_f = design_oat_wb_f + approach_r
258
283
 
284
+ end
259
285
 
260
- =end
286
+ # Report out design conditions
287
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{name}, design OATwb = #{design_oat_wb_f.round(1)} F, approach = #{approach_r.round(1)} deltaF, range = #{range_r.round(1)} deltaF, leaving condenser water temperature = #{leaving_cw_t_f.round(1)} F.")
261
288
 
262
- design_inlet_wb_f = 78
263
- design_approach_r = 7
264
- design_inlet_wb_c = OpenStudio.convert(design_inlet_wb_f,'F','C').get
265
- design_approach_k = OpenStudio.convert(design_approach_r,'R','K').get
289
+ # Convert to SI units
290
+ leaving_cw_t_c = OpenStudio.convert(leaving_cw_t_f, 'F', 'C').get
291
+ approach_k = OpenStudio.convert(approach_r, 'R', 'K').get
292
+ range_k = OpenStudio.convert(range_r, 'R', 'K').get
293
+ design_oat_wb_c = OpenStudio.convert(design_oat_wb_f, 'F', 'C').get
266
294
 
267
- cw_t_stpt_manager = OpenStudio::Model::SetpointManagerFollowOutdoorAirTemperature.new(self.model)
268
- cw_t_stpt_manager.setReferenceTemperatureType('OutdoorAirWetBulb')
269
- cw_t_stpt_manager.setMaximumSetpointTemperature(lcnwt_c)
270
- cw_t_stpt_manager.setMinimumSetpointTemperature(float_down_to_c)
271
- cw_t_stpt_manager.setOffsetTemperatureDifference(design_approach_k)
272
- cw_t_stpt_manager.addToNode(self.supplyOutletNode)
273
-
274
- # Cooling Tower properties
275
- self.supplyComponents.each do |sc|
276
- if sc.to_CoolingTowerSingleSpeed.is_initialized
277
- ct = sc.to_CoolingTowerSingleSpeed.get
278
- ct.setDesignInletAirWetBulbTemperature(design_inlet_wb_c)
279
- ct.setDesignApproachTemperature(design_approach_k)
280
- ct.setDesignRangeTemperature(range_t_k)
281
- elsif sc.to_CoolingTowerTwoSpeed.is_initialized
282
- ct = sc.to_CoolingTowerTwoSpeed.get
283
- ct.setDesignInletAirWetBulbTemperature(design_inlet_wb_c)
284
- ct.setDesignApproachTemperature(design_approach_k)
285
- ct.setDesignRangeTemperature(range_t_k)
286
- elsif sc.to_CoolingTowerVariableSpeed.is_initialized
295
+ # Set the CW sizing parameters
296
+ sizing_plant.setDesignLoopExitTemperature(leaving_cw_t_c)
297
+ sizing_plant.setLoopDesignTemperatureDifference(range_k)
298
+
299
+ # Set Cooling Tower sizing parameters.
300
+ # Only the variable speed cooling tower
301
+ # in E+ allows you to set the design temperatures.
302
+ #
303
+ # Per the documentation
304
+ # http://bigladdersoftware.com/epx/docs/8-4/input-output-reference/group-condenser-equipment.html#field-design-u-factor-times-area-value
305
+ # for CoolingTowerSingleSpeed and CoolingTowerTwoSpeed
306
+ # E+ uses the following values during sizing:
307
+ # 95F entering water temp
308
+ # 95F OATdb
309
+ # 78F OATwb
310
+ # range = loop design delta-T aka range (specified above)
311
+ supplyComponents.each do |sc|
312
+ if sc.to_CoolingTowerVariableSpeed.is_initialized
287
313
  ct = sc.to_CoolingTowerVariableSpeed.get
288
- ct.setDesignInletAirWetBulbTemperature(design_inlet_wb_c)
289
- ct.setDesignApproachTemperature(design_approach_k)
290
- ct.setDesignRangeTemperature(range_t_k)
291
- elsif sc.to_CoolingTowerPerformanceYorkCalc.is_initialized
292
- ct = sc.to_CoolingTowerPerformanceYorkCalc.get
293
- ct.setDesignInletAirWetBulbTemperature(design_inlet_wb_c)
294
- ct.setDesignApproachTemperature(design_approach_k)
295
- ct.setDesignRangeTemperature(range_t_k)
296
- elsif sc.to_CoolingTowerPerformanceCoolTools.is_initialized
297
- ct = sc.to_CoolingTowerPerformanceCoolTools.get
298
- ct.setDesignInletAirWetBulbTemperature(design_inlet_wb_c)
299
- ct.setDesignApproachTemperature(design_approach_k)
300
- ct.setDesignRangeTemperature(range_t_k)
314
+ # E+ has a minimum limit of 68F (20C) for this field.
315
+ # Check against limit before attempting to set value.
316
+ eplus_design_oat_wb_c_lim = 20
317
+ if design_oat_wb_c < eplus_design_oat_wb_c_lim
318
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{name}, a design OATwb of 68F will be used for sizing the cooling towers because the actual design value is below the limit EnergyPlus accepts for this input.")
319
+ design_oat_wb_c = eplus_design_oat_wb_c_lim
320
+ end
321
+ ct.setDesignInletAirWetBulbTemperature(design_oat_wb_c)
322
+ ct.setDesignApproachTemperature(approach_k)
323
+ ct.setDesignRangeTemperature(range_k)
301
324
  end
325
+ end
302
326
 
327
+ # Set the min and max CW temps
328
+ # Typical design of min temp is really around 40F
329
+ # (that's what basin heaters, when used, are sized for usually)
330
+ min_temp_f = 34
331
+ max_temp_f = 200
332
+ min_temp_c = OpenStudio.convert(min_temp_f, 'F', 'C').get
333
+ max_temp_c = OpenStudio.convert(max_temp_f, 'F', 'C').get
334
+ setMinimumLoopTemperature(min_temp_c)
335
+ setMaximumLoopTemperature(max_temp_c)
336
+
337
+ # Cooling Tower operational controls
338
+ # G3.1.3.11 - Tower shall be controlled to maintain a 70F
339
+ # LCnWT where weather permits,
340
+ # floating up to leaving water at design conditions.
341
+ float_down_to_f = 70
342
+ float_down_to_c = OpenStudio.convert(float_down_to_f, 'F', 'C').get
343
+ cw_t_stpt_manager = OpenStudio::Model::SetpointManagerFollowOutdoorAirTemperature.new(model)
344
+ cw_t_stpt_manager.setName("CW Temp Follows OATwb w/ #{approach_r} deltaF approach min #{float_down_to_f.round(1)} F to max #{leaving_cw_t_f.round(1)}")
345
+ cw_t_stpt_manager.setReferenceTemperatureType('OutdoorAirWetBulb')
346
+ # At low design OATwb, it is possible to calculate
347
+ # a maximum temperature below the minimum. In this case,
348
+ # make the maximum and minimum the same.
349
+ if leaving_cw_t_c < float_down_to_c
350
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.PlantLoop', "For #{name}, the maximum leaving temperature of #{leaving_cw_t_f.round(1)} F is below the minimum of #{float_down_to_f.round(1)} F. The maximum will be set to the same value as the minimum.")
351
+ leaving_cw_t_c = float_down_to_c
303
352
  end
353
+ cw_t_stpt_manager.setMaximumSetpointTemperature(leaving_cw_t_c)
354
+ cw_t_stpt_manager.setMinimumSetpointTemperature(float_down_to_c)
355
+ cw_t_stpt_manager.setOffsetTemperatureDifference(approach_k)
356
+ cw_t_stpt_manager.addToNode(supplyOutletNode)
304
357
 
305
358
  end
306
-
359
+
307
360
  return true
308
-
309
361
  end
310
-
311
- def is_supply_water_temperature_reset_required(template)
312
362
 
363
+ def supply_water_temperature_reset_required?(template)
313
364
  reset_required = false
314
365
 
315
366
  case template
316
367
  when 'DOE Ref Pre-1980', 'DOE Ref 1980-2004'
317
-
368
+
318
369
  # Not required before 90.1-2004
319
370
  return reset_required
320
-
371
+
321
372
  when '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013'
322
-
373
+
374
+ # Not required for service water heating systems
375
+ if swh_loop?
376
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{name}: supply water temperature reset not required for service water heating systems.")
377
+ return reset_required
378
+ end
379
+
323
380
  # Not required for variable flow systems
324
- if is_variable_flow_system
325
- OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{self.name}: supply water temperature reset not required for variable flow systems per 6.5.4.3 Exception b.")
381
+ if variable_flow_system?
382
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{name}: supply water temperature reset not required for variable flow systems per 6.5.4.3 Exception b.")
326
383
  return reset_required
327
384
  end
328
-
385
+
329
386
  # Determine the capacity of the system
330
- heating_capacity_w = self.total_heating_capacity
331
- cooling_capacity_w = self.total_cooling_capacity
332
-
333
- heating_capacity_btu_per_hr = OpenStudio.convert(heating_capacity_w,'W','Btu/hr').get
334
- cooling_capacity_btu_per_hr = OpenStudio.convert(cooling_capacity_w,'W','Btu/hr').get
335
-
387
+ heating_capacity_w = total_heating_capacity
388
+ cooling_capacity_w = total_cooling_capacity
389
+
390
+ heating_capacity_btu_per_hr = OpenStudio.convert(heating_capacity_w, 'W', 'Btu/hr').get
391
+ cooling_capacity_btu_per_hr = OpenStudio.convert(cooling_capacity_w, 'W', 'Btu/hr').get
392
+
336
393
  # Compare against capacity minimum requirement
337
- min_cap_btu_per_hr = 300000
338
- if heating_capacity_btu_per_hr > min_cap_btu_per_hr
339
- OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{self.name}: supply water temperature reset is required because heating capacity of #{heating_capacity_btu_per_hr.round} Btu/hr exceeds the minimum threshold of #{min_cap_btu_per_hr.round} Btu/hr.")
394
+ min_cap_btu_per_hr = 300_000
395
+ if heating_capacity_btu_per_hr > min_cap_btu_per_hr
396
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{name}: supply water temperature reset is required because heating capacity of #{heating_capacity_btu_per_hr.round} Btu/hr exceeds the minimum threshold of #{min_cap_btu_per_hr.round} Btu/hr.")
340
397
  reset_required = true
341
398
  elsif cooling_capacity_btu_per_hr > min_cap_btu_per_hr
342
- OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{self.name}: supply water temperature reset is required because cooling capacity of #{cooling_capacity_btu_per_hr.round} Btu/hr exceeds the minimum threshold of #{min_cap_btu_per_hr.round} Btu/hr.")
399
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{name}: supply water temperature reset is required because cooling capacity of #{cooling_capacity_btu_per_hr.round} Btu/hr exceeds the minimum threshold of #{min_cap_btu_per_hr.round} Btu/hr.")
343
400
  reset_required = true
344
401
  else
345
- OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{self.name}: supply water temperature reset is not required because capacity is less than minimum of #{min_cap_btu_per_hr.round} Btu/hr.")
402
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{name}: supply water temperature reset is not required because capacity is less than minimum of #{min_cap_btu_per_hr.round} Btu/hr.")
346
403
  end
347
-
404
+
348
405
  end
349
406
 
350
407
  return reset_required
351
-
352
408
  end
353
-
409
+
354
410
  def enable_supply_water_temperature_reset
355
-
356
411
  # Get the current setpoint manager on the outlet node
357
412
  # and determine if already has temperature reset
358
- spms = self.supplyOutletNode.setpointManagers
413
+ spms = supplyOutletNode.setpointManagers
359
414
  spms.each do |spm|
360
- if spm.to_SetpointManagerOutdoorAirReset
361
- OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{self.name}: supply water temperature reset is already enabled.")
415
+ if spm.to_SetpointManagerOutdoorAirReset.is_initialized
416
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{name}: supply water temperature reset is already enabled.")
362
417
  return false
363
418
  end
364
419
  end
365
420
 
366
421
  # Get the design water temperature
367
- sizing_plant = self.sizingPlant
368
- design_temp_c = sizing_plant.loopDesignExitTemperature
369
- design_temp_f = OpenStudio.convert(design_temp_c,'C','F').get
370
- loop_type = self.loopType
371
-
422
+ sizing_plant = sizingPlant
423
+ design_temp_c = sizing_plant.designLoopExitTemperature
424
+ design_temp_f = OpenStudio.convert(design_temp_c, 'C', 'F').get
425
+ loop_type = sizing_plant.loopType
426
+
372
427
  # Apply the reset, depending on the type of loop.
373
428
  case loop_type
374
429
  when 'Heating'
375
-
430
+
376
431
  # Hot water as-designed when cold outside
377
432
  hwt_at_lo_oat_f = design_temp_f
378
433
  hwt_at_lo_oat_c = OpenStudio.convert(hwt_at_lo_oat_f, 'F', 'C').get
379
434
  # 30F decrease when it's hot outside,
380
435
  # and therefore less heating capacity is likely required.
381
436
  decrease_f = 30.0
382
- hwt_at_hi_oat_f = hwt_at_lo_oat_f - decrease_f
383
- hwt_at_hi_oat_c = OpenStudio.convert(hwt_at_hi_oat_f, 'C', 'F').get
437
+ hwt_at_hi_oat_f = hwt_at_lo_oat_f - decrease_f
438
+ hwt_at_hi_oat_c = OpenStudio.convert(hwt_at_hi_oat_f, 'F', 'C').get
384
439
 
385
440
  # Define the high and low outdoor air temperatures
386
441
  lo_oat_f = 20
387
442
  lo_oat_c = OpenStudio.convert(lo_oat_f, 'F', 'C').get
388
443
  hi_oat_f = 50
389
444
  hi_oat_c = OpenStudio.convert(hi_oat_f, 'F', 'C').get
390
-
445
+
391
446
  # Create a setpoint manager
392
- hwt_oa_reset = OpenStudio::Model::SetpointManagerOutdoorAirReset.new(self.model)
393
- hwt_oa_reset.setName("#{self.name} HW Temp Reset")
447
+ hwt_oa_reset = OpenStudio::Model::SetpointManagerOutdoorAirReset.new(model)
448
+ hwt_oa_reset.setName("#{name} HW Temp Reset")
394
449
  hwt_oa_reset.setControlVariable('Temperature')
395
450
  hwt_oa_reset.setSetpointatOutdoorLowTemperature(hwt_at_lo_oat_c)
396
451
  hwt_oa_reset.setOutdoorLowTemperature(lo_oat_c)
397
452
  hwt_oa_reset.setSetpointatOutdoorHighTemperature(hwt_at_hi_oat_c)
398
453
  hwt_oa_reset.setOutdoorHighTemperature(hi_oat_c)
399
- hwt_oa_reset.addToNode(self.supplyOutletNode)
400
-
401
- OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{self.name}: hot water temperature reset from #{hwt_at_lo_oat_f.round}F to #{hwt_at_hi_oat_f}F between outdoor air temps of #{lo_oat_f.round}F and #{hi_oat_f}F.")
402
-
454
+ hwt_oa_reset.addToNode(supplyOutletNode)
455
+
456
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{name}: hot water temperature reset from #{hwt_at_lo_oat_f.round}F to #{hwt_at_hi_oat_f.round}F between outdoor air temps of #{lo_oat_f.round}F and #{hi_oat_f.round}F.")
457
+
403
458
  when 'Cooling'
404
-
459
+
405
460
  # Chilled water as-designed when hot outside
406
461
  chwt_at_hi_oat_f = design_temp_f
407
462
  chwt_at_hi_oat_c = OpenStudio.convert(chwt_at_hi_oat_f, 'F', 'C').get
@@ -410,46 +465,44 @@ lcnwt_f = lcnwt_10f_approach if lcnwt_10f_approach < 85
410
465
  increase_f = 10.0
411
466
  chwt_at_lo_oat_f = chwt_at_hi_oat_f + increase_f
412
467
  chwt_at_lo_oat_c = OpenStudio.convert(chwt_at_lo_oat_f, 'F', 'C').get
413
-
468
+
414
469
  # Define the high and low outdoor air temperatures
415
- lo_oat_f = 50
470
+ lo_oat_f = 60
416
471
  lo_oat_c = OpenStudio.convert(lo_oat_f, 'F', 'C').get
417
- hi_oat_f = 70
472
+ hi_oat_f = 80
418
473
  hi_oat_c = OpenStudio.convert(hi_oat_f, 'F', 'C').get
419
-
474
+
420
475
  # Create a setpoint manager
421
- chwt_oa_reset = OpenStudio::Model::SetpointManagerOutdoorAirReset.new(self.model)
422
- chwt_oa_reset.setName("#{self.name} CHW Temp Reset")
476
+ chwt_oa_reset = OpenStudio::Model::SetpointManagerOutdoorAirReset.new(model)
477
+ chwt_oa_reset.setName("#{name} CHW Temp Reset")
423
478
  chwt_oa_reset.setControlVariable('Temperature')
424
479
  chwt_oa_reset.setSetpointatOutdoorLowTemperature(chwt_at_lo_oat_c)
425
480
  chwt_oa_reset.setOutdoorLowTemperature(lo_oat_c)
426
481
  chwt_oa_reset.setSetpointatOutdoorHighTemperature(chwt_at_hi_oat_c)
427
482
  chwt_oa_reset.setOutdoorHighTemperature(hi_oat_c)
428
- chwt_oa_reset.addToNode(self.supplyOutletNode)
483
+ chwt_oa_reset.addToNode(supplyOutletNode)
484
+
485
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{name}: chilled water temperature reset from #{chwt_at_hi_oat_f.round}F to #{chwt_at_lo_oat_f.round}F between outdoor air temps of #{hi_oat_f.round}F and #{lo_oat_f.round}F.")
429
486
 
430
- OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{self.name}: chilled water temperature reset from #{chwt_at_hi_oat_f}F to #{chwt_at_lo_oat_f.round}F between outdoor air temps of #{hi_oat_f}F and #{lo_oat_f.round}F.")
431
-
432
487
  else
433
-
434
- OpenStudio::logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{self.name}: cannot enable supply water temperature reset for a #{loop_type} loop.")
488
+
489
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{name}: cannot enable supply water temperature reset for a #{loop_type} loop.")
435
490
  return false
436
-
491
+
437
492
  end
438
-
493
+
439
494
  return true
440
-
441
495
  end
442
-
496
+
443
497
  # Get the total cooling capacity for the plant loop
444
498
  #
445
499
  # @return [Double] total cooling capacity
446
500
  # units = Watts (W)
447
501
  def total_cooling_capacity
448
-
449
502
  # Sum the cooling capacity for all cooling components
450
503
  # on the plant loop.
451
504
  total_cooling_capacity_w = 0
452
- self.supplyComponents.each do |sc|
505
+ supplyComponents.each do |sc|
453
506
  # ChillerElectricEIR
454
507
  if sc.to_ChillerElectricEIR.is_initialized
455
508
  chiller = sc.to_ChillerElectricEIR.get
@@ -458,28 +511,37 @@ lcnwt_f = lcnwt_10f_approach if lcnwt_10f_approach < 85
458
511
  elsif chiller.autosizedReferenceCapacity.is_initialized
459
512
  total_cooling_capacity_w += chiller.autosizedReferenceCapacity.get
460
513
  else
461
- OpenStudio::logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{self.name} capacity of #{chiller.name} is not available, total cooling capacity of plant loop will be incorrect when applying standard.")
514
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{name} capacity of #{chiller.name} is not available, total cooling capacity of plant loop will be incorrect when applying standard.")
515
+ end
516
+ # DistrictCooling
517
+ elsif sc.to_DistrictCooling.is_initialized
518
+ dist_clg = sc.to_DistrictCooling.get
519
+ if dist_clg.nominalCapacity.is_initialized
520
+ total_cooling_capacity_w += dist_clg.nominalCapacity.get
521
+ elsif dist_clg.autosizedNominalCapacity.is_initialized
522
+ total_cooling_capacity_w += dist_clg.autosizedNominalCapacity.get
523
+ else
524
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.PlantLoop', "For #{self.name} capacity of DistrictCooling #{dist_clg.name} is not available, total heating capacity of plant loop will be incorrect when applying standard.")
462
525
  end
463
526
  end
464
527
  end
465
528
 
466
- total_cooling_capacity_tons = OpenStudio.convert(total_cooling_capacity_w,'W','ton').get
467
- OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}, cooling capacity is #{total_cooling_capacity_tons.round} tons of refrigeration.")
468
-
529
+ total_cooling_capacity_tons = OpenStudio.convert(total_cooling_capacity_w, 'W', 'ton').get
530
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}, cooling capacity is #{total_cooling_capacity_tons.round} tons of refrigeration.")
531
+
469
532
  return total_cooling_capacity_w
470
-
471
533
  end
472
-
534
+
473
535
  # Get the total heating capacity for the plant loop
474
536
  #
475
537
  # @return [Double] total heating capacity
476
538
  # units = Watts (W)
539
+ # @todo Add district heating to plant loop heating capacity
477
540
  def total_heating_capacity
478
-
479
541
  # Sum the heating capacity for all heating components
480
542
  # on the plant loop.
481
543
  total_heating_capacity_w = 0
482
- self.supplyComponents.each do |sc|
544
+ supplyComponents.each do |sc|
483
545
  # BoilerHotWater
484
546
  if sc.to_BoilerHotWater.is_initialized
485
547
  boiler = sc.to_BoilerHotWater.get
@@ -488,34 +550,61 @@ lcnwt_f = lcnwt_10f_approach if lcnwt_10f_approach < 85
488
550
  elsif boiler.autosizedNominalCapacity.is_initialized
489
551
  total_heating_capacity_w += boiler.autosizedNominalCapacity.get
490
552
  else
491
- OpenStudio::logFree(OpenStudio::Warn, 'openstudio.standards.AirLoopHVAC', "For #{self.name} capacity of #{boiler.name} is not available, total cooling capacity of plant loop will be incorrect when applying standard.")
553
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.PlantLoop', "For #{self.name} capacity of Boiler:HotWater ' #{boiler.name} is not available, total heating capacity of plant loop will be incorrect when applying standard.")
554
+ end
555
+ # WaterHeater:Mixed
556
+ elsif sc.to_WaterHeaterMixed.is_initialized
557
+ water_heater = sc.to_WaterHeaterMixed.get
558
+ if water_heater.heaterMaximumCapacity.is_initialized
559
+ total_heating_capacity_w += water_heater.heaterMaximumCapacity.get
560
+ elsif water_heater.autosizedHeaterMaximumCapacity.is_initialized
561
+ total_heating_capacity_w += water_heater.autosizedHeaterMaximumCapacity.get
562
+ else
563
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.PlantLoop', "For #{self.name} capacity of WaterHeater:Mixed #{water_heater.name} is not available, total heating capacity of plant loop will be incorrect when applying standard.")
564
+ end
565
+ # WaterHeater:Stratified
566
+ elsif sc.to_WaterHeaterStratified.is_initialized
567
+ water_heater = sc.to_WaterHeaterStratified.get
568
+ if water_heater.heater1Capacity.is_initialized
569
+ total_heating_capacity_w += water_heater.heater1Capacity.get
570
+ end
571
+ if water_heater.heater2Capacity.is_initialized
572
+ total_heating_capacity_w += water_heater.heater2Capacity.get
573
+ end
574
+ # DistrictHeating
575
+ elsif sc.to_DistrictHeating.is_initialized
576
+ dist_htg = sc.to_DistrictHeating.get
577
+ if dist_htg.nominalCapacity.is_initialized
578
+ total_heating_capacity_w += dist_htg.nominalCapacity.get
579
+ elsif dist_htg.autosizedNominalCapacity.is_initialized
580
+ total_heating_capacity_w += dist_htg.autosizedNominalCapacity.get
581
+ else
582
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.PlantLoop', "For #{self.name} capacity of DistrictHeating #{dist_htg.name} is not available, total heating capacity of plant loop will be incorrect when applying standard.")
492
583
  end
493
584
  end
494
- end
585
+ end # End loop on supplyComponents
586
+
587
+ total_heating_capacity_kbtu_per_hr = OpenStudio.convert(total_heating_capacity_w,'W','kBtu/hr').get
588
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{self.name}, heating capacity is #{total_heating_capacity_kbtu_per_hr.round} kBtu/hr.")
495
589
 
496
- total_heating_capacity_kbtu_per_hr = OpenStudio.convert(total_heating_capacity_w,'W','tons').get
497
- OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}, heating capacity is #{total_heating_capacity_kbtu_per_hr.round} kBtu/hr.")
498
-
499
590
  return total_heating_capacity_w
500
-
501
591
  end
502
-
592
+
503
593
  def total_floor_area_served
504
-
505
- sizing_plant = self.sizingPlant
594
+ sizing_plant = sizingPlant
506
595
  loop_type = sizing_plant.loopType
507
-
596
+
508
597
  # Get all the coils served by this loop
509
598
  coils = []
510
599
  case loop_type
511
- when 'Heating'
512
- self.demandComponents.each do |dc|
600
+ when 'Heating'
601
+ demandComponents.each do |dc|
513
602
  if dc.to_CoilHeatingWater.is_initialized
514
603
  coils << dc.to_CoilHeatingWater.get
515
604
  end
516
605
  end
517
606
  when 'Cooling'
518
- self.demandComponents.each do |dc|
607
+ demandComponents.each do |dc|
519
608
  if dc.to_CoilCoolingWater.is_initialized
520
609
  coils << dc.to_CoilCoolingWater.get
521
610
  end
@@ -523,13 +612,12 @@ lcnwt_f = lcnwt_10f_approach if lcnwt_10f_approach < 85
523
612
  else
524
613
  return 0.0
525
614
  end
526
-
615
+
527
616
  # The coil can either be on an airloop (as a main heating coil)
528
617
  # in an HVAC Component (like a unitary system on an airloop),
529
618
  # or in a Zone HVAC Component (like a fan coil).
530
619
  zones_served = []
531
620
  coils.each do |coil|
532
-
533
621
  if coil.airLoopHVAC.is_initialized
534
622
  air_loop = coil.airLoopHVAC.get
535
623
  zones_served += air_loop.thermalZones
@@ -545,71 +633,68 @@ lcnwt_f = lcnwt_10f_approach if lcnwt_10f_approach < 85
545
633
  zones_served << zone_hvac.thermalZone.get
546
634
  end
547
635
  end
548
-
549
- end
550
-
636
+ end
637
+
551
638
  # Add up the area of all zones served.
552
639
  # Make sure to only add unique zones in
553
640
  # case the same zone is served by multiple
554
641
  # coils served by the same loop. For example,
555
- # a HW and Reheat
642
+ # a HW and Reheat
556
643
  area_served_m2 = 0.0
557
644
  zones_served.uniq.each do |zone|
558
645
  area_served_m2 += zone.floorArea
559
646
  end
560
- area_served_ft2 = OpenStudio.convert(area_served_m2,'m^2','ft^2').get
647
+ area_served_ft2 = OpenStudio.convert(area_served_m2, 'm^2', 'ft^2').get
648
+
649
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}, serves #{area_served_ft2.round} ft^2.")
561
650
 
562
- OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}, serves #{area_served_ft2.round} ft^2.")
563
-
564
651
  return area_served_m2
565
-
566
652
  end
567
653
 
568
- def apply_performance_rating_method_baseline_pumping_type(template)
569
-
570
- sizing_plant = self.sizingPlant
571
- loop_type = sizing_plant.loopType
572
-
654
+ def apply_prm_baseline_pumping_type(template)
655
+ sizing_plant = sizingPlant
656
+ loop_type = sizing_plant.loopType
657
+
573
658
  case loop_type
574
659
  when 'Heating'
575
-
660
+
576
661
  # Hot water systems
577
-
662
+
578
663
  # Determine the minimum area to determine
579
664
  # pumping type.
580
665
  minimum_area_ft2 = nil
581
666
  case template
582
667
  when '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013'
583
- minimum_area_ft2 = 120000
668
+ minimum_area_ft2 = 120_000
584
669
  end
585
-
670
+
586
671
  # Determine the area served
587
- area_served_m2 = self.total_floor_area_served
588
- area_served_ft2 = OpenStudio.convert(area_served_m2,'m^2','ft^2').get
589
-
590
- # Determine the pump type
672
+ area_served_m2 = total_floor_area_served
673
+ area_served_ft2 = OpenStudio.convert(area_served_m2, 'm^2', 'ft^2').get
674
+
675
+ # Determine the pump type
591
676
  control_type = 'Riding Curve'
592
677
  if area_served_ft2 > minimum_area_ft2
593
678
  control_type = 'VSD No Reset'
594
679
  end
595
-
680
+
596
681
  # Modify all the primary pumps
597
- self.supplyComponents.each do |sc|
682
+ supplyComponents.each do |sc|
598
683
  if sc.to_PumpVariableSpeed.is_initialized
599
684
  pump = sc.to_PumpVariableSpeed.get
600
685
  pump.set_control_type(control_type)
601
686
  end
602
687
  end
603
-
604
- # Report out the pumping type
688
+
689
+ # Report out the pumping type
605
690
  unless control_type.nil?
606
- OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}, pump type is #{control_type}.")
691
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}, pump type is #{control_type}.")
607
692
  end
608
693
 
609
694
  when 'Cooling'
610
695
 
611
696
  # Chilled water systems
612
-
697
+
613
698
  # Determine the pumping type.
614
699
  # For some templates, this is
615
700
  # based on area. For others, it is built
@@ -619,161 +704,181 @@ lcnwt_f = lcnwt_10f_approach if lcnwt_10f_approach < 85
619
704
  case template
620
705
  when '90.1-2004'
621
706
 
622
- minimum_area_ft2 = 120000
623
-
707
+ minimum_area_ft2 = 120_000
708
+
624
709
  # Determine the area served
625
- area_served_m2 = self.total_floor_area_served
626
- area_served_ft2 = OpenStudio.convert(area_served_m2,'m^2','ft^2').get
627
-
628
- # Determine the primary pump type
629
- pri_control_type = 'Riding Curve'
630
-
710
+ area_served_m2 = total_floor_area_served
711
+ area_served_ft2 = OpenStudio.convert(area_served_m2, 'm^2', 'ft^2').get
712
+
713
+ # Determine the primary pump type
714
+ pri_control_type = 'Constant Flow'
715
+
631
716
  # Determine the secondary pump type
632
717
  sec_control_type = 'Riding Curve'
633
718
  if area_served_ft2 > minimum_area_ft2
634
719
  sec_control_type = 'VSD No Reset'
635
720
  end
636
-
721
+
637
722
  when '90.1-2007', '90.1-2010', '90.1-2013'
638
-
723
+
639
724
  minimum_cap_tons = 300
640
-
725
+
641
726
  # Determine the capacity
642
- cap_w = self.total_cooling_capacity
643
- cap_tons = OpenStudio.convert(cap_w,'m^2','ft^2').get
644
-
645
- # Determine the primary pump type
646
- pri_control_type = 'Riding Curve'
647
-
648
- # Determine the secondary pump type
649
- sec_control_type = 'Riding Curve'
650
- if cap_tons > minimum_cap_tons
651
- sec_control_type = 'VSD No Reset'
727
+ cap_w = total_cooling_capacity
728
+ cap_tons = OpenStudio.convert(cap_w, 'W', 'ton').get
729
+
730
+ # Determine if it a district cooling system
731
+ has_district_cooling = false
732
+ supplyComponents.each do |sc|
733
+ if sc.to_DistrictCooling.is_initialized
734
+ has_district_cooling = true
735
+ end
736
+ end
737
+
738
+ # Determine the primary and secondary pumping types
739
+ pri_control_type = nil
740
+ sec_control_type = nil
741
+ if has_district_cooling
742
+ pri_control_type = if cap_tons > minimum_cap_tons
743
+ 'VSD No Reset'
744
+ else
745
+ 'Riding Curve'
746
+ end
747
+ else
748
+ pri_control_type = 'Constant Flow'
749
+ sec_control_type = if cap_tons > minimum_cap_tons
750
+ 'VSD No Reset'
751
+ else
752
+ 'Riding Curve'
753
+ end
652
754
  end
653
-
654
755
  end
655
-
756
+
656
757
  # Report out the pumping type
657
758
  unless pri_control_type.nil?
658
- OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}, primary pump type is #{pri_control_type}.")
759
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}, primary pump type is #{pri_control_type}.")
659
760
  end
660
761
 
661
762
  unless sec_control_type.nil?
662
- OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}, secondary pump type is #{sec_control_type}.")
663
- end
664
-
763
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}, secondary pump type is #{sec_control_type}.")
764
+ end
765
+
665
766
  # Modify all the primary pumps
666
- self.supplyComponents.each do |sc|
767
+ supplyComponents.each do |sc|
667
768
  if sc.to_PumpVariableSpeed.is_initialized
668
769
  pump = sc.to_PumpVariableSpeed.get
669
770
  pump.set_control_type(pri_control_type)
771
+ elsif sc.to_HeaderedPumpsVariableSpeed.is_initialized
772
+ pump = sc.to_HeaderedPumpsVariableSpeed.get
773
+ pump.set_control_type(control_type)
670
774
  end
671
- end
672
-
775
+ end
776
+
673
777
  # Modify all the secondary pumps
674
- self.demandComponents.each do |sc|
778
+ demandComponents.each do |sc|
675
779
  if sc.to_PumpVariableSpeed.is_initialized
676
780
  pump = sc.to_PumpVariableSpeed.get
677
781
  pump.set_control_type(sec_control_type)
782
+ elsif sc.to_HeaderedPumpsVariableSpeed.is_initialized
783
+ pump = sc.to_HeaderedPumpsVariableSpeed.get
784
+ pump.set_control_type(control_type)
678
785
  end
679
- end
786
+ end
680
787
 
681
788
  when 'Condenser'
682
-
789
+
683
790
  # Condenser water systems
684
-
791
+
685
792
  # All condenser water loops are constant flow
686
- control_type = 'Riding Curve'
793
+ control_type = 'Constant Flow'
687
794
 
688
795
  # Report out the pumping type
689
796
  unless control_type.nil?
690
- OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{self.name}, pump type is #{control_type}.")
797
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.AirLoopHVAC', "For #{name}, pump type is #{control_type}.")
691
798
  end
692
-
799
+
693
800
  # Modify all primary pumps
694
- self.supplyComponents.each do |sc|
801
+ supplyComponents.each do |sc|
695
802
  if sc.to_PumpVariableSpeed.is_initialized
696
803
  pump = sc.to_PumpVariableSpeed.get
697
804
  pump.set_control_type(control_type)
805
+ elsif sc.to_HeaderedPumpsVariableSpeed.is_initialized
806
+ pump = sc.to_HeaderedPumpsVariableSpeed.get
807
+ pump.set_control_type(control_type)
698
808
  end
699
809
  end
700
810
 
701
811
  end
702
-
812
+
703
813
  return true
704
-
705
814
  end
706
-
707
- def apply_performance_rating_method_number_of_boilers(template)
708
-
815
+
816
+ def apply_prm_number_of_boilers(template)
709
817
  # Skip non-heating plants
710
- return true unless self.sizingPlant.loopType == 'Heating'
818
+ return true unless sizingPlant.loopType == 'Heating'
711
819
 
712
820
  # Determine the minimum area to determine
713
821
  # number of boilers.
714
822
  minimum_area_ft2 = nil
715
823
  case template
716
824
  when '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013'
717
- minimum_area_ft2 = 15000
825
+ minimum_area_ft2 = 15_000
718
826
  end
719
-
827
+
720
828
  # Determine the area served
721
- area_served_m2 = self.total_floor_area_served
722
- area_served_ft2 = OpenStudio.convert(area_served_m2,'m^2','ft^2').get
723
-
829
+ area_served_m2 = total_floor_area_served
830
+ area_served_ft2 = OpenStudio.convert(area_served_m2, 'm^2', 'ft^2').get
831
+
724
832
  # Do nothing if only one boiler is required
725
833
  return true if area_served_ft2 < minimum_area_ft2
726
834
 
727
835
  # Get all existing boilers
728
836
  boilers = []
729
- self.supplyComponents.each do |sc|
837
+ supplyComponents.each do |sc|
730
838
  if sc.to_BoilerHotWater.is_initialized
731
839
  boilers << sc.to_BoilerHotWater.get
732
840
  end
733
841
  end
734
-
842
+
735
843
  # Ensure there is only 1 boiler to start
736
844
  first_boiler = nil
737
- if boilers.size == 0
845
+ if boilers.size.zero?
738
846
  return true
739
847
  elsif boilers.size > 1
740
- OpenStudio::logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{self.name}, found #{boilers.size}, cannot split up per performance rating method baseline requirements.")
848
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{name}, found #{boilers.size}, cannot split up per performance rating method baseline requirements.")
741
849
  else
742
850
  first_boiler = boilers[0]
743
851
  end
744
-
852
+
745
853
  # Clone the existing boiler and create
746
854
  # a new branch for it
747
- second_boiler = first_boiler.clone(self.model)
855
+ second_boiler = first_boiler.clone(model)
748
856
  if second_boiler.to_BoilerHotWater.is_initialized
749
857
  second_boiler = second_boiler.to_BoilerHotWater.get
750
858
  else
751
- OpenStudio::logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{self.name}, could not clone boiler #{first_boiler.name}, cannot apply the performance rating method number of boilers.")
859
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{name}, could not clone boiler #{first_boiler.name}, cannot apply the performance rating method number of boilers.")
752
860
  return false
753
861
  end
754
- self.addSupplyBranchForComponent(second_boiler)
862
+ addSupplyBranchForComponent(second_boiler)
755
863
  final_boilers = [first_boiler, second_boiler]
756
- OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{self.name}, added a second boiler.")
757
-
864
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{name}, added a second boiler.")
758
865
 
759
866
  # Set the sizing factor for all boilers evenly and Rename the boilers
760
- sizing_factor = (1.0/final_boilers.size).round(2)
867
+ sizing_factor = (1.0 / final_boilers.size).round(2)
761
868
  final_boilers.each_with_index do |boiler, i|
762
869
  boiler.setSizingFactor(sizing_factor)
763
- boiler.setName("#{first_boiler.name} #{i+1} of #{final_boilers.size}")
870
+ boiler.setName("#{first_boiler.name} #{i + 1} of #{final_boilers.size}")
764
871
  end
765
-
872
+
766
873
  # Set the equipment to stage sequentially
767
- self.setLoadDistributionScheme('SequentialLoad')
768
-
874
+ setLoadDistributionScheme('SequentialLoad')
875
+
769
876
  return true
770
-
771
877
  end
772
878
 
773
- def apply_performance_rating_method_number_of_chillers(template)
774
-
879
+ def apply_prm_number_of_chillers(template)
775
880
  # Skip non-cooling plants
776
- return true unless self.sizingPlant.loopType == 'Cooling'
881
+ return true unless sizingPlant.loopType == 'Cooling'
777
882
 
778
883
  # Determine the number and type of chillers
779
884
  num_chillers = nil
@@ -781,11 +886,11 @@ lcnwt_f = lcnwt_10f_approach if lcnwt_10f_approach < 85
781
886
  chiller_compressor_type = nil
782
887
  case template
783
888
  when '90.1-2004', '90.1-2007', '90.1-2010', '90.1-2013'
784
-
889
+
785
890
  # Determine the capacity of the loop
786
- cap_w = self.total_cooling_capacity
787
- cap_tons = OpenStudio.convert(cap_w,'W','ton').get
788
-
891
+ cap_w = total_cooling_capacity
892
+ cap_tons = OpenStudio.convert(cap_w, 'W', 'ton').get
893
+
789
894
  if cap_tons <= 300
790
895
  num_chillers = 1
791
896
  chiller_cooling_type = 'WaterCooled'
@@ -797,145 +902,450 @@ lcnwt_f = lcnwt_10f_approach if lcnwt_10f_approach < 85
797
902
  else
798
903
  # Max capacity of a single chiller
799
904
  max_cap_ton = 800.0
800
- num_chillers = (cap_tons/max_cap_ton).floor + 1
905
+ num_chillers = (cap_tons / max_cap_ton).floor + 1
801
906
  # Must be at least 2 chillers
802
- num_chillers +=1 if num_chillers == 1
907
+ num_chillers += 1 if num_chillers == 1
803
908
  chiller_cooling_type = 'WaterCooled'
804
- chiller_compressor_type = 'Centrifugal'
909
+ chiller_compressor_type = 'Centrifugal'
805
910
  end
806
-
911
+
807
912
  end
808
-
809
- # Get all existing chillers
913
+
914
+ # Get all existing chillers and pumps
810
915
  chillers = []
811
- self.supplyComponents.each do |sc|
916
+ pumps = []
917
+ supplyComponents.each do |sc|
812
918
  if sc.to_ChillerElectricEIR.is_initialized
813
919
  chillers << sc.to_ChillerElectricEIR.get
920
+ elsif sc.to_PumpConstantSpeed.is_initialized
921
+ pumps << sc.to_PumpConstantSpeed.get
922
+ elsif sc.to_PumpVariableSpeed.is_initialized
923
+ pumps << sc.to_PumpVariableSpeed.get
814
924
  end
815
925
  end
816
926
 
817
927
  # Ensure there is only 1 chiller to start
818
928
  first_chiller = nil
819
- if chillers.size == 0
929
+ if chillers.size.zero?
820
930
  return true
821
931
  elsif chillers.size > 1
822
- OpenStudio::logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{self.name}, found #{chillers.size} chillers, cannot split up per performance rating method baseline requirements.")
932
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{name}, found #{chillers.size} chillers, cannot split up per performance rating method baseline requirements.")
823
933
  else
824
934
  first_chiller = chillers[0]
825
935
  end
826
-
936
+
937
+ # Ensure there is only 1 pump to start
938
+ orig_pump = nil
939
+ if pumps.size.zero?
940
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{name}, found #{pumps.size} pumps. A loop must have at least one pump.")
941
+ return false
942
+ elsif pumps.size > 1
943
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{name}, found #{pumps.size} pumps, cannot split up per performance rating method baseline requirements.")
944
+ return false
945
+ else
946
+ orig_pump = pumps[0]
947
+ end
948
+
827
949
  # Determine the per-chiller capacity
828
950
  # and sizing factor
829
- per_chiller_sizing_factor = (1.0/num_chillers).round(2)
951
+ per_chiller_sizing_factor = (1.0 / num_chillers).round(2)
830
952
  # This is unused
831
953
  per_chiller_cap_tons = cap_tons / num_chillers
832
954
 
833
955
  # Set the sizing factor and the chiller type: could do it on the first chiller before cloning it, but renaming warrants looping on chillers anyways
834
-
956
+
835
957
  # Add any new chillers
836
958
  final_chillers = [first_chiller]
837
- (num_chillers-1).times do
838
- #new_chiller = OpenStudio::Model::ChillerElectricEIR.new(self.model)
839
- # TODO renable the cloning of the chillers after curves are shared resources
840
- # Should be good to go since 1.10.2 (?)
841
- new_chiller = first_chiller.clone(self.model)
959
+ (num_chillers - 1).times do
960
+ new_chiller = first_chiller.clone(model)
842
961
  if new_chiller.to_ChillerElectricEIR.is_initialized
843
962
  new_chiller = new_chiller.to_ChillerElectricEIR.get
844
963
  else
845
- OpenStudio::logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{self.name}, could not clone chiller #{first_chiller.name}, cannot apply the performance rating method number of chillers.")
964
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{name}, could not clone chiller #{first_chiller.name}, cannot apply the performance rating method number of chillers.")
846
965
  return false
847
966
  end
848
- self.addSupplyBranchForComponent(new_chiller)
967
+ # Connect the new chiller to the same CHW loop
968
+ # as the old chiller.
969
+ addSupplyBranchForComponent(new_chiller)
970
+ # Connect the new chiller to the same CW loop
971
+ # as the old chiller, if it was water-cooled.
972
+ cw_loop = first_chiller.secondaryPlantLoop
973
+ if cw_loop.is_initialized
974
+ cw_loop.get.addDemandBranchForComponent(new_chiller)
975
+ end
976
+
849
977
  final_chillers << new_chiller
850
978
  end
851
979
 
980
+ # If there is more than one cooling tower,
981
+ # replace the original pump with a headered pump
982
+ # of the same type and properties.
983
+ if final_chillers.size > 1
984
+ num_pumps = final_chillers.size
985
+ new_pump = nil
986
+ if orig_pump.to_PumpConstantSpeed.is_initialized
987
+ new_pump = OpenStudio::Model::HeaderedPumpsConstantSpeed.new(model)
988
+ new_pump.setNumberofPumpsinBank(num_pumps)
989
+ new_pump.setName("#{orig_pump.name} Bank of #{num_pumps}")
990
+ new_pump.setRatedPumpHead(orig_pump.ratedPumpHead)
991
+ new_pump.setMotorEfficiency(orig_pump.motorEfficiency)
992
+ new_pump.setFractionofMotorInefficienciestoFluidStream(orig_pump.fractionofMotorInefficienciestoFluidStream)
993
+ new_pump.setPumpControlType(orig_pump.pumpControlType)
994
+ elsif orig_pump.to_PumpVariableSpeed.is_initialized
995
+ new_pump = OpenStudio::Model::HeaderedPumpsVariableSpeed.new(model)
996
+ new_pump.setNumberofPumpsinBank(num_pumps)
997
+ new_pump.setName("#{orig_pump.name} Bank of #{num_pumps}")
998
+ new_pump.setRatedPumpHead(orig_pump.ratedPumpHead)
999
+ new_pump.setMotorEfficiency(orig_pump.motorEfficiency)
1000
+ new_pump.setFractionofMotorInefficienciestoFluidStream(orig_pump.fractionofMotorInefficienciestoFluidStream)
1001
+ new_pump.setPumpControlType(orig_pump.pumpControlType)
1002
+ new_pump.setCoefficient1ofthePartLoadPerformanceCurve(orig_pump.coefficient1ofthePartLoadPerformanceCurve)
1003
+ new_pump.setCoefficient2ofthePartLoadPerformanceCurve(orig_pump.coefficient2ofthePartLoadPerformanceCurve)
1004
+ new_pump.setCoefficient3ofthePartLoadPerformanceCurve(orig_pump.coefficient3ofthePartLoadPerformanceCurve)
1005
+ new_pump.setCoefficient4ofthePartLoadPerformanceCurve(orig_pump.coefficient4ofthePartLoadPerformanceCurve)
1006
+ end
1007
+ # Remove the old pump
1008
+ orig_pump.remove
1009
+ # Attach the new headered pumps
1010
+ new_pump.addToNode(supplyInletNode)
1011
+ end
1012
+
852
1013
  # Set the sizing factor and the chiller types
853
1014
  final_chillers.each_with_index do |final_chiller, i|
854
- final_chiller.setName("#{template} #{chiller_cooling_type} #{chiller_compressor_type} Chiller #{i+1} of #{final_chillers.size}")
1015
+ final_chiller.setName("#{template} #{chiller_cooling_type} #{chiller_compressor_type} Chiller #{i + 1} of #{final_chillers.size}")
855
1016
  final_chiller.setSizingFactor(per_chiller_sizing_factor)
856
1017
  final_chiller.setCondenserType(chiller_cooling_type)
857
1018
  end
858
- OpenStudio::logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{self.name}, there are #{final_chillers.size} #{chiller_cooling_type} #{chiller_compressor_type} chillers.")
859
-
1019
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{name}, there are #{final_chillers.size} #{chiller_cooling_type} #{chiller_compressor_type} chillers.")
1020
+
860
1021
  # Set the equipment to stage sequentially
861
- self.setLoadDistributionScheme('SequentialLoad')
862
-
1022
+ setLoadDistributionScheme('SequentialLoad')
1023
+
863
1024
  return true
864
-
865
1025
  end
866
1026
 
1027
+ def apply_prm_number_of_cooling_towers(template)
1028
+ # Skip non-cooling plants
1029
+ return true unless sizingPlant.loopType == 'Condenser'
1030
+
1031
+ # Determine the number of chillers
1032
+ # already in the model
1033
+ num_chillers = model.getChillerElectricEIRs.size
1034
+
1035
+ # Get all existing cooling towers and pumps
1036
+ clg_twrs = []
1037
+ pumps = []
1038
+ supplyComponents.each do |sc|
1039
+ if sc.to_CoolingTowerSingleSpeed.is_initialized
1040
+ clg_twrs << sc.to_CoolingTowerSingleSpeed.get
1041
+ elsif sc.to_CoolingTowerTwoSpeed.is_initialized
1042
+ clg_twrs << sc.to_CoolingTowerTwoSpeed.get
1043
+ elsif sc.to_CoolingTowerVariableSpeed.is_initialized
1044
+ clg_twrs << sc.to_CoolingTowerVariableSpeed.get
1045
+ elsif sc.to_PumpConstantSpeed.is_initialized
1046
+ pumps << sc.to_PumpConstantSpeed.get
1047
+ elsif sc.to_PumpVariableSpeed.is_initialized
1048
+ pumps << sc.to_PumpVariableSpeed.get
1049
+ end
1050
+ end
1051
+
1052
+ # Ensure there is only 1 cooling tower to start
1053
+ orig_twr = nil
1054
+ if clg_twrs.size.zero?
1055
+ return true
1056
+ elsif clg_twrs.size > 1
1057
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{name}, found #{clg_twrs.size} cooling towers, cannot split up per performance rating method baseline requirements.")
1058
+ return false
1059
+ else
1060
+ orig_twr = clg_twrs[0]
1061
+ end
1062
+
1063
+ # Ensure there is only 1 pump to start
1064
+ orig_pump = nil
1065
+ if pumps.size.zero?
1066
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{name}, found #{pumps.size} pumps. A loop must have at least one pump.")
1067
+ return false
1068
+ elsif pumps.size > 1
1069
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{name}, found #{pumps.size} pumps, cannot split up per performance rating method baseline requirements.")
1070
+ return false
1071
+ else
1072
+ orig_pump = pumps[0]
1073
+ end
1074
+
1075
+ # Determine the per-cooling_tower sizing factor
1076
+ clg_twr_sizing_factor = (1.0 / num_chillers).round(2)
1077
+
1078
+ # Add a cooling tower for each chiller.
1079
+ # Add an accompanying CW pump for each cooling tower.
1080
+ final_twrs = [orig_twr]
1081
+ new_twr = nil
1082
+ (num_chillers - 1).times do
1083
+ if orig_twr.to_CoolingTowerSingleSpeed.is_initialized
1084
+ new_twr = orig_twr.clone(model)
1085
+ new_twr = new_twr.to_CoolingTowerSingleSpeed.get
1086
+ elsif orig_twr.to_CoolingTowerTwoSpeed.is_initialized
1087
+ new_twr = orig_twr.clone(model)
1088
+ new_twr = new_twr.to_CoolingTowerTwoSpeed.get
1089
+ elsif orig_twr.to_CoolingTowerVariableSpeed.is_initialized
1090
+ # TODO: remove workaround after resolving
1091
+ # https://github.com/NREL/OpenStudio/issues/2212
1092
+ # Workaround is to create a new tower
1093
+ # and replicate all the properties of the first tower.
1094
+ new_twr = OpenStudio::Model::CoolingTowerVariableSpeed.new(model)
1095
+ new_twr.setName(orig_twr.name.get.to_s)
1096
+ new_twr.setDesignInletAirWetBulbTemperature(orig_twr.designInletAirWetBulbTemperature.get)
1097
+ new_twr.setDesignApproachTemperature(orig_twr.designApproachTemperature.get)
1098
+ new_twr.setDesignRangeTemperature(orig_twr.designRangeTemperature.get)
1099
+ new_twr.setFractionofTowerCapacityinFreeConvectionRegime(orig_twr.fractionofTowerCapacityinFreeConvectionRegime.get)
1100
+ if orig_twr.fanPowerRatioFunctionofAirFlowRateRatioCurve.is_initialized
1101
+ new_twr.setFanPowerRatioFunctionofAirFlowRateRatioCurve(orig_twr.fanPowerRatioFunctionofAirFlowRateRatioCurve.get)
1102
+ end
1103
+ else
1104
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "For #{name}, could not clone cooling tower #{orig_twr.name}, cannot apply the performance rating method number of cooling towers.")
1105
+ return false
1106
+ end
1107
+
1108
+ # Connect the new cooling tower to the CW loop
1109
+ addSupplyBranchForComponent(new_twr)
1110
+ new_twr_inlet = new_twr.inletModelObject.get.to_Node.get
1111
+
1112
+ final_twrs << new_twr
1113
+ end
1114
+
1115
+ # If there is more than one cooling tower,
1116
+ # replace the original pump with a headered pump
1117
+ # of the same type and properties.
1118
+ if final_twrs.size > 1
1119
+ num_pumps = final_twrs.size
1120
+ new_pump = nil
1121
+ if orig_pump.to_PumpConstantSpeed.is_initialized
1122
+ new_pump = OpenStudio::Model::HeaderedPumpsConstantSpeed.new(model)
1123
+ new_pump.setNumberofPumpsinBank(num_pumps)
1124
+ new_pump.setName("#{orig_pump.name} Bank of #{num_pumps}")
1125
+ new_pump.setRatedPumpHead(orig_pump.ratedPumpHead)
1126
+ new_pump.setMotorEfficiency(orig_pump.motorEfficiency)
1127
+ new_pump.setFractionofMotorInefficienciestoFluidStream(orig_pump.fractionofMotorInefficienciestoFluidStream)
1128
+ new_pump.setPumpControlType(orig_pump.pumpControlType)
1129
+ elsif orig_pump.to_PumpVariableSpeed.is_initialized
1130
+ new_pump = OpenStudio::Model::HeaderedPumpsVariableSpeed.new(model)
1131
+ new_pump.setNumberofPumpsinBank(num_pumps)
1132
+ new_pump.setName("#{orig_pump.name} Bank of #{num_pumps}")
1133
+ new_pump.setRatedPumpHead(orig_pump.ratedPumpHead)
1134
+ new_pump.setMotorEfficiency(orig_pump.motorEfficiency)
1135
+ new_pump.setFractionofMotorInefficienciestoFluidStream(orig_pump.fractionofMotorInefficienciestoFluidStream)
1136
+ new_pump.setPumpControlType(orig_pump.pumpControlType)
1137
+ new_pump.setCoefficient1ofthePartLoadPerformanceCurve(orig_pump.coefficient1ofthePartLoadPerformanceCurve)
1138
+ new_pump.setCoefficient2ofthePartLoadPerformanceCurve(orig_pump.coefficient2ofthePartLoadPerformanceCurve)
1139
+ new_pump.setCoefficient3ofthePartLoadPerformanceCurve(orig_pump.coefficient3ofthePartLoadPerformanceCurve)
1140
+ new_pump.setCoefficient4ofthePartLoadPerformanceCurve(orig_pump.coefficient4ofthePartLoadPerformanceCurve)
1141
+ end
1142
+ # Remove the old pump
1143
+ orig_pump.remove
1144
+ # Attach the new headered pumps
1145
+ new_pump.addToNode(supplyInletNode)
1146
+ end
1147
+
1148
+ # Set the sizing factors
1149
+ final_twrs.each_with_index do |final_cooling_tower, i|
1150
+ final_cooling_tower.setName("#{final_cooling_tower.name} #{i + 1} of #{final_twrs.size}")
1151
+ final_cooling_tower.setSizingFactor(clg_twr_sizing_factor)
1152
+ end
1153
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{name}, there are #{final_twrs.size} cooling towers, one for each chiller.")
1154
+
1155
+ # Set the equipment to stage sequentially
1156
+ setLoadDistributionScheme('SequentialLoad')
1157
+ end
867
1158
 
868
1159
  # Determines the total rated watts per GPM of the loop
869
1160
  #
870
1161
  # @return [Double] rated power consumption per flow
871
1162
  # @units Watts per GPM (W*s/m^3)
872
- def total_rated_w_per_gpm()
873
- sizing_plant = self.sizingPlant
1163
+ def total_rated_w_per_gpm
1164
+ sizing_plant = sizingPlant
874
1165
  loop_type = sizing_plant.loopType
875
1166
 
876
1167
  # Supply W/GPM
877
1168
  supply_w_per_gpm = 0
878
1169
  demand_w_per_gpm = 0
879
1170
 
880
- self.supplyComponents.each do |component|
1171
+ supplyComponents.each do |component|
881
1172
  if component.to_PumpConstantSpeed.is_initialized
882
1173
  pump = component.to_PumpConstantSpeed.get
883
1174
  pump_rated_w_per_gpm = pump.rated_w_per_gpm
884
- OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.Pump", "'#{loop_type}' Loop #{self.name} - Primary (Supply) Constant Speed Pump '#{pump.name}' - pump_rated_w_per_gpm #{pump_rated_w_per_gpm} W/GPM")
1175
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Pump', "'#{loop_type}' Loop #{name} - Primary (Supply) Constant Speed Pump '#{pump.name}' - pump_rated_w_per_gpm #{pump_rated_w_per_gpm} W/GPM")
885
1176
  supply_w_per_gpm += pump_rated_w_per_gpm
886
1177
  elsif component.to_PumpVariableSpeed.is_initialized
887
1178
  pump = component.to_PumpVariableSpeed.get
888
1179
  pump_rated_w_per_gpm = pump.rated_w_per_gpm
889
- OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.Pump", "'#{loop_type}' Loop #{self.name} - Primary (Supply) VSD Pump '#{pump.name}' - pump_rated_w_per_gpm #{pump_rated_w_per_gpm} W/GPM")
1180
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Pump', "'#{loop_type}' Loop #{name} - Primary (Supply) VSD Pump '#{pump.name}' - pump_rated_w_per_gpm #{pump_rated_w_per_gpm} W/GPM")
890
1181
  supply_w_per_gpm += pump_rated_w_per_gpm
891
1182
  end
892
1183
  end
893
1184
 
894
1185
  # Determine if primary only or primary-secondary
895
1186
  # IF there's a pump on the demand side it's primary-secondary
896
- demandPumps = self.demandComponents('OS_Pump_VariableSpeed'.to_IddObjectType) + self.demandComponents('OS_Pump_ConstantSpeed'.to_IddObjectType)
897
- demandPumps.each do |component|
1187
+ demand_pumps = demandComponents('OS:Pump:VariableSpeed'.to_IddObjectType) + demandComponents('OS:Pump:ConstantSpeed'.to_IddObjectType)
1188
+ demand_pumps.each do |component|
898
1189
  if component.to_PumpConstantSpeed.is_initialized
899
1190
  pump = component.to_PumpConstantSpeed.get
900
1191
  pump_rated_w_per_gpm = pump.rated_w_per_gpm
901
- OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.Pump", "'#{loop_type}' Loop #{self.name} - Secondary (Demand) Constant Speed Pump '#{pump.name}' - pump_rated_w_per_gpm #{pump_rated_w_per_gpm} W/GPM")
1192
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Pump', "'#{loop_type}' Loop #{name} - Secondary (Demand) Constant Speed Pump '#{pump.name}' - pump_rated_w_per_gpm #{pump_rated_w_per_gpm} W/GPM")
902
1193
  demand_w_per_gpm += pump_rated_w_per_gpm
903
1194
  elsif component.to_PumpVariableSpeed.is_initialized
904
1195
  pump = component.to_PumpVariableSpeed.get
905
1196
  pump_rated_w_per_gpm = pump.rated_w_per_gpm
906
- OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.Pump", "'#{loop_type}' Loop #{self.name} - Secondary (Demand) VSD Pump '#{pump.name}' - pump_rated_w_per_gpm #{pump_rated_w_per_gpm} W/GPM")
1197
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Pump', "'#{loop_type}' Loop #{name} - Secondary (Demand) VSD Pump '#{pump.name}' - pump_rated_w_per_gpm #{pump_rated_w_per_gpm} W/GPM")
907
1198
  demand_w_per_gpm += pump_rated_w_per_gpm
908
1199
  end
909
1200
  end
910
1201
 
911
-
912
1202
  total_rated_w_per_gpm = supply_w_per_gpm + demand_w_per_gpm
913
1203
 
914
- OpenStudio::logFree(OpenStudio::Info, "openstudio.standards.Loop", "'#{loop_type}' Loop #{self.name} - Total #{total_rated_w_per_gpm} W/GPM - Supply #{supply_w_per_gpm} W/GPM - Demand #{demand_w_per_gpm} W/GPM")
1204
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Loop', "'#{loop_type}' Loop #{name} - Total #{total_rated_w_per_gpm} W/GPM - Supply #{supply_w_per_gpm} W/GPM - Demand #{demand_w_per_gpm} W/GPM")
915
1205
 
916
1206
  return total_rated_w_per_gpm
917
-
918
1207
  end
919
1208
 
920
1209
  # find maximum_loop_flow_rate
921
1210
  #
922
1211
  # @return [Double] maximum_loop_flow_rate m^3/s
923
- def find_maximum_loop_flow_rate()
924
-
1212
+ def find_maximum_loop_flow_rate
925
1213
  # Get the maximum_loop_flow_rate
926
1214
  maximum_loop_flow_rate = nil
927
- if self.maximumLoopFlowRate.is_initialized
928
- maximum_loop_flow_rate = self.maximumLoopFlowRate.get
929
- elsif self.autosizedMaximumLoopFlowRate.is_initialized
930
- maximum_loop_flow_rate = self.autosizedMaximumLoopFlowRate.get
1215
+ if maximumLoopFlowRate.is_initialized
1216
+ maximum_loop_flow_rate = maximumLoopFlowRate.get
1217
+ elsif autosizedMaximumLoopFlowRate.is_initialized
1218
+ maximum_loop_flow_rate = autosizedMaximumLoopFlowRate.get
931
1219
  else
932
- OpenStudio::logFree(OpenStudio::Warn, 'openstudio.standards.PlantLoop', "For #{self.name} maximum loop flow rate is not available.")
1220
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.PlantLoop', "For #{name} maximum loop flow rate is not available.")
933
1221
  end
934
1222
 
935
1223
  return maximum_loop_flow_rate
1224
+ end
1225
+
1226
+ # Determines if the loop is a Service Water Heating loop by checking if there is a WaterUseConnection on the demand side
1227
+ #
1228
+ # @return [Boolean] true if it's indeed a SHW loop, false otherwise
1229
+ def swh_loop?()
1230
+
1231
+ serves_swh = false
1232
+ self.demandComponents.each do |comp|
1233
+ if comp.to_WaterUseConnections.is_initialized
1234
+ serves_swh = true
1235
+ break
1236
+ end
1237
+ end
936
1238
 
1239
+ return serves_swh
937
1240
  end
1241
+
1242
+ # Classifies the service water system and returns information
1243
+ # about fuel types, whether it serves both heating and service water heating,
1244
+ # the water storage volume, and the total heating capacity.
1245
+ #
1246
+ # @return [Array<Array<String>, Bool, Double, Double>] An array of:
1247
+ # fuel types, combination_system (true/false), storage_capacity (m^3), total_heating_capacity (W)
1248
+ def swh_system_type
1249
+ combination_system = true
1250
+ storage_capacity = 0
1251
+ primary_fuels = []
1252
+ secondary_fuels = []
1253
+
1254
+ # @Todo: to work correctly, plantloop.total_heating_capacity requires to have either hardsized capacities or a sizing run.
1255
+ primary_heating_capacity = total_heating_capacity
1256
+ secondary_heating_capacity = 0
1257
+
1258
+ supplyComponents.each do |component|
1259
+
1260
+ # Get the object type
1261
+ obj_type = component.iddObjectType.valueName.to_s
1262
+
1263
+ case obj_type
1264
+ when 'OS_DistrictHeating'
1265
+ primary_fuels << 'DistrictHeating'
1266
+ combination_system = false
1267
+ when 'OS_HeatPump_WaterToWater_EquationFit_Heating'
1268
+ primary_fuels << 'Electricity'
1269
+ when 'OS_SolarCollector_FlatPlate_PhotovoltaicThermal'
1270
+ primary_fuels << 'SolarEnergy'
1271
+ when 'OS_SolarCollector_FlatPlate_Water'
1272
+ primary_fuels << 'SolarEnergy'
1273
+ when 'OS_SolarCollector_IntegralCollectorStorage'
1274
+ primary_fuels << 'SolarEnergy'
1275
+ when 'OS_WaterHeater_HeatPump'
1276
+ primary_fuels << 'Electricity'
1277
+ when 'OS_WaterHeater_Mixed'
1278
+ component = component.to_WaterHeaterMixed.get
1279
+ # Check it it's actually a heater, not just a storage tank
1280
+ if component.heaterMaximumCapacity.empty? || component.heaterMaximumCapacity.get != 0
1281
+ # If it does, we add the heater Fuel Type
1282
+ primary_fuels << component.heaterFuelType
1283
+ # And in this case we'll reuse this object
1284
+ combination_system = false
1285
+ end # @Todo: not sure about whether it should be an elsif or not
1286
+ # Check the plant loop connection on the source side
1287
+ if component.secondaryPlantLoop.is_initialized
1288
+ source_plant_loop = component.secondaryPlantLoop.get
1289
+ secondary_fuels += plant_loop_heating_fuels(source_plant_loop)
1290
+ secondary_heating_capacity += source_plant_loop.total_heating_capacity
1291
+ end
938
1292
 
1293
+ # Storage capacity
1294
+ if component.tankVolume.is_initialized
1295
+ storage_capacity = component.tankVolume.get
1296
+ end
939
1297
 
940
- end
1298
+ when 'OS_WaterHeater_Stratified'
1299
+ component = component.to_WaterHeaterStratified.get
1300
+
1301
+ # Check if the heater actually has a capacity (otherwise it's simply a Storage Tank)
1302
+ if component.heaterMaximumCapacity.empty? || component.heaterMaximumCapacity.get != 0
1303
+ # If it does, we add the heater Fuel Type
1304
+ primary_fuels << component.heaterFuelType
1305
+ # And in this case we'll reuse this object
1306
+ combination_system = false
1307
+ end # @Todo: not sure about whether it should be an elsif or not
1308
+ # Check the plant loop connection on the source side
1309
+ if component.secondaryPlantLoop.is_initialized
1310
+ source_plant_loop = component.secondaryPlantLoop.get
1311
+ secondary_fuels += plant_loop_heating_fuels(source_plant_loop)
1312
+ secondary_heating_capacity += source_plant_loop.total_heating_capacity
1313
+ end
1314
+
1315
+ # Storage capacity
1316
+ if component.tankVolume.is_initialized
1317
+ storage_capacity = component.tankVolume.get
1318
+ end
1319
+
1320
+ when 'OS_HeatExchanger_FluidToFluid'
1321
+ hx = component.to_HeatExchangerFluidToFluid.get
1322
+ cooling_hx_control_types = ["CoolingSetpointModulated", "CoolingSetpointOnOff", "CoolingDifferentialOnOff", "CoolingSetpointOnOffWithComponentOverride"]
1323
+ cooling_hx_control_types.each {|x| x.downcase!}
1324
+ if !cooling_hx_control_types.include?(hx.controlType.downcase) && hx.secondaryPlantLoop.is_initialized
1325
+ source_plant_loop = hx.secondaryPlantLoop.get
1326
+ secondary_fuels += plant_loop_heating_fuels(source_plant_loop)
1327
+ secondary_heating_capacity += source_plant_loop.total_heating_capacity
1328
+ end
1329
+
1330
+ when 'OS_Node', 'OS_Pump_ConstantSpeed', 'OS_Pump_VariableSpeed', 'OS_Connector_Splitter', 'OS_Connector_Mixer', 'OS_Pipe_Adiabatic'
1331
+ # To avoid extraneous debug messages
1332
+ else
1333
+ #OpenStudio::logFree(OpenStudio::Debug, 'openstudio.sizing.Model', "No heating fuel types found for #{obj_type}")
1334
+ end
1335
+
1336
+ end
941
1337
 
1338
+ # @Todo: decide how to handle primary and secondary stuff
1339
+ fuels = primary_fuels + secondary_fuels
1340
+ total_heating_capacity = primary_heating_capacity + secondary_heating_capacity
1341
+ # If the primary heating capacity is bigger than secondary, assume the secondary is just a backup and disregard it?
1342
+ # if primary_heating_capacity > secondary_heating_capacity
1343
+ # total_heating_capacity = primary_heating_capacity
1344
+ # fuels = primary_fuels
1345
+ # end
1346
+
1347
+ return fuels.uniq.sort, combination_system, storage_capacity, total_heating_capacity
1348
+
1349
+ end # end classify_swh_system_type
1350
+
1351
+ end