openstudio-standards 0.6.3 → 0.7.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/data/standards/OpenStudio_Standards-ashrae_90_1.xlsx +0 -0
  3. data/data/standards/manage_OpenStudio_Standards.rb +2 -49
  4. data/data/standards/openstudio_standards_duplicates_log.csv +1 -7962
  5. data/data/standards/test_performance_expected_dd_results.csv +2005 -97
  6. data/lib/openstudio-standards/create_typical/space_type_ratios.rb +47 -57
  7. data/lib/openstudio-standards/geometry/create.rb +1 -1
  8. data/lib/openstudio-standards/geometry/create_bar.rb +6 -3
  9. data/lib/openstudio-standards/geometry/create_shape.rb +1 -1
  10. data/lib/openstudio-standards/geometry/group.rb +1 -1
  11. data/lib/openstudio-standards/geometry/information.rb +1 -1
  12. data/lib/openstudio-standards/geometry/modify.rb +53 -1
  13. data/lib/openstudio-standards/infiltration/nist_infiltration.rb +1 -1
  14. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Model.rb +11 -11
  15. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.Model.rb +11 -11
  16. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.hvac_systems.rb +2 -2
  17. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SuperTallBuilding.rb +44 -47
  18. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.TallBuilding.rb +43 -48
  19. data/lib/openstudio-standards/prototypes/common/objects/Prototype.CentralAirSourceHeatPump.rb +1 -1
  20. data/lib/openstudio-standards/prototypes/common/objects/Prototype.hvac_systems.rb +44 -24
  21. data/lib/openstudio-standards/prototypes/common/objects/Prototype.refrigeration.rb +24 -24
  22. data/lib/openstudio-standards/schedules/parametric.rb +1 -1
  23. data/lib/openstudio-standards/service_water_heating/create_piping_losses.rb +152 -0
  24. data/lib/openstudio-standards/service_water_heating/create_water_heater.rb +544 -0
  25. data/lib/openstudio-standards/service_water_heating/create_water_heating_loop.rb +303 -0
  26. data/lib/openstudio-standards/service_water_heating/create_water_use.rb +95 -0
  27. data/lib/openstudio-standards/space/space.rb +1 -1
  28. data/lib/openstudio-standards/standards/Standards.AirLoopHVAC.rb +65 -70
  29. data/lib/openstudio-standards/standards/Standards.CoilCoolingWaterToAirHeatPumpEquationFit.rb +12 -14
  30. data/lib/openstudio-standards/standards/Standards.CoilHeatingDXSingleSpeed.rb +16 -5
  31. data/lib/openstudio-standards/standards/Standards.Model.rb +2 -2
  32. data/lib/openstudio-standards/standards/Standards.PlanarSurface.rb +10 -2
  33. data/lib/openstudio-standards/{prototypes/common/objects/Prototype.Model.swh.rb → standards/Standards.ServiceWaterHeating.rb} +209 -139
  34. data/lib/openstudio-standards/standards/Standards.Surface.rb +1 -1
  35. data/lib/openstudio-standards/standards/Standards.ZoneHVACComponent.rb +4 -8
  36. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/ashrae_90_1_2004.Model.rb +2 -2
  37. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.construction_properties.json +22251 -12963
  38. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.construction_sets.json +91 -91
  39. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/data/ashrae_90_1_2007.construction_properties.json +8981 -5228
  40. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/data/ashrae_90_1_2010.construction_properties.json +8935 -5182
  41. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.construction_properties.json +7281 -5391
  42. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.construction_sets.json +91 -91
  43. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.construction_properties.json +9005 -15215
  44. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.construction_sets.json +136 -136
  45. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.AirLoopHVAC.rb +1 -1
  46. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.construction_properties.json +8717 -17168
  47. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.construction_sets.json +136 -136
  48. data/lib/openstudio-standards/standards/ashrae_90_1/data/ashrae_90_1.constructions.json +1941 -651
  49. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_1980_2004/data/doe_ref_1980_2004.construction_properties.json +135 -135
  50. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_pre_1980/data/doe_ref_pre_1980.construction_properties.json +135 -135
  51. data/lib/openstudio-standards/standards/ashrae_90_1/nrel_zne_ready_2017/data/nrel_zne_ready_2017.construction_properties.json +36 -36
  52. data/lib/openstudio-standards/standards/ashrae_90_1/ze_aedg_multifamily/data/ze_aedg_multifamily.construction_properties.json +36 -36
  53. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.PlantLoop.rb +377 -99
  54. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.SpaceType.rb +2 -2
  55. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/ashrae_90_1_prm_2019.Model.rb +3 -3
  56. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.construction_properties.json +6889 -4044
  57. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.prm_constructions.json +108 -108
  58. data/lib/openstudio-standards/standards/cbes/cbes_pre_1978/data/cbes_pre_1978.construction_properties.json +16 -16
  59. data/lib/openstudio-standards/standards/cbes/cbes_t24_1978/data/cbes_t24_1978.construction_properties.json +16 -16
  60. data/lib/openstudio-standards/standards/cbes/cbes_t24_1992/data/cbes_t24_1992.construction_properties.json +16 -16
  61. data/lib/openstudio-standards/standards/cbes/cbes_t24_2001/data/cbes_t24_2001.construction_properties.json +16 -16
  62. data/lib/openstudio-standards/standards/cbes/cbes_t24_2005/data/cbes_t24_2005.construction_properties.json +16 -16
  63. data/lib/openstudio-standards/standards/cbes/cbes_t24_2008/data/cbes_t24_2008.construction_properties.json +16 -16
  64. data/lib/openstudio-standards/standards/cbes/data/cbes.constructions.json +142 -142
  65. data/lib/openstudio-standards/standards/deer/data/deer.constructions.json +5 -1551
  66. data/lib/openstudio-standards/standards/deer/data/deer.materials.json +40 -0
  67. data/lib/openstudio-standards/standards/deer/deer_1985/data/deer_1985.motors.json +88 -8
  68. data/lib/openstudio-standards/standards/deer/deer_1996/data/deer_1996.motors.json +88 -8
  69. data/lib/openstudio-standards/standards/deer/deer_2003/data/deer_2003.motors.json +88 -8
  70. data/lib/openstudio-standards/standards/deer/deer_2007/data/deer_2007.motors.json +88 -8
  71. data/lib/openstudio-standards/standards/deer/deer_2011/data/deer_2011.motors.json +88 -8
  72. data/lib/openstudio-standards/standards/deer/deer_2014/data/deer_2014.motors.json +88 -8
  73. data/lib/openstudio-standards/standards/deer/deer_2015/data/deer_2015.motors.json +88 -8
  74. data/lib/openstudio-standards/standards/deer/deer_2017/data/deer_2017.motors.json +88 -8
  75. data/lib/openstudio-standards/standards/deer/deer_2020/data/deer_2020.motors.json +88 -8
  76. data/lib/openstudio-standards/standards/deer/deer_2025/data/deer_2025.motors.json +88 -8
  77. data/lib/openstudio-standards/standards/deer/deer_2030/data/deer_2030.motors.json +88 -8
  78. data/lib/openstudio-standards/standards/deer/deer_2035/data/deer_2035.motors.json +88 -8
  79. data/lib/openstudio-standards/standards/deer/deer_2040/data/deer_2040.motors.json +88 -8
  80. data/lib/openstudio-standards/standards/deer/deer_2045/data/deer_2045.motors.json +88 -8
  81. data/lib/openstudio-standards/standards/deer/deer_2050/data/deer_2050.motors.json +88 -8
  82. data/lib/openstudio-standards/standards/deer/deer_2055/data/deer_2055.motors.json +88 -8
  83. data/lib/openstudio-standards/standards/deer/deer_2060/data/deer_2060.motors.json +88 -8
  84. data/lib/openstudio-standards/standards/deer/deer_2065/data/deer_2065.motors.json +88 -8
  85. data/lib/openstudio-standards/standards/deer/deer_2070/data/deer_2070.motors.json +88 -8
  86. data/lib/openstudio-standards/standards/deer/deer_2075/data/deer_2075.motors.json +88 -8
  87. data/lib/openstudio-standards/standards/deer/deer_pre_1975/data/deer_pre_1975.motors.json +88 -8
  88. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/btap_pre1980.rb +17 -0
  89. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/hvac_systems.rb +2 -1
  90. data/lib/openstudio-standards/standards/necb/ECMS/ecms.rb +4 -4
  91. data/lib/openstudio-standards/standards/necb/ECMS/hvac_systems.rb +61 -88
  92. data/lib/openstudio-standards/standards/necb/NECB2011/autozone.rb +3 -2
  93. data/lib/openstudio-standards/standards/necb/NECB2011/data/boiler_fuel_type_sets.json +54 -0
  94. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LEEPMidriseApartment.osm +1 -1
  95. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LEEPMultiTower.osm +1 -1
  96. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LEEPPointTower.osm +1 -1
  97. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LEEPTownHouse.osm +1 -1
  98. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/NorthernEducation.osm +4 -4
  99. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/NorthernHealthCare.osm +4 -4
  100. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb +32 -24
  101. data/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +89 -15
  102. data/lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb +5 -1
  103. data/lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb +22 -65
  104. data/lib/openstudio-standards/standards/necb/NECB2011/system_fuels.rb +19 -0
  105. data/lib/openstudio-standards/standards/necb/common/btap_data.rb +56 -2
  106. data/lib/openstudio-standards/standards/necb/common/btap_datapoint.rb +3 -1
  107. data/lib/openstudio-standards/standards/necb/common/construction_defaults.osm +2 -2
  108. data/lib/openstudio-standards/standards/necb/docs/air_system_names_method.md +127 -0
  109. data/lib/openstudio-standards/thermal_zone/thermal_zone.rb +1 -1
  110. data/lib/openstudio-standards/utilities/template_measure/resources/BTAPMeasureHelper.rb +1 -1
  111. data/lib/openstudio-standards/version.rb +1 -1
  112. data/lib/openstudio-standards/weather/information.rb +61 -5
  113. data/lib/openstudio-standards/weather/modify.rb +1 -1
  114. data/lib/openstudio-standards.rb +5 -3
  115. metadata +12 -63
  116. data/data/standards/OpenStudio_Standards-deer.xlsx +0 -0
  117. data/lib/openstudio-standards/prototypes/common/objects/Prototype.ServiceWaterHeating.rb +0 -1100
  118. data/lib/openstudio-standards/service_water_heating/component.rb +0 -189
@@ -92,22 +92,16 @@ class ASHRAE901PRM < Standard
92
92
  end
93
93
 
94
94
  # Splits the single chiller used for the initial sizing run
95
- # into multiple separate chillers based on Appendix G.
96
- #
95
+ # into multiple separate chillers based on Appendix G. Also applies
96
+ # EMS to stage chillers properly
97
97
  # @param plant_loop [OpenStudio::Model::PlantLoop] chilled water loop
98
- # @param sizing_run_dir [String] sizing run directory
99
98
  # @return [Boolean] returns true if successful, false if not
100
- def plant_loop_apply_prm_number_of_chillers(plant_loop, sizing_run_dir = nil)
99
+ def plant_loop_apply_prm_number_of_chillers(plant_loop)
101
100
  # Skip non-cooling plants & secondary cooling loop
102
101
  return true unless plant_loop.sizingPlant.loopType == 'Cooling'
103
102
  # If the loop is cooling but it is a secondary loop, then skip.
104
103
  return true if plant_loop.additionalProperties.hasFeature('is_secondary_loop')
105
104
 
106
- # Determine the number and type of chillers
107
- num_chillers = nil
108
- chiller_cooling_type = nil
109
- chiller_compressor_type = nil
110
-
111
105
  # Set the equipment to stage sequentially or uniformload if there is secondary loop
112
106
  if plant_loop.additionalProperties.hasFeature('is_primary_loop')
113
107
  plant_loop.setLoadDistributionScheme('UniformLoad')
@@ -115,9 +109,55 @@ class ASHRAE901PRM < Standard
115
109
  plant_loop.setLoadDistributionScheme('SequentialLoad')
116
110
  end
117
111
 
112
+ model = plant_loop.model
113
+
114
+ # Get all existing chillers and pumps. Copy chiller properties needed when duplicating existing settings
115
+ chillers = []
116
+ pumps = []
117
+ default_cop = nil
118
+ condenser_water_loop = nil
119
+ dsgn_sup_wtr_temp_c = nil
120
+
121
+ plant_loop.supplyComponents.each do |sc|
122
+ if sc.to_ChillerElectricEIR.is_initialized
123
+ chiller = sc.to_ChillerElectricEIR.get
124
+
125
+ # Copy the last chillers COP, leaving chilled water temperature, and reference cooling tower. These will be the
126
+ # default for any extra chillers.
127
+ default_cop = chiller.referenceCOP
128
+ dsgn_sup_wtr_temp_c = chiller.referenceLeavingChilledWaterTemperature
129
+ condenser_water_loop = chiller.condenserWaterLoop
130
+ chillers << chiller
131
+
132
+ elsif sc.to_PumpConstantSpeed.is_initialized
133
+ pumps << sc.to_PumpConstantSpeed.get
134
+ elsif sc.to_PumpVariableSpeed.is_initialized
135
+ pumps << sc.to_PumpVariableSpeed.get
136
+ end
137
+ end
138
+
139
+ # Get existing plant loop pump. We'll copy this pumps parameters before removing it. Throw exception for multiple pumps on supply side
140
+ if pumps.size.zero?
141
+ OpenStudio.logFree(OpenStudio::Error, 'prm.log', "For #{plant_loop.name}, found #{pumps.size} pumps. A loop must have at least one pump.")
142
+ return false
143
+ elsif pumps.size > 1
144
+ OpenStudio.logFree(OpenStudio::Error, 'prm.log', "For #{plant_loop.name}, found #{pumps.size} pumps, cannot split up per performance rating method baseline requirements.")
145
+ return false
146
+ else
147
+ original_pump = pumps[0]
148
+ end
149
+
150
+ return true if chillers.empty?
151
+
118
152
  # Determine the capacity of the loop
119
153
  cap_w = plant_loop_total_cooling_capacity(plant_loop)
120
154
  cap_tons = OpenStudio.convert(cap_w, 'W', 'ton').get
155
+
156
+ # Throw exception for > 2,400 tons as this breaks our staging strategy cap of 3 chillers
157
+ if cap_tons > 2400
158
+ OpenStudio.logFree(OpenStudio::Error, 'prm.log', "For #{plant_loop.name}, the total capacity (#{cap_w}) exceeded 2400 tons and would require more than 3 chillers. The existing code base cannot accommodate the staging required for this")
159
+ end
160
+
121
161
  if cap_tons <= 300
122
162
  num_chillers = 1
123
163
  chiller_cooling_type = 'WaterCooled'
@@ -136,114 +176,352 @@ class ASHRAE901PRM < Standard
136
176
  chiller_compressor_type = 'Centrifugal'
137
177
  end
138
178
 
139
- # Get all existing chillers and pumps
140
- chillers = []
141
- pumps = []
142
- plant_loop.supplyComponents.each do |sc|
179
+ if chillers.length > num_chillers
180
+ OpenStudio.logFree(OpenStudio::Error, 'prm.log', "For #{plant_loop.name}, the existing number of chillers exceeds the recommended amount. We have not accounted for this in the codebase yet.")
181
+ end
182
+
183
+ # Determine the per-chiller capacity and sizing factor
184
+ per_chiller_sizing_factor = (1.0 / num_chillers).round(2)
185
+ per_chiller_cap_w = cap_w / num_chillers
186
+
187
+ # Set the sizing factor and the chiller types
188
+ # chillers.each_with_index do |chiller, i|
189
+ for i in 0..num_chillers - 1
190
+ # if not enough chillers exist, create a new one. Else reference the i'th chiller
191
+ if i <= chillers.length - 1
192
+ chiller = chillers[i]
193
+ else
194
+ chiller = OpenStudio::Model::ChillerElectricEIR.new(model)
195
+ plant_loop.addSupplyBranchForComponent(chiller)
196
+ chiller.setReferenceLeavingChilledWaterTemperature(dsgn_sup_wtr_temp_c)
197
+ chiller.setLeavingChilledWaterLowerTemperatureLimit(OpenStudio.convert(36.0, 'F', 'C').get)
198
+ chiller.setReferenceEnteringCondenserFluidTemperature(OpenStudio.convert(95.0, 'F', 'C').get)
199
+ chiller.setMinimumPartLoadRatio(0.15)
200
+ chiller.setMaximumPartLoadRatio(1.0)
201
+ chiller.setOptimumPartLoadRatio(1.0)
202
+ chiller.setMinimumUnloadingRatio(0.25)
203
+ chiller.setChillerFlowMode('ConstantFlow')
204
+ chiller.setReferenceCOP(default_cop)
205
+
206
+ condenser_water_loop.get.addDemandBranchForComponent(chiller) if condenser_water_loop.is_initialized
207
+
208
+ end
209
+
210
+ chiller.setName("#{template} #{chiller_cooling_type} #{chiller_compressor_type} Chiller #{i + 1} of #{num_chillers}")
211
+ chiller.setSizingFactor(per_chiller_sizing_factor)
212
+ chiller.setReferenceCapacity(per_chiller_cap_w)
213
+ chiller.setCondenserType(chiller_cooling_type)
214
+ chiller.additionalProperties.setFeature('compressor_type', chiller_compressor_type)
215
+
216
+ # Add inlet pump
217
+ new_pump = OpenStudio::Model::PumpVariableSpeed.new(plant_loop.model)
218
+ new_pump.setName("#{chiller.name} Inlet Pump")
219
+ new_pump.setRatedPumpHead(original_pump.ratedPumpHead / num_chillers)
220
+
221
+ pump_variable_speed_set_control_type(new_pump, control_type = 'Riding Curve')
222
+ chiller_inlet_node = chiller.connectedObject(chiller.supplyInletPort).get.to_Node.get
223
+ new_pump.addToNode(chiller_inlet_node)
224
+
225
+ end
226
+
227
+ # Remove original pump, dedicated chiller pumps have all been added
228
+ original_pump.remove
229
+
230
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{plant_loop.name}, there are #{chillers.size} #{chiller_cooling_type} #{chiller_compressor_type} chillers.")
231
+
232
+ # Check for a heat exchanger fluid to fluid-- that lets you know if this is a primary loop
233
+ has_secondary_plant_loop = !plant_loop.demandComponents(OpenStudio::Model::HeatExchangerFluidToFluid.iddObjectType).empty?
234
+
235
+ if has_secondary_plant_loop
236
+ # Add EMS to stage chillers if there's a primary/secondary configuration
237
+ if num_chillers > 3
238
+ OpenStudio.logFree(OpenStudio::Error, 'prm.log', "For #{plant_loop.name} has more than 3 chillers. We do not have an EMS strategy for that yet.")
239
+ elsif num_chillers > 1
240
+ add_ems_for_multiple_chiller_pumps_w_secondary_plant(model, plant_loop)
241
+ end
242
+ end
243
+
244
+ return true
245
+ end
246
+
247
+ # Adds EMS program for pumps serving 3 chillers on primary + secondary loop. This was due to an issue when modeling two
248
+ # dedicated loops. The headered pumps or dedicated constant speed pumps operate at full flow as long as there's a
249
+ # load on the loop unless this EMS is in place.
250
+ # @param model [OpenStudio::Model] OpenStudio model with plant loops
251
+ # @param primary_plant [OpenStudio::Model::PlantLoop] Primary chilled water loop with chillers
252
+ def add_ems_for_multiple_chiller_pumps_w_secondary_plant(model, primary_plant)
253
+ # Aggregate array of chillers on primary plant supply side
254
+ chiller_list = []
255
+
256
+ primary_plant.supplyComponents.each do |sc|
143
257
  if sc.to_ChillerElectricEIR.is_initialized
144
- chillers << sc.to_ChillerElectricEIR.get
145
- elsif sc.to_PumpConstantSpeed.is_initialized
146
- pumps << sc.to_PumpConstantSpeed.get
147
- elsif sc.to_PumpVariableSpeed.is_initialized
148
- pumps << sc.to_PumpVariableSpeed.get
258
+ chiller_list << sc.to_ChillerElectricEIR.get
149
259
  end
150
260
  end
151
261
 
152
- # Ensure there is only 1 chiller to start
153
- first_chiller = nil
154
- return true if chillers.empty?
262
+ num_of_chillers = chiller_list.length # Either 2 or 3
155
263
 
156
- if chillers.size > 1
157
- OpenStudio.logFree(OpenStudio::Error, 'prm.log', "For #{plant_loop.name}, found #{chillers.size} chillers, cannot split up per performance rating method baseline requirements.")
158
- else
159
- first_chiller = chillers[0]
264
+ return if num_of_chillers <= 1
265
+
266
+ plant_name = primary_plant.name.to_s
267
+
268
+ # Make a variable to track the chilled water demand
269
+ chw_sensor = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Plant Supply Side Cooling Demand Rate')
270
+ chw_sensor.setKeyName(plant_name)
271
+ chw_sensor.setName("#{plant_name.gsub(/[-\s]+/, '_')}_CHW_DEMAND")
272
+
273
+ # Sort chillers by their reference capacity
274
+ sorted_chiller_list = chiller_list.sort_by { |chiller| chiller.referenceCapacity.get.to_f}
275
+
276
+ # Make pump specific parameters for EMS. Use counter
277
+ sorted_chiller_list.each_with_index do |chiller, i|
278
+ # Get chiller pump
279
+ pump_name = "#{chiller.name} Inlet Pump"
280
+ pump = model.getPumpVariableSpeedByName(pump_name).get
281
+
282
+ # Set EMS names
283
+ ems_pump_flow_name = "CHILLER_PUMP_#{i + 1}_FLOW"
284
+ ems_pump_status_name = "CHILLER_PUMP_#{i + 1}_STATUS"
285
+ ems_pump_design_flow_name = "CHILLER_PUMP_#{i + 1}_DES_FLOW"
286
+
287
+ # ---- Actuators ----
288
+
289
+ # Pump Flow Actuator
290
+ actuator_pump_flow = OpenStudio::Model::EnergyManagementSystemActuator.new(pump, 'Pump', 'Pump Mass Flow Rate')
291
+ actuator_pump_flow.setName(ems_pump_flow_name)
292
+
293
+ # Pump Status Actuator
294
+ actuator_pump_status = OpenStudio::Model::EnergyManagementSystemActuator.new(pump,
295
+ 'Plant Component Pump:VariableSpeed',
296
+ 'On/Off Supervisory')
297
+ actuator_pump_status.setName(ems_pump_status_name)
298
+
299
+ # ---- Internal Variable ----
300
+
301
+ internal_variable = OpenStudio::Model::EnergyManagementSystemInternalVariable.new(model, 'Pump Maximum Mass Flow Rate')
302
+ internal_variable.setInternalDataIndexKeyName(pump_name)
303
+ internal_variable.setName(ems_pump_design_flow_name)
160
304
  end
161
305
 
162
- # Ensure there is only 1 pump to start
163
- orig_pump = nil
164
- if pumps.empty?
165
- OpenStudio.logFree(OpenStudio::Error, 'prm.log', "For #{plant_loop.name}, found #{pumps.size} pumps. A loop must have at least one pump.")
166
- return false
167
- elsif pumps.size > 1
168
- OpenStudio.logFree(OpenStudio::Error, 'prm.log', "For #{plant_loop.name}, found #{pumps.size} pumps, cannot split up per performance rating method baseline requirements.")
169
- return false
170
- else
171
- orig_pump = pumps[0]
306
+ # Write EMS program
307
+ if num_of_chillers > 3
308
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.PlantLoop', "EMS Code for multiple chiller pump has not been written for greater than 2 chillers. This has #{num_of_chillers} chillers")
309
+ elsif num_of_chillers == 3
310
+ add_ems_program_for_3_pump_chiller_plant(model, sorted_chiller_list, primary_plant)
311
+ elsif num_of_chillers == 2
312
+ add_ems_program_for_2_pump_chiller_plant(model, sorted_chiller_list, primary_plant)
172
313
  end
173
314
 
174
- # Determine the per-chiller capacity
175
- # and sizing factor
176
- per_chiller_sizing_factor = (1.0 / num_chillers).round(2)
177
- # This is unused
178
- per_chiller_cap_tons = cap_tons / num_chillers
179
- per_chiller_cap_w = cap_w / num_chillers
315
+ # Update chilled water loop operation scheme to work with updated EMS ranges
316
+ stage_chilled_water_loop_operation_schemes(model, primary_plant)
317
+ end
180
318
 
181
- # 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
319
+ # Updates a chilled water plant's operation scheme to match the EMS written by either
320
+ # add_ems_program_for_3_pump_chiller_plant or add_ems_program_for_2_pump_chiller_plant
321
+ # @param model [OpenStudio::Model] OpenStudio model with plant loops
322
+ # @param chilled_water_loop [OpenStudio::Model::PlantLoop] chilled water loop
323
+ def stage_chilled_water_loop_operation_schemes(model, chilled_water_loop)
324
+ # Initialize array of cooling plant systems
325
+ chillers = []
182
326
 
183
- # Add any new chillers
184
- final_chillers = [first_chiller]
185
- (num_chillers - 1).times do
186
- new_chiller = first_chiller.clone(plant_loop.model)
187
- if new_chiller.to_ChillerElectricEIR.is_initialized
188
- new_chiller = new_chiller.to_ChillerElectricEIR.get
189
- else
190
- OpenStudio.logFree(OpenStudio::Error, 'prm.log', "For #{plant_loop.name}, could not clone chiller #{first_chiller.name}, cannot apply the performance rating method number of chillers.")
191
- return false
192
- end
193
- # Connect the new chiller to the same CHW loop
194
- # as the old chiller.
195
- plant_loop.addSupplyBranchForComponent(new_chiller)
196
- # Connect the new chiller to the same CW loop
197
- # as the old chiller, if it was water-cooled.
198
- cw_loop = first_chiller.secondaryPlantLoop
199
- if cw_loop.is_initialized
200
- cw_loop.get.addDemandBranchForComponent(new_chiller)
201
- end
327
+ # Gets all associated chillers from the supply side and adds them to the chillers list
328
+ chilled_water_loop.supplyComponents(OpenStudio::Model::ChillerElectricEIR.iddObjectType).each do |chiller|
329
+ chillers << chiller.to_ChillerElectricEIR.get
330
+ end
202
331
 
203
- final_chillers << new_chiller
332
+ # Skip those without chillers or only 1 (i.e., nothing to stage)
333
+ return if chillers.empty?
334
+ return if chillers.length == 1
335
+
336
+ # Sort chillers by capacity
337
+ sorted_chillers = chillers.sort_by { |chiller| chiller.referenceCapacity.get }
338
+
339
+ primary_chiller = sorted_chillers[0]
340
+ secondary_1_chiller = sorted_chillers[1]
341
+ secondary_2_chiller = sorted_chillers[2] if chillers.length == 3
342
+
343
+ equip_operation_cool_load = OpenStudio::Model::PlantEquipmentOperationCoolingLoad.new(model)
344
+
345
+ # Calculate load ranges into the PlantEquipmentOperation:CoolingLoad
346
+ loading_factor = 0.8
347
+ # # when the capacity of primary chiller is larger than the capacity of secondary chiller - the loading factor
348
+ # # will need to be adjusted to avoid load range intersect.
349
+ # if secondary_1_chiller.referenceCapacity.get <= primary_chiller.referenceCapacity.get * loading_factor
350
+ # # Adjustment_factor can creates a bandwidth for step 2 staging strategy.
351
+ # # set adjustment_factor = 1.0 means the step 2 staging strategy is skipped
352
+ # adjustment_factor = 1.0
353
+ # loading_factor = secondary_1_chiller.referenceCapacity.get / primary_chiller.referenceCapacity.get * adjustment_factor
354
+ # end
355
+
356
+ if chillers.length == 3
357
+
358
+ # Add four ranges for small, medium, and large chiller capacities
359
+ # 1: 0 W -> 80% of smallest chiller capacity
360
+ # 2: 80% of primary chiller -> medium size chiller capacity
361
+ # 3: medium chiller capacity -> medium + large chiller capacity
362
+ # 4: medium + large chiller capacity -> infinity
363
+ # Control strategy first stage
364
+ equipment_list = [primary_chiller]
365
+ range = primary_chiller.referenceCapacity.get * loading_factor
366
+ equip_operation_cool_load.addLoadRange(range, equipment_list)
367
+
368
+ # Control strategy second stage
369
+ equipment_list = [secondary_1_chiller]
370
+ range = secondary_1_chiller.referenceCapacity.get
371
+ equip_operation_cool_load.addLoadRange(range, equipment_list)
372
+
373
+ # Control strategy third stage
374
+ equipment_list = [secondary_1_chiller, secondary_2_chiller]
375
+ range = secondary_1_chiller.referenceCapacity.get + secondary_2_chiller.referenceCapacity.get
376
+ equip_operation_cool_load.addLoadRange(range, equipment_list)
377
+
378
+ equipment_list = [primary_chiller, secondary_1_chiller, secondary_2_chiller]
379
+ range = 999999999
380
+ equip_operation_cool_load.addLoadRange(range, equipment_list)
381
+
382
+ elsif chillers.length == 2
383
+
384
+ # Add three ranges for primary and secondary chiller capacities
385
+ # 1: 0 W -> 80% of smallest chiller capacity
386
+ # 2: 80% of primary chiller -> secondary chiller capacity
387
+ # 3: secondary chiller capacity -> infinity
388
+ # Control strategy first stage
389
+ equipment_list = [primary_chiller]
390
+ range = primary_chiller.referenceCapacity.get * loading_factor
391
+ equip_operation_cool_load.addLoadRange(range, equipment_list)
392
+
393
+ # Control strategy second stage
394
+ equipment_list = [secondary_1_chiller]
395
+ range = secondary_1_chiller.referenceCapacity.get
396
+ equip_operation_cool_load.addLoadRange(range, equipment_list)
397
+
398
+ # Control strategy third stage
399
+ equipment_list = [primary_chiller, secondary_1_chiller]
400
+ range = 999999999
401
+ equip_operation_cool_load.addLoadRange(range, equipment_list)
402
+
403
+ else
404
+ raise "Failed to stage chillers, #{chillers.length} chillers found in the loop.Logic for staging chillers has only been done for either 2 or 3 chillers"
204
405
  end
205
406
 
206
- # If there is more than one cooling tower,
207
- # add one pump to each chiller, assume chillers are equally sized
208
- if final_chillers.size > 1
209
- num_pumps = final_chillers.size
210
- final_chillers.each do |chiller|
211
- if orig_pump.to_PumpConstantSpeed.is_initialized
212
- new_pump = OpenStudio::Model::PumpConstantSpeed.new(plant_loop.model)
213
- new_pump.setName("#{chiller.name} Primary Pump")
214
- # Will need to adjust the pump power after a sizing run
215
- new_pump.setRatedPumpHead(orig_pump.ratedPumpHead / num_pumps)
216
- new_pump.setMotorEfficiency(0.9)
217
- new_pump.setPumpControlType('Intermittent')
218
- chiller_inlet_node = chiller.connectedObject(chiller.supplyInletPort).get.to_Node.get
219
- new_pump.addToNode(chiller_inlet_node)
220
- elsif orig_pump.to_PumpVariableSpeed.is_initialized
221
- new_pump = OpenStudio::Model::PumpVariableSpeed.new(plant_loop.model)
222
- new_pump.setName("#{chiller.name} Primary Pump")
223
- new_pump.setRatedPumpHead(orig_pump.ratedPumpHead / num_pumps)
224
- new_pump.setCoefficient1ofthePartLoadPerformanceCurve(orig_pump.coefficient1ofthePartLoadPerformanceCurve)
225
- new_pump.setCoefficient2ofthePartLoadPerformanceCurve(orig_pump.coefficient2ofthePartLoadPerformanceCurve)
226
- new_pump.setCoefficient3ofthePartLoadPerformanceCurve(orig_pump.coefficient3ofthePartLoadPerformanceCurve)
227
- new_pump.setCoefficient4ofthePartLoadPerformanceCurve(orig_pump.coefficient4ofthePartLoadPerformanceCurve)
228
- chiller_inlet_node = chiller.connectedObject(chiller.supplyInletPort).get.to_Node.get
229
- new_pump.addToNode(chiller_inlet_node)
230
- end
231
- end
232
- # Remove the old pump
233
- orig_pump.remove
407
+ chilled_water_loop.setPlantEquipmentOperationCoolingLoad(equip_operation_cool_load)
408
+ end
409
+
410
+ # Adds EMS program for pumps serving 2 chillers on primary + secondary loop. This was due to an issue when modeling two
411
+ # dedicated loops. The headered pumps or dedicated constant speed pumps operate at full flow as long as there's a
412
+ # load on the loop unless this EMS is in place.
413
+ # @param model [OpenStudio::Model] OpenStudio model with plant loops
414
+ # @param sorted_chiller_list [Array] Array of chillers in primary_plant sorted by capacity
415
+ # @param primary_plant [OpenStudio::Model::PlantLoop] Primary chilled water loop with chillers
416
+ def add_ems_program_for_2_pump_chiller_plant(model, sorted_chiller_list, primary_plant)
417
+ plant_name = primary_plant.name.to_s
418
+
419
+ # Break out sorted chillers and get their respective capacities
420
+ small_chiller = sorted_chiller_list[0]
421
+ large_chiller = sorted_chiller_list[1]
422
+
423
+ capacity_small_chiller = small_chiller.referenceCapacity.get
424
+ capacity_large_chiller = large_chiller.referenceCapacity.get
425
+
426
+ chw_demand = "#{primary_plant.name.to_s.gsub(/[-\s]+/, '_')}_CHW_DEMAND"
427
+
428
+ ems_pump_program = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
429
+ ems_pump_program.setName("#{plant_name.gsub(/[-\s]+/, '_')}_Pump_EMS")
430
+ ems_pump_program.addLine('SET CHILLER_PUMP_1_STATUS = NULL, !- Program Line 1')
431
+ ems_pump_program.addLine('SET CHILLER_PUMP_2_STATUS = NULL, !- Program Line 2')
432
+ ems_pump_program.addLine('SET CHILLER_PUMP_1_FLOW = NULL, !- A3')
433
+ ems_pump_program.addLine('SET CHILLER_PUMP_2_FLOW = NULL, !- A4')
434
+ ems_pump_program.addLine("IF #{chw_demand} <= #{0.8 * capacity_small_chiller}, !- A5")
435
+ ems_pump_program.addLine('SET CHILLER_PUMP_2_STATUS = 0, !- A6')
436
+ ems_pump_program.addLine('SET CHILLER_PUMP_2_FLOW = 0, !- A7')
437
+ ems_pump_program.addLine("ELSEIF #{chw_demand} <= #{capacity_large_chiller}, !- A8")
438
+ ems_pump_program.addLine('SET CHILLER_PUMP_1_STATUS = 0, !- A9')
439
+ ems_pump_program.addLine('SET CHILLER_PUMP_2_STATUS = 1, !- A10')
440
+ ems_pump_program.addLine('SET CHILLER_PUMP_1_FLOW = 0, !- A11')
441
+ ems_pump_program.addLine('SET CHILLER_PUMP_2_FLOW = CHILLER_PUMP_2_DES_FLOW, !- A12')
442
+ ems_pump_program.addLine("ELSEIF #{chw_demand} > #{capacity_large_chiller}, !- A13")
443
+ ems_pump_program.addLine('SET CHILLER_PUMP_1_STATUS = 1, !- A14')
444
+ ems_pump_program.addLine('SET CHILLER_PUMP_2_STATUS = 1, !- A15')
445
+ ems_pump_program.addLine('SET CHILLER_PUMP_1_FLOW = CHILLER_PUMP_1_DES_FLOW, !- A16')
446
+ ems_pump_program.addLine('SET CHILLER_PUMP_2_FLOW = CHILLER_PUMP_2_DES_FLOW, !- A17')
447
+ ems_pump_program.addLine('ENDIF !- A18')
448
+
449
+ ems_pump_program_manager = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model)
450
+ ems_pump_program_manager.setName("#{plant_name.gsub(/[-\s]+/, '_')}_Pump_Program_Manager")
451
+ ems_pump_program_manager.setCallingPoint('InsideHVACSystemIterationLoop')
452
+ ems_pump_program_manager.addProgram(ems_pump_program)
453
+ end
454
+
455
+ # Adds EMS program for pumps serving 3 chillers on primary + secondary loop. This was due to an issue when modeling two
456
+ # dedicated loops. The headered pumps or dedicated constant speed pumps operate at full flow as long as there's a
457
+ # load on the loop unless this EMS is in place.
458
+ # @param model [OpenStudio::Model] OpenStudio model with plant loops
459
+ # @param sorted_chiller_list [Array] Array of chillers in primary_plant sorted by capacity
460
+ # @param primary_plant [OpenStudio::Model::PlantLoop] Primary chilled water loop with chillers
461
+ def add_ems_program_for_3_pump_chiller_plant(model, sorted_chiller_list, primary_plant)
462
+ plant_name = primary_plant.name.to_s
463
+
464
+ # Break out sorted chillers and get their respective capacities
465
+ primary_chiller = sorted_chiller_list[0]
466
+ medium_chiller = sorted_chiller_list[1]
467
+ large_chiller = sorted_chiller_list[2]
468
+
469
+ capacity_80_pct_small = 0.8 * primary_chiller.referenceCapacity.get
470
+ capacity_medium_chiller = medium_chiller.referenceCapacity.get
471
+ capacity_large_chiller = large_chiller.referenceCapacity.get
472
+
473
+ if capacity_80_pct_small >= capacity_medium_chiller
474
+ first_stage_capacity = capacity_medium_chiller
475
+ else
476
+ first_stage_capacity = capacity_80_pct_small
234
477
  end
235
478
 
236
- # Set the sizing factor and the chiller types
237
- final_chillers.each_with_index do |final_chiller, i|
238
- final_chiller.setName("#{template} #{chiller_cooling_type} #{chiller_compressor_type} Chiller #{i + 1} of #{final_chillers.size}")
239
- final_chiller.setSizingFactor(per_chiller_sizing_factor)
240
- final_chiller.setReferenceCapacity(per_chiller_cap_w)
241
- final_chiller.setCondenserType(chiller_cooling_type)
242
- final_chiller.additionalProperties.setFeature('compressor_type', chiller_compressor_type)
479
+ chw_demand = "#{primary_plant.name.to_s.gsub(/[-\s]+/, '_')}_CHW_DEMAND"
480
+
481
+ ems_pump_program = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
482
+ ems_pump_program.setName("#{plant_name.gsub(/[-\s]+/, '_')}_Pump_EMS")
483
+ ems_pump_program.addLine('SET CHILLER_PUMP_1_STATUS = NULL, !- Program Line 1')
484
+ ems_pump_program.addLine('SET CHILLER_PUMP_2_STATUS = NULL, !- Program Line 2')
485
+ ems_pump_program.addLine('SET CHILLER_PUMP_3_STATUS = NULL, !- A4')
486
+ ems_pump_program.addLine('SET CHILLER_PUMP_1_FLOW = NULL, !- A5')
487
+ ems_pump_program.addLine('SET CHILLER_PUMP_2_FLOW = NULL, !- A6')
488
+ ems_pump_program.addLine('SET CHILLER_PUMP_3_FLOW = NULL, !- A7')
489
+ ems_pump_program.addLine("IF #{chw_demand} <= #{first_stage_capacity}, !- A8")
490
+ ems_pump_program.addLine('SET CHILLER_PUMP_2_STATUS = 0, !- A9')
491
+ ems_pump_program.addLine('SET CHILLER_PUMP_3_STATUS = 0, !- A10')
492
+ ems_pump_program.addLine('SET CHILLER_PUMP_2_FLOW = 0, !- A11')
493
+ ems_pump_program.addLine('SET CHILLER_PUMP_3_FLOW = 0, !- A12')
494
+
495
+ if capacity_80_pct_small < capacity_medium_chiller
496
+ ems_pump_program.addLine("ELSEIF #{chw_demand} <= #{capacity_medium_chiller}, !- A13")
497
+ ems_pump_program.addLine('SET CHILLER_PUMP_1_STATUS = 0, !- A14')
498
+ ems_pump_program.addLine('SET CHILLER_PUMP_2_STATUS = 1, !- A15')
499
+ ems_pump_program.addLine('SET CHILLER_PUMP_3_STATUS = 0, !- A16')
500
+ ems_pump_program.addLine('SET CHILLER_PUMP_1_FLOW = 0, !- A17')
501
+ ems_pump_program.addLine('SET CHILLER_PUMP_2_FLOW = CHILLER_PUMP_2_DES_FLOW, !- A18')
502
+ ems_pump_program.addLine('SET CHILLER_PUMP_3_FLOW = 0, !- A19')
243
503
  end
244
- OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.PlantLoop', "For #{plant_loop.name}, there are #{final_chillers.size} #{chiller_cooling_type} #{chiller_compressor_type} chillers.")
245
504
 
246
- return true
505
+ ems_pump_program.addLine("ELSEIF #{chw_demand} <= #{capacity_medium_chiller + capacity_large_chiller}, !- A20")
506
+ ems_pump_program.addLine('SET CHILLER_PUMP_1_STATUS = 0, !- A21')
507
+ ems_pump_program.addLine('SET CHILLER_PUMP_2_STATUS = 1, !- A22')
508
+ ems_pump_program.addLine('SET CHILLER_PUMP_3_STATUS = 1, !- A23')
509
+ ems_pump_program.addLine('SET CHILLER_PUMP_1_FLOW = 0, !- A24')
510
+ ems_pump_program.addLine('SET CHILLER_PUMP_2_FLOW = CHILLER_PUMP_2_DES_FLOW, !- A25')
511
+ ems_pump_program.addLine('SET CHILLER_PUMP_3_FLOW = CHILLER_PUMP_3_DES_FLOW, !- A26')
512
+ ems_pump_program.addLine("ELSEIF #{chw_demand} > #{capacity_medium_chiller + capacity_large_chiller}, !- A27")
513
+ ems_pump_program.addLine('SET CHILLER_PUMP_1_STATUS = 1, !- A28')
514
+ ems_pump_program.addLine('SET CHILLER_PUMP_2_STATUS = 1, !- A29')
515
+ ems_pump_program.addLine('SET CHILLER_PUMP_3_STATUS = 1, !- A30')
516
+ ems_pump_program.addLine('SET CHILLER_PUMP_1_FLOW = CHILLER_PUMP_1_DES_FLOW, !- A31')
517
+ ems_pump_program.addLine('SET CHILLER_PUMP_2_FLOW = CHILLER_PUMP_2_DES_FLOW, !- A32')
518
+ ems_pump_program.addLine('SET CHILLER_PUMP_3_FLOW = CHILLER_PUMP_3_DES_FLOW, !- A33')
519
+ ems_pump_program.addLine('ENDIF !- A34')
520
+
521
+ ems_pump_program_manager = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(model)
522
+ ems_pump_program_manager.setName("#{plant_name.gsub(/[-\s]+/, '_')}_Pump_Program_Manager")
523
+ ems_pump_program_manager.setCallingPoint('InsideHVACSystemIterationLoop')
524
+ ems_pump_program_manager.addProgram(ems_pump_program)
247
525
  end
248
526
 
249
527
  # Apply prm baseline pump power
@@ -552,7 +552,7 @@ class ASHRAE901PRM < Standard
552
552
  space_area = space.floorArea
553
553
  space_height = OpenStudio.convert(space_volume / space_area, 'm', 'ft').get
554
554
  # calculate the new lpd values
555
- space_lighting_per_area = (lighting_per_length * space_height) + lighting_per_area
555
+ space_lighting_per_area = ((lighting_per_length * space_height) / space_area) + lighting_per_area
556
556
 
557
557
  # Adjust the occupancy control sensor reduction factor from dataset
558
558
  if manon_or_partauto == 1
@@ -652,7 +652,7 @@ class ASHRAE901PRM < Standard
652
652
  space_area = space.floorArea
653
653
  space_height = OpenStudio.convert(space_volume / space_area, 'm', 'ft').get
654
654
  # calculate and add new lpd values
655
- user_space_type_lighting_per_area = ((lighting_per_length * space_height) + lighting_per_area) * sub_space_type_frac
655
+ user_space_type_lighting_per_area = (((lighting_per_length * space_height) / space_area) + lighting_per_area) * sub_space_type_frac
656
656
  space_lighting_per_area += user_space_type_lighting_per_area
657
657
 
658
658
  # Adjust the occupancy control sensor reduction factor from dataset
@@ -37,13 +37,13 @@ class ASHRAE901PRM2019 < ASHRAE901PRM
37
37
  wwr_range['minimum_percent_of_surface'] = 0
38
38
  wwr_range['maximum_percent_of_surface'] = 10
39
39
  elsif wwr_info[wwr_building_type] <= 20
40
- wwr_range['minimum_percent_of_surface'] = 10.1
40
+ wwr_range['minimum_percent_of_surface'] = 10.001
41
41
  wwr_range['maximum_percent_of_surface'] = 20
42
42
  elsif wwr_info[wwr_building_type] <= 30
43
- wwr_range['minimum_percent_of_surface'] = 20.1
43
+ wwr_range['minimum_percent_of_surface'] = 20.001
44
44
  wwr_range['maximum_percent_of_surface'] = 30
45
45
  elsif wwr_info[wwr_building_type] <= 40
46
- wwr_range['minimum_percent_of_surface'] = 30.1
46
+ wwr_range['minimum_percent_of_surface'] = 30.001
47
47
  wwr_range['maximum_percent_of_surface'] = 40
48
48
  else
49
49
  wwr_range['minimum_percent_of_surface'] = nil