openstudio-standards 0.4.0 → 0.5.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (376) hide show
  1. checksums.yaml +4 -4
  2. data/data/inventory/thermal_bridging.csv +90 -0
  3. data/data/standards/OpenStudio_Standards-deer-comstock.xlsx +0 -0
  4. data/data/standards/manage_OpenStudio_Standards.rb +1 -1
  5. data/data/standards/test_performance_expected_dd_results.csv +2014 -1891
  6. data/lib/openstudio-standards/btap/analysis.rb +8 -8
  7. data/lib/openstudio-standards/btap/bridging.rb +664 -645
  8. data/lib/openstudio-standards/btap/btap.model.rb +14 -14
  9. data/lib/openstudio-standards/btap/btap.rb +7 -7
  10. data/lib/openstudio-standards/btap/btap_result.rb +1 -1
  11. data/lib/openstudio-standards/btap/economics.rb +23 -23
  12. data/lib/openstudio-standards/btap/envelope.rb +8 -8
  13. data/lib/openstudio-standards/btap/equest.rb +1 -1
  14. data/lib/openstudio-standards/btap/geometry.rb +2 -2
  15. data/lib/openstudio-standards/btap/mpc.rb +7 -7
  16. data/lib/openstudio-standards/btap/schedules.rb +1 -1
  17. data/lib/openstudio-standards/btap/simmanager.rb +4 -4
  18. data/lib/openstudio-standards/btap/spaceloads.rb +26 -26
  19. data/lib/openstudio-standards/btap/utilities.rb +6 -6
  20. data/lib/openstudio-standards/btap/vintagizer.rb +1 -1
  21. data/lib/openstudio-standards/constructions/information.rb +83 -0
  22. data/lib/openstudio-standards/constructions/materials/modify.rb +72 -0
  23. data/lib/openstudio-standards/constructions/modify.rb +80 -0
  24. data/lib/openstudio-standards/create_typical/create_typical.rb +983 -0
  25. data/lib/openstudio-standards/create_typical/enumerations.rb +484 -0
  26. data/lib/openstudio-standards/create_typical/space_type_blend.rb +791 -0
  27. data/lib/openstudio-standards/create_typical/space_type_ratios.rb +494 -0
  28. data/lib/openstudio-standards/daylighting/space.rb +47 -0
  29. data/lib/openstudio-standards/geometry/create.rb +801 -0
  30. data/lib/openstudio-standards/geometry/create_bar.rb +2171 -0
  31. data/lib/openstudio-standards/geometry/information.rb +462 -0
  32. data/lib/openstudio-standards/geometry/modify.rb +48 -0
  33. data/lib/openstudio-standards/hvac/air_loop/information.rb +79 -0
  34. data/lib/openstudio-standards/hvac/cbecs_hvac.rb +616 -0
  35. data/lib/openstudio-standards/hvac/setpoint_managers/information.rb +91 -0
  36. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2004/ashrae_90_1_2004.AirTerminalSingleDuctVAVReheat.rb +1 -1
  37. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2007/ashrae_90_1_2007.AirTerminalSingleDuctVAVReheat.rb +1 -1
  38. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2010/ashrae_90_1_2010.AirTerminalSingleDuctVAVReheat.rb +1 -1
  39. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2010/ashrae_90_1_2010.Model.rb +1 -1
  40. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.AirTerminalSingleDuctVAVReheat.rb +1 -1
  41. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.Model.rb +2 -2
  42. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.hvac_systems.rb +1 -1
  43. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.AirTerminalSingleDuctVAVReheat.rb +1 -1
  44. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Model.rb +4 -36
  45. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.hvac_systems.rb +1 -1
  46. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.AirTerminalSingleDuctVAVReheat.rb +1 -1
  47. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.Model.rb +4 -36
  48. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.Space.rb +3 -3
  49. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.hvac_systems.rb +1 -1
  50. data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_1980_2004/doe_ref_1980_2004.AirTerminalSingleDuctVAVReheat.rb +1 -1
  51. data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_1980_2004/doe_ref_1980_2004.Model.elevators.rb +1 -1
  52. data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_pre_1980/doe_ref_pre_1980.AirTerminalSingleDuctVAVReheat.rb +1 -1
  53. data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_pre_1980/doe_ref_pre_1980.CoilHeatingGas.rb +1 -1
  54. data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_pre_1980/doe_ref_pre_1980.Model.elevators.rb +1 -1
  55. data/lib/openstudio-standards/prototypes/ashrae_90_1/nrel_nze_ready_2017/nrel_zne_ready_2017.AirTerminalSingleDuctVAVReheat.rb +1 -1
  56. data/lib/openstudio-standards/prototypes/ashrae_90_1/ze_aedg_multifamily/ze_aedg_multifamily.AirTerminalSingleDuctVAVReheat.rb +1 -1
  57. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.College.rb +7 -7
  58. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.Courthouse.rb +8 -8
  59. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.FullServiceRestaurant.rb +14 -14
  60. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.HighRiseApartment.rb +9 -9
  61. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.Hospital.rb +16 -16
  62. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.Laboratory.rb +7 -7
  63. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.LargeDataCenterHighITE.rb +8 -8
  64. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.LargeDataCenterLowITE.rb +8 -8
  65. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.LargeHotel.rb +11 -11
  66. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.LargeOffice.rb +7 -7
  67. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.LargeOfficeDetailed.rb +9 -9
  68. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.MediumOffice.rb +8 -8
  69. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.MediumOfficeDetailed.rb +11 -11
  70. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.MidriseApartment.rb +9 -9
  71. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.Outpatient.rb +19 -19
  72. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.PrimarySchool.rb +10 -10
  73. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.QuickServiceRestaurant.rb +13 -13
  74. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.RetailStandalone.rb +6 -6
  75. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.RetailStripmall.rb +6 -6
  76. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SecondarySchool.rb +9 -9
  77. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SmallDataCenterHighITE.rb +8 -8
  78. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SmallDataCenterLowITE.rb +8 -8
  79. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SmallHotel.rb +8 -8
  80. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SmallOffice.rb +8 -8
  81. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SmallOfficeDetailed.rb +11 -11
  82. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SuperMarket.rb +10 -10
  83. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SuperTallBuilding.rb +19 -19
  84. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.TallBuilding.rb +18 -18
  85. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.Warehouse.rb +6 -6
  86. data/lib/openstudio-standards/prototypes/common/do_not_edit_metaclasses.rb +957 -957
  87. data/lib/openstudio-standards/prototypes/common/objects/Prototype.AirConditionerVariableRefrigerantFlow.rb +1 -1
  88. data/lib/openstudio-standards/prototypes/common/objects/Prototype.AirTerminalSingleDuctVAVReheat.rb +1 -1
  89. data/lib/openstudio-standards/prototypes/common/objects/Prototype.CoilCoolingWaterToAirHeatPumpEquationFit.rb +84 -16
  90. data/lib/openstudio-standards/prototypes/common/objects/Prototype.CoilHeatingDXSingleSpeed.rb +1 -1
  91. data/lib/openstudio-standards/prototypes/common/objects/Prototype.CoilHeatingGas.rb +1 -1
  92. data/lib/openstudio-standards/prototypes/common/objects/Prototype.CoilHeatingWaterToAirHeatPumpEquationFit.rb +61 -10
  93. data/lib/openstudio-standards/prototypes/common/objects/Prototype.ControllerWaterCoil.rb +1 -1
  94. data/lib/openstudio-standards/prototypes/common/objects/Prototype.CoolingTower.rb +1 -1
  95. data/lib/openstudio-standards/prototypes/common/objects/Prototype.Fan.rb +1 -1
  96. data/lib/openstudio-standards/prototypes/common/objects/Prototype.FanConstantVolume.rb +1 -1
  97. data/lib/openstudio-standards/prototypes/common/objects/Prototype.FanOnOff.rb +1 -1
  98. data/lib/openstudio-standards/prototypes/common/objects/Prototype.FanVariableVolume.rb +1 -1
  99. data/lib/openstudio-standards/prototypes/common/objects/Prototype.FanZoneExhaust.rb +1 -1
  100. data/lib/openstudio-standards/prototypes/common/objects/Prototype.HeatExchangerAirToAirSensibleAndLatent.rb +2 -2
  101. data/lib/openstudio-standards/prototypes/common/objects/Prototype.Model.exterior_lights.rb +4 -4
  102. data/lib/openstudio-standards/prototypes/common/objects/Prototype.Model.hvac.rb +4 -4
  103. data/lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb +43 -30
  104. data/lib/openstudio-standards/prototypes/common/objects/Prototype.Model.swh.rb +1 -1
  105. data/lib/openstudio-standards/prototypes/common/objects/Prototype.ServiceWaterHeating.rb +18 -11
  106. data/lib/openstudio-standards/prototypes/common/objects/Prototype.SizingSystem.rb +1 -1
  107. data/lib/openstudio-standards/prototypes/common/objects/Prototype.hvac_systems.rb +774 -117
  108. data/lib/openstudio-standards/prototypes/common/objects/Prototype.radiant_system_controls.rb +340 -481
  109. data/lib/openstudio-standards/prototypes/common/objects/Prototype.refrigeration.rb +3 -3
  110. data/lib/openstudio-standards/prototypes/common/objects/Prototype.utilities.rb +3 -3
  111. data/lib/openstudio-standards/prototypes/common/prototype_metaprogramming.rb +22 -22
  112. data/lib/openstudio-standards/prototypes/deer/deer.Model.rb +1 -1
  113. data/lib/openstudio-standards/qaqc/calibration.rb +131 -0
  114. data/lib/openstudio-standards/qaqc/create_results.rb +983 -0
  115. data/lib/openstudio-standards/qaqc/envelope.rb +399 -0
  116. data/lib/openstudio-standards/qaqc/eui.rb +213 -0
  117. data/lib/openstudio-standards/qaqc/hvac.rb +1943 -0
  118. data/lib/openstudio-standards/qaqc/internal_loads.rb +568 -0
  119. data/lib/openstudio-standards/qaqc/reporting.rb +141 -0
  120. data/lib/openstudio-standards/qaqc/schedules.rb +129 -0
  121. data/lib/openstudio-standards/qaqc/service_water_heating.rb +273 -0
  122. data/lib/openstudio-standards/qaqc/weather_files.rb +497 -0
  123. data/lib/openstudio-standards/qaqc/zone_conditions.rb +278 -0
  124. data/lib/openstudio-standards/schedules/create.rb +364 -0
  125. data/lib/openstudio-standards/schedules/information.rb +169 -0
  126. data/lib/openstudio-standards/schedules/modify.rb +445 -0
  127. data/lib/openstudio-standards/standards/Standards.AirLoopHVAC.rb +110 -71
  128. data/lib/openstudio-standards/standards/Standards.AirTerminalSingleDuctParallelPIUReheat.rb +3 -3
  129. data/lib/openstudio-standards/standards/Standards.AirTerminalSingleDuctVAVReheat.rb +4 -4
  130. data/lib/openstudio-standards/standards/Standards.BoilerHotWater.rb +2 -1
  131. data/lib/openstudio-standards/standards/Standards.ChillerElectricEIR.rb +16 -10
  132. data/lib/openstudio-standards/standards/Standards.CoilCoolingDXSingleSpeed.rb +4 -4
  133. data/lib/openstudio-standards/standards/Standards.CoilCoolingDXTwoSpeed.rb +1 -1
  134. data/lib/openstudio-standards/standards/Standards.CoilCoolingWaterToAirHeatPumpEquationFit.rb +1 -1
  135. data/lib/openstudio-standards/standards/Standards.CoilDX.rb +4 -4
  136. data/lib/openstudio-standards/standards/Standards.CoilHeatingDXMultiSpeed.rb +1 -1
  137. data/lib/openstudio-standards/standards/Standards.CoilHeatingDXSingleSpeed.rb +5 -5
  138. data/lib/openstudio-standards/standards/Standards.CoilHeatingGas.rb +1 -1
  139. data/lib/openstudio-standards/standards/Standards.CoilHeatingGasMultiStage.rb +1 -1
  140. data/lib/openstudio-standards/standards/Standards.CoilHeatingWaterToAirHeatPumpEquationFit.rb +1 -1
  141. data/lib/openstudio-standards/standards/Standards.Construction.rb +17 -18
  142. data/lib/openstudio-standards/standards/Standards.CoolingTower.rb +6 -6
  143. data/lib/openstudio-standards/standards/Standards.CoolingTowerSingleSpeed.rb +1 -1
  144. data/lib/openstudio-standards/standards/Standards.CoolingTowerTwoSpeed.rb +1 -1
  145. data/lib/openstudio-standards/standards/Standards.CoolingTowerVariableSpeed.rb +1 -1
  146. data/lib/openstudio-standards/standards/Standards.Fan.rb +6 -12
  147. data/lib/openstudio-standards/standards/Standards.FanVariableVolume.rb +2 -2
  148. data/lib/openstudio-standards/standards/Standards.FluidCooler.rb +1 -1
  149. data/lib/openstudio-standards/standards/Standards.HeaderedPumpsVariableSpeed.rb +1 -1
  150. data/lib/openstudio-standards/standards/Standards.HeatExchangerSensLat.rb +3 -3
  151. data/lib/openstudio-standards/standards/Standards.Model.rb +411 -261
  152. data/lib/openstudio-standards/standards/Standards.PlanarSurface.rb +2 -2
  153. data/lib/openstudio-standards/standards/Standards.PlantLoop.rb +94 -29
  154. data/lib/openstudio-standards/standards/Standards.Pump.rb +2 -2
  155. data/lib/openstudio-standards/standards/Standards.ScheduleConstant.rb +2 -2
  156. data/lib/openstudio-standards/standards/Standards.ScheduleRuleset.rb +14 -14
  157. data/lib/openstudio-standards/standards/Standards.Space.rb +37 -30
  158. data/lib/openstudio-standards/standards/Standards.SpaceType.rb +38 -29
  159. data/lib/openstudio-standards/standards/Standards.SubSurface.rb +7 -7
  160. data/lib/openstudio-standards/standards/Standards.Surface.rb +13 -13
  161. data/lib/openstudio-standards/standards/Standards.ThermalZone.rb +109 -66
  162. data/lib/openstudio-standards/standards/Standards.WaterHeaterMixed.rb +11 -4
  163. data/lib/openstudio-standards/standards/Standards.ZoneHVACComponent.rb +6 -6
  164. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/ashrae_90_1_2004.AirLoopHVAC.rb +1 -1
  165. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/ashrae_90_1_2004.PlantLoop.rb +1 -1
  166. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/ashrae_90_1_2004.Space.rb +1 -1
  167. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/ashrae_90_1_2007.Space.rb +1 -1
  168. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/ashrae_90_1_2010.AirLoopHVAC.rb +4 -4
  169. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/ashrae_90_1_2010.AirTerminalSingleDuctVAVReheat.rb +1 -1
  170. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/ashrae_90_1_2010.Space.rb +4 -4
  171. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.AirLoopHVAC.rb +4 -4
  172. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.AirTerminalSingleDuctVAVReheat.rb +1 -1
  173. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.CoolingTowerVariableSpeed.rb +1 -1
  174. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.Model.rb +5 -21
  175. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.Space.rb +4 -4
  176. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.WaterHeaterMixed.rb +1 -1
  177. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.ZoneHVACComponent.rb +1 -1
  178. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.AirLoopHVAC.rb +36 -4
  179. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.AirTerminalSingleDuctVAVReheat.rb +1 -1
  180. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.CoolingTowerVariableSpeed.rb +1 -1
  181. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Space.rb +4 -4
  182. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.ZoneHVACComponent.rb +1 -1
  183. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/comstock_ashrae_90_1_2016/comstock_ashrae_90_1_2016.AirLoopHVAC.rb +26 -0
  184. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.AirLoopHVAC.rb +53 -10
  185. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.AirTerminalSingleDuctVAVReheat.rb +1 -1
  186. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.CoolingTowerVariableSpeed.rb +1 -1
  187. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.Space.rb +6 -6
  188. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.ZoneHVACComponent.rb +2 -2
  189. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/comstock_ashrae_90_1_2019/comstock_ashrae_90_1_2019.AirLoopHVAC.rb +26 -0
  190. data/lib/openstudio-standards/standards/ashrae_90_1/data/ashrae_90_1.curves.json +211 -211
  191. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_1980_2004/data/doe_ref_1980_2004.economizers.json +14 -14
  192. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_1980_2004/doe_ref_1980_2004.AirLoopHVAC.rb +4 -4
  193. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_1980_2004/doe_ref_1980_2004.Model.rb +1 -1
  194. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_1980_2004/doe_ref_1980_2004.PlantLoop.rb +1 -1
  195. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_pre_1980/data/doe_ref_pre_1980.economizers.json +14 -14
  196. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_pre_1980/doe_ref_pre_1980.AirLoopHVAC.rb +4 -4
  197. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_pre_1980/doe_ref_pre_1980.Model.rb +1 -1
  198. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_pre_1980/doe_ref_pre_1980.PlantLoop.rb +1 -1
  199. data/lib/openstudio-standards/standards/ashrae_90_1/nrel_zne_ready_2017/nrel_zne_ready_2017.AirLoopHVAC.rb +6 -6
  200. data/lib/openstudio-standards/standards/ashrae_90_1/nrel_zne_ready_2017/nrel_zne_ready_2017.AirTerminalSingleDuctVAVReheat.rb +1 -1
  201. data/lib/openstudio-standards/standards/ashrae_90_1/nrel_zne_ready_2017/nrel_zne_ready_2017.CoolingTowerVariableSpeed.rb +1 -1
  202. data/lib/openstudio-standards/standards/ashrae_90_1/nrel_zne_ready_2017/nrel_zne_ready_2017.PlantLoop.rb +1 -1
  203. data/lib/openstudio-standards/standards/ashrae_90_1/nrel_zne_ready_2017/nrel_zne_ready_2017.Space.rb +4 -4
  204. data/lib/openstudio-standards/standards/ashrae_90_1/ze_aedg_multifamily/ze_aedg_multifamily.AirLoopHVAC.rb +6 -6
  205. data/lib/openstudio-standards/standards/ashrae_90_1/ze_aedg_multifamily/ze_aedg_multifamily.AirTerminalSingleDuctVAVReheat.rb +1 -1
  206. data/lib/openstudio-standards/standards/ashrae_90_1/ze_aedg_multifamily/ze_aedg_multifamily.CoolingTowerVariableSpeed.rb +1 -1
  207. data/lib/openstudio-standards/standards/ashrae_90_1/ze_aedg_multifamily/ze_aedg_multifamily.PlantLoop.rb +1 -1
  208. data/lib/openstudio-standards/standards/ashrae_90_1/ze_aedg_multifamily/ze_aedg_multifamily.Space.rb +4 -4
  209. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb +22 -28
  210. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirTerminalSingleDuctParallelPIUReheat.rb +1 -1
  211. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirTerminalSingleDuctVAVReheat.rb +2 -2
  212. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.BoilerHotWater.rb +1 -74
  213. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.ChillerElectricEIR.rb +7 -59
  214. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilCoolingDXSingleSpeed.rb +1 -1
  215. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilCoolingDXTwoSpeed.rb +1 -1
  216. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilDX.rb +1 -1
  217. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilHeatingDXSingleSpeed.rb +1 -1
  218. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilHeatingGas.rb +1 -21
  219. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.DesignSpecificationOutdoorAir.rb +101 -0
  220. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.FanConstantVolume.rb +1 -1
  221. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.FanOnOff.rb +1 -1
  222. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.FanVariableVolume.rb +1 -1
  223. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.HeatExchangerSensLat.rb +1 -1
  224. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb +643 -526
  225. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.PlanarSurface.rb +8 -2
  226. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.PlantLoop.rb +17 -77
  227. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Space.rb +74 -16
  228. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.SpaceType.rb +96 -44
  229. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Surface.rb +6 -6
  230. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.ThermalZone.rb +18 -6
  231. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.ZoneHVACComponent.rb +1 -1
  232. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb +328 -74
  233. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/ashrae_90_1_prm_2019.Model.rb +0 -118
  234. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/ashrae_90_1_prm_2019.rb +2 -1
  235. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.heat_rejection.json +1 -1
  236. data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/baseline_outdoor_air.md +35 -0
  237. data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/set_plug_load_measures.md +1 -1
  238. data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/ashrae_90_1_prm.UserData.rb +228 -0
  239. data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/userdata_enums.rb +131 -0
  240. data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/userdata_space.csv +1 -1
  241. data/lib/openstudio-standards/standards/cbes/cbes.AirLoopHVAC.rb +5 -5
  242. data/lib/openstudio-standards/standards/cbes/cbes.Model.rb +1 -1
  243. data/lib/openstudio-standards/standards/cbes/cbes.PlantLoop.rb +1 -1
  244. data/lib/openstudio-standards/standards/cbes/cbes.Space.rb +1 -1
  245. data/lib/openstudio-standards/standards/cbes/cbes_t24_2005/cbes_t24_2005.Space.rb +1 -1
  246. data/lib/openstudio-standards/standards/cbes/cbes_t24_2008/cbes_t24_2008.Space.rb +1 -1
  247. data/lib/openstudio-standards/standards/deer/deer.AirLoopHVAC.rb +109 -27
  248. data/lib/openstudio-standards/standards/deer/deer.Space.rb +1 -1
  249. data/lib/openstudio-standards/standards/deer/deer_1985/data/deer_1985.economizers.json +246 -4
  250. data/lib/openstudio-standards/standards/deer/deer_1996/data/deer_1996.economizers.json +246 -4
  251. data/lib/openstudio-standards/standards/deer/deer_2003/data/deer_2003.economizers.json +246 -4
  252. data/lib/openstudio-standards/standards/deer/deer_2003/deer_2003.ThermalZone.rb +18 -18
  253. data/lib/openstudio-standards/standards/deer/deer_2007/data/deer_2007.economizers.json +246 -4
  254. data/lib/openstudio-standards/standards/deer/deer_2007/deer_2007.ThermalZone.rb +18 -18
  255. data/lib/openstudio-standards/standards/deer/deer_2011/data/deer_2011.economizers.json +246 -4
  256. data/lib/openstudio-standards/standards/deer/deer_2011/deer_2011.ThermalZone.rb +18 -18
  257. data/lib/openstudio-standards/standards/deer/deer_2014/data/deer_2014.economizers.json +248 -6
  258. data/lib/openstudio-standards/standards/deer/deer_2014/deer_2014.Space.rb +3 -3
  259. data/lib/openstudio-standards/standards/deer/deer_2014/deer_2014.ThermalZone.rb +18 -18
  260. data/lib/openstudio-standards/standards/deer/deer_2015/data/deer_2015.economizers.json +248 -6
  261. data/lib/openstudio-standards/standards/deer/deer_2015/deer_2015.Space.rb +3 -3
  262. data/lib/openstudio-standards/standards/deer/deer_2015/deer_2015.ThermalZone.rb +18 -18
  263. data/lib/openstudio-standards/standards/deer/deer_2017/data/deer_2017.economizers.json +248 -6
  264. data/lib/openstudio-standards/standards/deer/deer_2017/deer_2017.Space.rb +3 -3
  265. data/lib/openstudio-standards/standards/deer/deer_2017/deer_2017.ThermalZone.rb +18 -18
  266. data/lib/openstudio-standards/standards/deer/deer_2020/data/deer_2020.economizers.json +248 -6
  267. data/lib/openstudio-standards/standards/deer/deer_2020/deer_2020.AirLoopHVAC.rb +18 -5
  268. data/lib/openstudio-standards/standards/deer/deer_2020/deer_2020.FanVariableVolume.rb +1 -1
  269. data/lib/openstudio-standards/standards/deer/deer_2020/deer_2020.Space.rb +3 -3
  270. data/lib/openstudio-standards/standards/deer/deer_2020/deer_2020.ThermalZone.rb +18 -18
  271. data/lib/openstudio-standards/standards/deer/deer_2025/data/deer_2025.economizers.json +248 -6
  272. data/lib/openstudio-standards/standards/deer/deer_2025/deer_2025.AirLoopHVAC.rb +3 -3
  273. data/lib/openstudio-standards/standards/deer/deer_2025/deer_2025.FanVariableVolume.rb +1 -1
  274. data/lib/openstudio-standards/standards/deer/deer_2025/deer_2025.Space.rb +3 -3
  275. data/lib/openstudio-standards/standards/deer/deer_2030/data/deer_2030.economizers.json +248 -6
  276. data/lib/openstudio-standards/standards/deer/deer_2030/data/deer_2030.heat_pumps.json +2 -2
  277. data/lib/openstudio-standards/standards/deer/deer_2030/deer_2030.AirLoopHVAC.rb +3 -3
  278. data/lib/openstudio-standards/standards/deer/deer_2030/deer_2030.FanVariableVolume.rb +1 -1
  279. data/lib/openstudio-standards/standards/deer/deer_2030/deer_2030.Space.rb +3 -3
  280. data/lib/openstudio-standards/standards/deer/deer_2035/data/deer_2035.economizers.json +248 -6
  281. data/lib/openstudio-standards/standards/deer/deer_2035/deer_2035.AirLoopHVAC.rb +3 -3
  282. data/lib/openstudio-standards/standards/deer/deer_2035/deer_2035.FanVariableVolume.rb +1 -1
  283. data/lib/openstudio-standards/standards/deer/deer_2035/deer_2035.Space.rb +3 -3
  284. data/lib/openstudio-standards/standards/deer/deer_2040/data/deer_2040.economizers.json +248 -6
  285. data/lib/openstudio-standards/standards/deer/deer_2040/deer_2040.AirLoopHVAC.rb +3 -3
  286. data/lib/openstudio-standards/standards/deer/deer_2040/deer_2040.FanVariableVolume.rb +1 -1
  287. data/lib/openstudio-standards/standards/deer/deer_2040/deer_2040.Space.rb +3 -3
  288. data/lib/openstudio-standards/standards/deer/deer_2045/data/deer_2045.economizers.json +260 -0
  289. data/lib/openstudio-standards/standards/deer/deer_2045/deer_2045.AirLoopHVAC.rb +3 -3
  290. data/lib/openstudio-standards/standards/deer/deer_2045/deer_2045.FanVariableVolume.rb +1 -1
  291. data/lib/openstudio-standards/standards/deer/deer_2045/deer_2045.Space.rb +3 -3
  292. data/lib/openstudio-standards/standards/deer/deer_2050/data/deer_2050.economizers.json +248 -6
  293. data/lib/openstudio-standards/standards/deer/deer_2050/deer_2050.AirLoopHVAC.rb +3 -3
  294. data/lib/openstudio-standards/standards/deer/deer_2050/deer_2050.FanVariableVolume.rb +1 -1
  295. data/lib/openstudio-standards/standards/deer/deer_2050/deer_2050.Space.rb +3 -3
  296. data/lib/openstudio-standards/standards/deer/deer_2055/data/deer_2055.economizers.json +248 -6
  297. data/lib/openstudio-standards/standards/deer/deer_2055/deer_2055.AirLoopHVAC.rb +3 -3
  298. data/lib/openstudio-standards/standards/deer/deer_2055/deer_2055.FanVariableVolume.rb +1 -1
  299. data/lib/openstudio-standards/standards/deer/deer_2055/deer_2055.Space.rb +3 -3
  300. data/lib/openstudio-standards/standards/deer/deer_2060/data/deer_2060.economizers.json +248 -6
  301. data/lib/openstudio-standards/standards/deer/deer_2060/deer_2060.AirLoopHVAC.rb +3 -3
  302. data/lib/openstudio-standards/standards/deer/deer_2060/deer_2060.FanVariableVolume.rb +1 -1
  303. data/lib/openstudio-standards/standards/deer/deer_2060/deer_2060.Space.rb +3 -3
  304. data/lib/openstudio-standards/standards/deer/deer_2065/data/deer_2065.economizers.json +248 -6
  305. data/lib/openstudio-standards/standards/deer/deer_2065/deer_2065.AirLoopHVAC.rb +3 -3
  306. data/lib/openstudio-standards/standards/deer/deer_2065/deer_2065.FanVariableVolume.rb +1 -1
  307. data/lib/openstudio-standards/standards/deer/deer_2065/deer_2065.Space.rb +3 -3
  308. data/lib/openstudio-standards/standards/deer/deer_2070/data/deer_2070.economizers.json +248 -6
  309. data/lib/openstudio-standards/standards/deer/deer_2070/deer_2070.AirLoopHVAC.rb +3 -3
  310. data/lib/openstudio-standards/standards/deer/deer_2070/deer_2070.FanVariableVolume.rb +1 -1
  311. data/lib/openstudio-standards/standards/deer/deer_2070/deer_2070.Space.rb +3 -3
  312. data/lib/openstudio-standards/standards/deer/deer_2075/data/deer_2075.economizers.json +248 -6
  313. data/lib/openstudio-standards/standards/deer/deer_2075/deer_2075.AirLoopHVAC.rb +3 -3
  314. data/lib/openstudio-standards/standards/deer/deer_2075/deer_2075.FanVariableVolume.rb +1 -1
  315. data/lib/openstudio-standards/standards/deer/deer_2075/deer_2075.Space.rb +3 -3
  316. data/lib/openstudio-standards/standards/deer/deer_pre_1975/data/deer_pre_1975.economizers.json +246 -4
  317. data/lib/openstudio-standards/standards/necb/BTAP1980TO2010/data/space_types.json +447 -223
  318. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/building_envelope.rb +1 -1
  319. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/data/space_types.json +447 -223
  320. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/hvac_systems.rb +5 -2
  321. data/lib/openstudio-standards/standards/necb/ECMS/data/chiller_types.json +25 -0
  322. data/lib/openstudio-standards/standards/necb/ECMS/data/chillers.json +44 -0
  323. data/lib/openstudio-standards/standards/necb/ECMS/data/curves.json +225 -0
  324. data/lib/openstudio-standards/standards/necb/ECMS/ecms.rb +2 -2
  325. data/lib/openstudio-standards/standards/necb/ECMS/hvac_systems.rb +193 -73
  326. data/lib/openstudio-standards/standards/necb/ECMS/pv_ground.rb +1 -1
  327. data/lib/openstudio-standards/standards/necb/NECB2011/autozone.rb +10 -4
  328. data/lib/openstudio-standards/standards/necb/NECB2011/beps_compliance_path.rb +7 -7
  329. data/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb +4 -5
  330. data/lib/openstudio-standards/standards/necb/NECB2011/data/chiller_types.json +32 -0
  331. data/lib/openstudio-standards/standards/necb/NECB2011/data/chillers.json +1 -1
  332. data/lib/openstudio-standards/standards/necb/NECB2011/data/constants.json +36 -0
  333. data/lib/openstudio-standards/standards/necb/NECB2011/data/fuel_type_sets.json +7 -7
  334. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/NorthernEducation.osm +47587 -0
  335. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/NorthernHealthCare.osm +49764 -0
  336. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/Warehouse.osm +283 -297
  337. data/lib/openstudio-standards/standards/necb/NECB2011/data/space_type_unit_definitions.txt +2 -1
  338. data/lib/openstudio-standards/standards/necb/NECB2011/data/space_types.json +447 -223
  339. data/lib/openstudio-standards/standards/necb/NECB2011/data/standards_data.rb +3 -3
  340. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_1_multi_speed.rb +1 -1
  341. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb +49 -27
  342. data/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +400 -202
  343. data/lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb +4 -4
  344. data/lib/openstudio-standards/standards/necb/NECB2015/data/space_types.json +637 -318
  345. data/lib/openstudio-standards/standards/necb/NECB2015/hvac_systems.rb +18 -1
  346. data/lib/openstudio-standards/standards/necb/NECB2015/necb_2015.rb +3 -3
  347. data/lib/openstudio-standards/standards/necb/NECB2017/data/space_types.json +637 -318
  348. data/lib/openstudio-standards/standards/necb/NECB2017/hvac_systems.rb +1 -1
  349. data/lib/openstudio-standards/standards/necb/NECB2017/necb_2017.rb +3 -3
  350. data/lib/openstudio-standards/standards/necb/NECB2020/building_envelope.rb +1 -1
  351. data/lib/openstudio-standards/standards/necb/NECB2020/data/space_types.json +615 -307
  352. data/lib/openstudio-standards/standards/necb/NECB2020/service_water_heating.rb +4 -4
  353. data/lib/openstudio-standards/standards/necb/common/btap_data.rb +10 -5
  354. data/lib/openstudio-standards/standards/necb/common/btap_datapoint.rb +1 -1
  355. data/lib/openstudio-standards/utilities/assertion.rb +128 -0
  356. data/lib/openstudio-standards/utilities/logging.rb +2 -3
  357. data/lib/openstudio-standards/utilities/object_info.rb +39 -18
  358. data/lib/openstudio-standards/utilities/schedule_translator.rb +8 -6
  359. data/lib/openstudio-standards/utilities/simulation.rb +24 -11
  360. data/lib/openstudio-standards/utilities/sqlfile.rb +10 -5
  361. data/lib/openstudio-standards/version.rb +1 -1
  362. data/lib/openstudio-standards/weather/Weather.Model.rb +8 -9
  363. data/lib/openstudio-standards/weather/Weather.stat_file.rb +3 -3
  364. data/lib/openstudio-standards/weather/information.rb +35 -0
  365. data/lib/openstudio-standards.rb +69 -5
  366. metadata +52 -16
  367. data/data/standards/OpenStudio_Standards-deer-ALL-comstock(space_types).xlsx +0 -0
  368. data/lib/openstudio-standards/hvac_sizing/Siz.AirLoopHVAC.rb +0 -59
  369. data/lib/openstudio-standards/hvac_sizing/Siz.CoilCoolingWater.rb +0 -13
  370. data/lib/openstudio-standards/hvac_sizing/Siz.HVACComponent.rb +0 -36
  371. data/lib/openstudio-standards/hvac_sizing/Siz.HeatingCoolingFuels.rb +0 -898
  372. data/lib/openstudio-standards/hvac_sizing/Siz.Model.rb +0 -126
  373. data/lib/openstudio-standards/hvac_sizing/Siz.ThermalZone.rb +0 -356
  374. data/lib/openstudio-standards/prototypes/ashrae_90_1/nrel_nze_ready_2017/nrel_zne_ready_2017.Model.rb +0 -35
  375. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoolingTower.rb +0 -110
  376. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoolingTowerVariableSpeed.rb +0 -5
@@ -0,0 +1,1943 @@
1
+ # Module to apply QAQC checks to a model
2
+ module OpenstudioStandards
3
+ module QAQC
4
+ # @!group HVAC
5
+
6
+ # Check the air loop and zone operational vs. sizing temperatures and make sure everything is coordinated.
7
+ # This identifies problems caused by sizing to one set of conditions and operating at a different set.
8
+ #
9
+ # @param category [String] category to bin this check into
10
+ # @param max_sizing_temp_delta [Double] threshold for throwing an error for design sizing temperatures
11
+ # @param max_operating_temp_delta [Double] threshold for throwing an error on operating temperatures
12
+ # @param name_only [Boolean] If true, only return the name of this check
13
+ # @return [OpenStudio::Attribute] OpenStudio Attribute object containing check results
14
+ def self.check_air_loop_temperatures(category, max_sizing_temp_delta: 2.0, max_operating_temp_delta: 5.0, name_only: false)
15
+ # summary of the check
16
+ check_elems = OpenStudio::AttributeVector.new
17
+ check_elems << OpenStudio::Attribute.new('name', 'Air System Temperatures')
18
+ check_elems << OpenStudio::Attribute.new('category', category)
19
+ check_elems << OpenStudio::Attribute.new('description', 'Check that air system sizing and operation temperatures are coordinated.')
20
+
21
+ # stop here if only name is requested this is used to populate display name for arguments
22
+ if name_only == true
23
+ results = []
24
+ check_elems.each do |elem|
25
+ results << elem.valueAsString
26
+ end
27
+ return results
28
+ end
29
+
30
+ begin
31
+ # get the weather file run period (as opposed to design day run period)
32
+ ann_env_pd = nil
33
+ @sql = @model.sqlFile.get
34
+ @sql.availableEnvPeriods.each do |env_pd|
35
+ env_type = @sql.environmentType(env_pd)
36
+ if env_type.is_initialized
37
+ if env_type.get == OpenStudio::EnvironmentType.new('WeatherRunPeriod')
38
+ ann_env_pd = env_pd
39
+ break
40
+ end
41
+ end
42
+ end
43
+
44
+ # only try to get the annual timeseries if an annual simulation was run
45
+ if ann_env_pd.nil?
46
+ check_elems << OpenStudio::Attribute.new('flag', 'Cannot find the annual simulation run period, cannot check equipment part load ratios.')
47
+ return check_elems
48
+ end
49
+
50
+ @model.getAirLoopHVACs.sort.each do |air_loop|
51
+ supply_outlet_node_name = air_loop.supplyOutletNode.name.to_s
52
+ design_cooling_sat = air_loop.sizingSystem.centralCoolingDesignSupplyAirTemperature
53
+ design_cooling_sat = OpenStudio.convert(design_cooling_sat, 'C', 'F').get
54
+ design_heating_sat = air_loop.sizingSystem.centralHeatingDesignSupplyAirTemperature
55
+ design_heating_sat = OpenStudio.convert(design_heating_sat, 'C', 'F').get
56
+
57
+ # check if the system is a unitary system
58
+ is_unitary_system = OpenstudioStandards::HVAC.air_loop_hvac_unitary_system?(air_loop)
59
+ is_direct_evap = OpenstudioStandards::HVAC.air_loop_hvac_direct_evap?(air_loop)
60
+
61
+ if is_unitary_system && !is_direct_evap
62
+ unitary_system_name = nil
63
+ unitary_system_type = '<unspecified>'
64
+ unitary_min_temp_f = nil
65
+ unitary_max_temp_f = nil
66
+ air_loop.supplyComponents.each do |component|
67
+ obj_type = component.iddObjectType.valueName.to_s
68
+ case obj_type
69
+ when 'OS_AirLoopHVAC_UnitarySystem', 'OS_AirLoopHVAC_UnitaryHeatPump_AirToAir', 'OS_AirLoopHVAC_UnitaryHeatPump_AirToAir_MultiSpeed', 'OS_AirLoopHVAC_UnitaryHeatCool_VAVChangeoverBypass'
70
+ unitary_system_name = component.name.to_s
71
+ unitary_system_type = obj_type
72
+ unitary_system_temps = OpenstudioStandards::HVAC.unitary_system_min_max_temperature_value(component)
73
+ unitary_min_temp_f = unitary_system_temps['min_temp']
74
+ unitary_max_temp_f = unitary_system_temps['max_temp']
75
+ end
76
+ end
77
+ # set expected minimums for operating temperatures
78
+ expected_min = unitary_min_temp_f.nil? ? design_cooling_sat : [design_cooling_sat, unitary_min_temp_f].min
79
+ expected_max = unitary_max_temp_f.nil? ? design_heating_sat : [design_heating_sat, unitary_max_temp_f].max
80
+ else
81
+ # get setpoint manager
82
+ spm_name = nil
83
+ spm_type = '<unspecified>'
84
+ spm_min_temp_f = nil
85
+ spm_max_temp_f = nil
86
+ @model.getSetpointManagers.each do |spm|
87
+ if spm.setpointNode.is_initialized
88
+ spm_node = spm.setpointNode.get
89
+ if spm_node.name.to_s == supply_outlet_node_name
90
+ spm_name = spm.name
91
+ spm_type = spm.iddObjectType.valueName.to_s
92
+ spm_temps_f = OpenstudioStandards::HVAC.setpoint_manager_min_max_temperature(spm)
93
+ spm_min_temp_f = spm_temps_f['min_temp']
94
+ spm_max_temp_f = spm_temps_f['max_temp']
95
+ break
96
+ end
97
+ end
98
+ end
99
+
100
+ # check setpoint manager temperatures against design temperatures
101
+ if spm_min_temp_f
102
+ if (spm_min_temp_f - design_cooling_sat).abs > max_sizing_temp_delta
103
+ check_elems << OpenStudio::Attribute.new('flag', "Minor Error: Air loop '#{air_loop.name}' sizing uses a #{design_cooling_sat.round(1)}F design cooling supply air temperature, but the setpoint manager operates down to #{spm_min_temp_f.round(1)}F.")
104
+ end
105
+ end
106
+ if spm_max_temp_f
107
+ if (spm_max_temp_f - design_heating_sat).abs > max_sizing_temp_delta
108
+ check_elems << OpenStudio::Attribute.new('flag', "Minor Error: Air loop '#{air_loop.name}' sizing uses a #{design_heating_sat.round(1)}F design heating supply air temperature, but the setpoint manager operates up to #{spm_max_temp_f.round(1)}F.")
109
+ end
110
+ end
111
+
112
+ # set expected minimums for operating temperatures
113
+ expected_min = spm_min_temp_f.nil? ? design_cooling_sat : [design_cooling_sat, spm_min_temp_f].min
114
+ expected_max = spm_max_temp_f.nil? ? design_heating_sat : [design_heating_sat, spm_max_temp_f].max
115
+
116
+ # check zone sizing temperature against air loop design temperatures
117
+ air_loop.thermalZones.each do |zone|
118
+ # if this zone has a reheat terminal, get the reheat temp for comparison
119
+ reheat_op_f = nil
120
+ reheat_zone = false
121
+ zone.equipment.each do |equipment|
122
+ obj_type = equipment.iddObjectType.valueName.to_s
123
+ case obj_type
124
+ when 'OS_AirTerminal_SingleDuct_ConstantVolume_Reheat'
125
+ term = equipment.to_AirTerminalSingleDuctConstantVolumeReheat.get
126
+ reheat_op_f = OpenStudio.convert(term.maximumReheatAirTemperature, 'C', 'F').get
127
+ reheat_zone = true
128
+ when 'OS_AirTerminal_SingleDuct_VAV_HeatAndCool_Reheat'
129
+ term = equipment.to_AirTerminalSingleDuctVAVHeatAndCoolReheat.get
130
+ reheat_op_f = OpenStudio.convert(term.maximumReheatAirTemperature, 'C', 'F').get
131
+ reheat_zone = true
132
+ when 'OS_AirTerminal_SingleDuct_VAV_Reheat'
133
+ term = equipment.to_AirTerminalSingleDuctVAVReheat.get
134
+ reheat_op_f = OpenStudio.convert(term.maximumReheatAirTemperature, 'C', 'F').get
135
+ reheat_zone = true
136
+ when 'OS_AirTerminal_SingleDuct_ParallelPIU_Reheat'
137
+ # reheat_op_f = # Not an OpenStudio input
138
+ reheat_zone = true
139
+ when 'OS_AirTerminal_SingleDuct_SeriesPIU_Reheat'
140
+ # reheat_op_f = # Not an OpenStudio input
141
+ reheat_zone = true
142
+ end
143
+ end
144
+
145
+ # get the zone heating and cooling SAT for sizing
146
+ sizing_zone = zone.sizingZone
147
+ zone_siz_htg_f = OpenStudio.convert(sizing_zone.zoneHeatingDesignSupplyAirTemperature, 'C', 'F').get
148
+ zone_siz_clg_f = OpenStudio.convert(sizing_zone.zoneCoolingDesignSupplyAirTemperature, 'C', 'F').get
149
+
150
+ # check cooling temperatures
151
+ if (design_cooling_sat - zone_siz_clg_f).abs > max_sizing_temp_delta
152
+ check_elems << OpenStudio::Attribute.new('flag', "Minor Error: Air loop '#{air_loop.name}' sizing uses a #{design_cooling_sat.round(1)}F design cooling supply air temperature but the sizing for zone #{zone.name} uses a cooling supply air temperature of #{zone_siz_clg_f.round(1)}F.")
153
+ end
154
+
155
+ # check heating temperatures
156
+ if reheat_zone && reheat_op_f
157
+ if (reheat_op_f - zone_siz_htg_f).abs > max_sizing_temp_delta
158
+ check_elems << OpenStudio::Attribute.new('flag', "Minor Error: For zone '#{zone.name}', the reheat air temperature is set to #{reheat_op_f.round(1)}F, but the sizing for the zone is done with a heating supply air temperature of #{zone_siz_htg_f.round(1)}F.")
159
+ end
160
+ elsif reheat_zone && !reheat_op_f
161
+ # reheat zone but no reheat temperature available from terminal object
162
+ elsif (design_heating_sat - zone_siz_htg_f).abs > max_sizing_temp_delta
163
+ check_elems << OpenStudio::Attribute.new('flag', "Minor Error: Air loop '#{air_loop.name}' sizing uses a #{design_heating_sat.round(1)}F design heating supply air temperature but the sizing for zone #{zone.name} uses a heating supply air temperature of #{zone_siz_htg_f.round(1)}F.")
164
+ end
165
+ end
166
+ end
167
+
168
+ # get supply air temperatures for supply outlet node
169
+ supply_temp_timeseries = @sql.timeSeries(ann_env_pd, 'Timestep', 'System Node Temperature', supply_outlet_node_name)
170
+ if supply_temp_timeseries.empty?
171
+ check_elems << OpenStudio::Attribute.new('flag', "Warning: No supply node temperature timeseries found for '#{air_loop.name}'")
172
+ next
173
+ else
174
+ # convert to ruby array
175
+ temperatures = []
176
+ supply_temp_vector = supply_temp_timeseries.get.values
177
+ for i in (0..supply_temp_vector.size - 1)
178
+ temperatures << supply_temp_vector[i]
179
+ end
180
+ end
181
+
182
+ # get supply air flow rates for supply outlet node
183
+ supply_flow_timeseries = @sql.timeSeries(ann_env_pd, 'Timestep', 'System Node Standard Density Volume Flow Rate', supply_outlet_node_name)
184
+ if supply_flow_timeseries.empty?
185
+ check_elems << OpenStudio::Attribute.new('flag', "Warning: No supply node temperature timeseries found for '#{air_loop.name}'")
186
+ next
187
+ else
188
+ # convert to ruby array
189
+ flowrates = []
190
+ supply_flow_vector = supply_flow_timeseries.get.values
191
+ for i in (0..supply_flow_vector.size - 1)
192
+ flowrates << supply_flow_vector[i]
193
+ end
194
+ end
195
+ # check reasonableness of supply air temperatures when supply air flow rate is operating
196
+ flow_tolerance = OpenStudio.convert(10.0, 'cfm', 'm^3/s').get
197
+ operating_temperatures = temperatures.select.with_index { |_t, k| flowrates[k] > flow_tolerance }
198
+ operating_temperatures = operating_temperatures.map { |t| (t * 1.8 + 32.0) }
199
+
200
+ next if operating_temperatures.empty?
201
+
202
+ runtime_fraction = operating_temperatures.size.to_f / temperatures.size
203
+ temps_out_of_bounds = operating_temperatures.select { |t| ((t < 40.0) || (t > 110.0) || ((t + max_operating_temp_delta) < expected_min) || ((t - max_operating_temp_delta) > expected_max)) }
204
+
205
+ next if temps_out_of_bounds.empty?
206
+
207
+ min_op_temp_f = temps_out_of_bounds.min
208
+ max_op_temp_f = temps_out_of_bounds.max
209
+ # avg_F = temps_out_of_bounds.inject(:+).to_f / temps_out_of_bounds.size
210
+ err = []
211
+ err << 'Major Error:'
212
+ err << "Expected supply air temperatures out of bounds for air loop '#{air_loop.name}'"
213
+ err << "with #{design_cooling_sat.round(1)}F design cooling SAT"
214
+ err << "and #{design_heating_sat.round(1)}F design heating SAT."
215
+ unless is_unitary_system && !is_direct_evap
216
+ err << "Air loop setpoint manager '#{spm_name}' of type '#{spm_type}' with a"
217
+ err << "#{spm_min_temp_f.round(1)}F minimum setpoint temperature and"
218
+ err << "#{spm_max_temp_f.round(1)}F maximum setpoint temperature."
219
+ end
220
+ if is_unitary_system && !is_direct_evap
221
+ err << "Unitary system '#{unitary_system_name}' of type '#{unitary_system_type}' with"
222
+ temp_str = unitary_min_temp_f.nil? ? 'no' : "#{unitary_min_temp_f.round(1)}F"
223
+ err << "#{temp_str} minimum setpoint temperature and"
224
+ temp_str = unitary_max_temp_f.nil? ? 'no' : "#{unitary_max_temp_f.round(1)}F"
225
+ err << "#{temp_str} maximum setpoint temperature."
226
+ end
227
+ err << "Out of #{operating_temperatures.size}/#{temperatures.size} (#{(runtime_fraction * 100.0).round(1)}%) operating supply air temperatures"
228
+ err << "#{temps_out_of_bounds.size}/#{operating_temperatures.size} (#{((temps_out_of_bounds.size.to_f / operating_temperatures.size) * 100.0).round(1)}%)"
229
+ err << "are out of bounds with #{min_op_temp_f.round(1)}F min and #{max_op_temp_f.round(1)}F max."
230
+ check_elems << OpenStudio::Attribute.new('flag', err.join(' ').gsub(/\n/, ''))
231
+ end
232
+ rescue StandardError => e
233
+ # brief description of ruby error
234
+ check_elems << OpenStudio::Attribute.new('flag', "Major Error: Error prevented QAQC check from running (#{e}).")
235
+
236
+ # backtrace of ruby error for diagnostic use
237
+ if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
238
+ end
239
+
240
+ # add check_elms to new attribute
241
+ check_elem = OpenStudio::Attribute.new('check', check_elems)
242
+
243
+ return check_elem
244
+ end
245
+
246
+ # Check the fan power (W/cfm) for each air loop fan in the model to identify unrealistically sized fans.
247
+ #
248
+ # @param category [String] category to bin this check into
249
+ # @param target_standard [String] standard template, e.g. '90.1-2013'
250
+ # @param max_pct_delta [Double] threshold for throwing an error for percent difference
251
+ # @param name_only [Boolean] If true, only return the name of this check
252
+ # @return [OpenStudio::Attribute] OpenStudio Attribute object containing check results
253
+ def self.check_air_loop_fan_power(category, target_standard, max_pct_delta: 0.3, name_only: false)
254
+ # summary of the check
255
+ check_elems = OpenStudio::AttributeVector.new
256
+ check_elems << OpenStudio::Attribute.new('name', 'Fan Power')
257
+ check_elems << OpenStudio::Attribute.new('category', category)
258
+ check_elems << OpenStudio::Attribute.new('description', 'Check that fan power vs flow makes sense.')
259
+
260
+ # stop here if only name is requested this is used to populate display name for arguments
261
+ if name_only == true
262
+ results = []
263
+ check_elems.each do |elem|
264
+ results << elem.valueAsString
265
+ end
266
+ return results
267
+ end
268
+
269
+ std = Standard.build(target_standard)
270
+
271
+ begin
272
+ # Check each air loop
273
+ @model.getAirLoopHVACs.sort.each do |air_loop|
274
+ # Set the expected W/cfm
275
+ if air_loop.thermalZones.size.to_i == 1
276
+ # expect single zone systems to be lower
277
+ expected_w_per_cfm = 0.5
278
+ else
279
+ expected_w_per_cfm = 1.1
280
+ end
281
+
282
+ # Check the W/cfm for each fan on each air loop
283
+ air_loop.supplyComponents.each do |component|
284
+ # Get the W/cfm for the fan
285
+ obj_type = component.iddObjectType.valueName.to_s
286
+ case obj_type
287
+ when 'OS_Fan_ConstantVolume'
288
+ actual_w_per_cfm = std.fan_rated_w_per_cfm(component.to_FanConstantVolume.get)
289
+ when 'OS_Fan_OnOff'
290
+ actual_w_per_cfm = std.fan_rated_w_per_cfm(component.to_FanOnOff.get)
291
+ when 'OS_Fan_VariableVolume'
292
+ actual_w_per_cfm = std.fan_rated_w_per_cfm(component.to_FanVariableVolume.get)
293
+ else
294
+ next # Skip non-fan objects
295
+ end
296
+
297
+ # Compare W/cfm to expected/typical values
298
+ if ((expected_w_per_cfm - actual_w_per_cfm) / actual_w_per_cfm).abs > max_pct_delta
299
+ check_elems << OpenStudio::Attribute.new('flag', "For #{component.name} on #{air_loop.name}, the actual fan power of #{actual_w_per_cfm.round(1)} W/cfm is more than #{(max_pct_delta * 100.0).round(2)}% different from the expected #{expected_w_per_cfm} W/cfm.")
300
+ end
301
+ end
302
+ end
303
+ rescue StandardError => e
304
+ # brief description of ruby error
305
+ check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
306
+
307
+ # backtrace of ruby error for diagnostic use
308
+ if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
309
+ end
310
+
311
+ # add check_elms to new attribute
312
+ check_elem = OpenStudio::Attribute.new('check', check_elems)
313
+
314
+ return check_elem
315
+ end
316
+
317
+ # checks the HVAC system type against 90.1 baseline system type
318
+ #
319
+ # @param category [String] category to bin this check into
320
+ # @param target_standard [String] standard template, e.g. '90.1-2013'
321
+ # @param name_only [Boolean] If true, only return the name of this check
322
+ # @return [OpenStudio::Attribute] OpenStudio Attribute object containing check results
323
+ def self.check_hvac_system_type(category, target_standard, name_only: false)
324
+ # summary of the check
325
+ check_elems = OpenStudio::AttributeVector.new
326
+ check_elems << OpenStudio::Attribute.new('name', 'Mechanical System Type')
327
+ check_elems << OpenStudio::Attribute.new('category', category)
328
+
329
+ # add ASHRAE to display of target standard if includes with 90.1
330
+ if target_standard.include?('90.1 2013')
331
+ check_elems << OpenStudio::Attribute.new('description', 'Check against ASHRAE 90.1 2013 Tables G3.1.1 A-B. Infers the baseline system type based on the equipment serving the zone and their heating/cooling fuels. Only does a high-level inference; does not look for the presence/absence of required controls, etc.')
332
+ else
333
+ check_elems << OpenStudio::Attribute.new('description', 'Check against ASHRAE 90.1. Infers the baseline system type based on the equipment serving the zone and their heating/cooling fuels. Only does a high-level inference; does not look for the presence/absence of required controls, etc.')
334
+ end
335
+
336
+ # stop here if only name is requested this is used to populate display name for arguments
337
+ if name_only == true
338
+ results = []
339
+ check_elems.each do |elem|
340
+ results << elem.valueAsString
341
+ end
342
+ return results
343
+ end
344
+
345
+ std = Standard.build(target_standard)
346
+
347
+ begin
348
+ # Get the actual system type for all zones in the model
349
+ act_zone_to_sys_type = {}
350
+ @model.getThermalZones.each do |zone|
351
+ act_zone_to_sys_type[zone] = std.thermal_zone_infer_system_type(zone)
352
+ end
353
+
354
+ # Get the baseline system type for all zones in the model
355
+ climate_zone = std.model_get_building_properties(@model)['climate_zone']
356
+ req_zone_to_sys_type = std.model_get_baseline_system_type_by_zone(@model, climate_zone)
357
+
358
+ # Compare the actual to the correct
359
+ @model.getThermalZones.each do |zone|
360
+ is_plenum = false
361
+ zone.spaces.each do |space|
362
+ if std.space_plenum?(space)
363
+ is_plenum = true
364
+ end
365
+ end
366
+ next if is_plenum
367
+
368
+ req_sys_type = req_zone_to_sys_type[zone]
369
+ act_sys_type = act_zone_to_sys_type[zone]
370
+
371
+ unless act_sys_type == req_sys_type
372
+ if req_sys_type == '' then req_sys_type = 'Unknown' end
373
+ check_elems << OpenStudio::Attribute.new('flag', "#{zone.name} baseline system type is incorrect. Supposed to be #{req_sys_type}, but was #{act_sys_type} instead.")
374
+ end
375
+ end
376
+ rescue StandardError => e
377
+ # brief description of ruby error
378
+ check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
379
+
380
+ # backtrace of ruby error for diagnostic use
381
+ if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
382
+ end
383
+
384
+ # add check_elms to new attribute
385
+ check_elem = OpenStudio::Attribute.new('check', check_elems)
386
+
387
+ return check_elem
388
+ end
389
+
390
+ # Check mechanical equipment capacity against typical sizing
391
+ #
392
+ # @param category [String] category to bin this check into
393
+ # @param target_standard [String] standard template, e.g. '90.1-2013'
394
+ # @param name_only [Boolean] If true, only return the name of this check
395
+ # @return [OpenStudio::Attribute] OpenStudio Attribute object containing check results
396
+ def self.check_hvac_capacity(category, target_standard, name_only: false)
397
+ # summary of the check
398
+ check_elems = OpenStudio::AttributeVector.new
399
+ check_elems << OpenStudio::Attribute.new('name', 'Mechanical System Capacity')
400
+ check_elems << OpenStudio::Attribute.new('category', category)
401
+ check_elems << OpenStudio::Attribute.new('description', 'Check HVAC capacity against ASHRAE rules of thumb for chiller max flow rate, air loop max flow rate, air loop cooling capciaty, and zone heating capcaity. Zone heating check will skip thermal zones without any exterior exposure, and thermal zones that are not conditioned.')
402
+
403
+ # stop here if only name is requested this is used to populate display name for arguments
404
+ if name_only == true
405
+ results = []
406
+ check_elems.each do |elem|
407
+ results << elem.valueAsString
408
+ end
409
+ return results
410
+ end
411
+
412
+ std = Standard.build(target_standard)
413
+
414
+ # Sizing benchmarks. Each option has a target value, min and max fractional tolerance, and units.
415
+ # In the future climate zone specific targets may be in standards
416
+ sizing_benchmarks = {}
417
+ sizing_benchmarks['chiller_max_flow_rate'] = { 'min_error' => 1.5, 'min_warning' => 2.0, 'max_warning' => 3.0, 'max_error' => 3.5, 'units' => 'gal/ton*min' }
418
+ sizing_benchmarks['air_loop_max_flow_rate'] = { 'min_error' => 0.2, 'min_warning' => 0.5, 'max_warning' => 2.0, 'max_error' => 4.0, 'units' => 'cfm/ft^2' }
419
+ sizing_benchmarks['air_loop_cooling_capacity'] = { 'min_error' => 200.0, 'min_warning' => 300.0, 'max_warning' => 1500.0, 'max_error' => 2000.0, 'units' => 'ft^2/ton' }
420
+ sizing_benchmarks['zone_heating_capacity'] = { 'min_error' => 4.0, 'min_warning' => 8.0, 'max_warning' => 30.0, 'max_error' => 60.0, 'units' => 'Btu/ft^2*h' }
421
+
422
+ begin
423
+ # check max flow rate of chillers in model
424
+ @model.getPlantLoops.sort.each do |plant_loop|
425
+ # next if no chiller on plant loop
426
+ chillers = []
427
+ plant_loop.supplyComponents.each do |sc|
428
+ if sc.to_ChillerElectricEIR.is_initialized
429
+ chillers << sc.to_ChillerElectricEIR.get
430
+ end
431
+ end
432
+ next if chillers.empty?
433
+
434
+ # gather targets for chiller capacity
435
+ chiller_max_flow_rate_min_error = sizing_benchmarks['chiller_max_flow_rate']['min_error']
436
+ chiller_max_flow_rate_min_warning = sizing_benchmarks['chiller_max_flow_rate']['min_warning']
437
+ chiller_max_flow_rate_max_warning = sizing_benchmarks['chiller_max_flow_rate']['max_warning']
438
+ chiller_max_flow_rate_max_error = sizing_benchmarks['chiller_max_flow_rate']['max_error']
439
+ chiller_max_flow_rate_units_ip = options['chiller_max_flow_rate']['units']
440
+
441
+ # get capacity of loop (not individual chiller but entire loop)
442
+ total_cooling_capacity_w = std.plant_loop_total_cooling_capacity(plant_loop)
443
+ total_cooling_capacity_ton = OpenStudio.convert(total_cooling_capacity_w, 'W', 'Btu/h').get / 12_000.0
444
+
445
+ # get the max flow rate (not individual chiller)
446
+ maximum_loop_flow_rate = std.plant_loop_find_maximum_loop_flow_rate(plant_loop)
447
+ maximum_loop_flow_rate_ip = OpenStudio.convert(maximum_loop_flow_rate, 'm^3/s', 'gal/min').get
448
+
449
+ if total_cooling_capacity_ton < 0.01
450
+ check_elems << OpenStudio::Attribute.new('flag', "Cooling capacity for #{plant_loop.name.get} is too small for flow rate #{maximum_loop_flow_rate_ip.round(2)} gal/min.")
451
+ end
452
+
453
+ # calculate the flow per tons of cooling
454
+ model_flow_rate_per_ton_cooling_ip = maximum_loop_flow_rate_ip / total_cooling_capacity_ton
455
+
456
+ # check flow rate per capacity
457
+ if model_flow_rate_per_ton_cooling_ip < chiller_max_flow_rate_min_error
458
+ check_elems << OpenStudio::Attribute.new('flag', "Error: Flow Rate of #{model_flow_rate_per_ton_cooling_ip.round(2)} #{chiller_max_flow_rate_units_ip} for #{plant_loop.name.get} is below #{chiller_max_flow_rate_min_error.round(2)} #{chiller_max_flow_rate_units_ip}.")
459
+ elsif model_flow_rate_per_ton_cooling_ip < chiller_max_flow_rate_min_warning
460
+ check_elems << OpenStudio::Attribute.new('flag', "Warning: Flow Rate of #{model_flow_rate_per_ton_cooling_ip.round(2)} #{chiller_max_flow_rate_units_ip} for #{plant_loop.name.get} is below #{chiller_max_flow_rate_min_warning.round(2)} #{chiller_max_flow_rate_units_ip}.")
461
+ elsif model_flow_rate_per_ton_cooling_ip > chiller_max_flow_rate_max_warning
462
+ check_elems << OpenStudio::Attribute.new('flag', "Warning: Flow Rate of #{model_flow_rate_per_ton_cooling_ip.round(2)} #{chiller_max_flow_rate_units_ip} for #{plant_loop.name.get} is above #{chiller_max_flow_rate_max_warning.round(2)} #{chiller_max_flow_rate_units_ip}.")
463
+ elsif model_flow_rate_per_ton_cooling_ip > chiller_max_flow_rate_max_error
464
+ check_elems << OpenStudio::Attribute.new('flag', "Error: Flow Rate of #{model_flow_rate_per_ton_cooling_ip.round(2)} #{chiller_max_flow_rate_units_ip} for #{plant_loop.name.get} is above #{chiller_max_flow_rate_max_error.round(2)} #{chiller_max_flow_rate_units_ip}.")
465
+ end
466
+ end
467
+
468
+ # loop through air loops to get max flow rate and cooling capacity.
469
+ @model.getAirLoopHVACs.sort.each do |air_loop|
470
+ # skip DOAS systems for now
471
+ sizing_system = air_loop.sizingSystem
472
+ next if sizing_system.typeofLoadtoSizeOn.to_s == 'VentilationRequirement'
473
+
474
+ # gather argument sizing_benchmarks for air_loop_max_flow_rate checks
475
+ air_loop_max_flow_rate_min_error = sizing_benchmarks['air_loop_max_flow_rate']['min_error']
476
+ air_loop_max_flow_rate_min_warning = sizing_benchmarks['air_loop_max_flow_rate']['min_warning']
477
+ air_loop_max_flow_rate_max_warning = sizing_benchmarks['air_loop_max_flow_rate']['max_warning']
478
+ air_loop_max_flow_rate_max_error = sizing_benchmarks['air_loop_max_flow_rate']['max_error']
479
+ air_loop_max_flow_rate_units_ip = sizing_benchmarks['air_loop_max_flow_rate']['units']
480
+
481
+ # get values from model for air loop checks
482
+ floor_area_served = std.air_loop_hvac_floor_area_served(air_loop)
483
+ design_supply_air_flow_rate = std.air_loop_hvac_find_design_supply_air_flow_rate(air_loop)
484
+
485
+ # check max flow rate of air loops in the model
486
+ model_normalized_flow_rate_si = design_supply_air_flow_rate / floor_area_served
487
+ model_normalized_flow_rate_ip = OpenStudio.convert(model_normalized_flow_rate_si, 'm^3/m^2*s', air_loop_max_flow_rate_units_ip).get
488
+ if model_normalized_flow_rate_ip < air_loop_max_flow_rate_min_error
489
+ check_elems << OpenStudio::Attribute.new('flag', "Error: Flow Rate of #{model_normalized_flow_rate_ip.round(2)} #{air_loop_max_flow_rate_units_ip} for #{air_loop.name.get} is below #{air_loop_max_flow_rate_min_error.round(2)} #{air_loop_max_flow_rate_units_ip}.")
490
+ elsif model_normalized_flow_rate_ip < air_loop_max_flow_rate_min_warning
491
+ check_elems << OpenStudio::Attribute.new('flag', "Warning: Flow Rate of #{model_normalized_flow_rate_ip.round(2)} #{air_loop_max_flow_rate_units_ip} for #{air_loop.name.get} is below #{air_loop_max_flow_rate_min_warning.round(2)} #{air_loop_max_flow_rate_units_ip}.")
492
+ elsif model_normalized_flow_rate_ip > air_loop_max_flow_rate_max_warning
493
+ check_elems << OpenStudio::Attribute.new('flag', "Warning: Flow Rate of #{model_normalized_flow_rate_ip.round(2)} #{air_loop_max_flow_rate_units_ip} for #{air_loop.name.get} is above #{air_loop_max_flow_rate_max_warning.round(2)} #{air_loop_max_flow_rate_units_ip}.")
494
+ elsif model_normalized_flow_rate_ip > air_loop_max_flow_rate_max_error
495
+ check_elems << OpenStudio::Attribute.new('flag', "Error: Flow Rate of #{model_normalized_flow_rate_ip.round(2)} #{air_loop_max_flow_rate_units_ip} for #{air_loop.name.get} is above #{air_loop_max_flow_rate_max_error.round(2)} #{air_loop_max_flow_rate_units_ip}.")
496
+ end
497
+ end
498
+
499
+ # loop through air loops to get max flow rate and cooling capacity.
500
+ @model.getAirLoopHVACs.sort.each do |air_loop|
501
+ # check if DOAS, don't check airflow or cooling capacity if it is
502
+ sizing_system = air_loop.sizingSystem
503
+ next if sizing_system.typeofLoadtoSizeOn.to_s == 'VentilationRequirement'
504
+
505
+ # gather argument options for air_loop_cooling_capacity checks
506
+ air_loop_cooling_capacity_min_error = sizing_benchmarks['air_loop_cooling_capacity']['min_error']
507
+ air_loop_cooling_capacity_min_warning = sizing_benchmarks['air_loop_cooling_capacity']['min_warning']
508
+ air_loop_cooling_capacity_max_warning = sizing_benchmarks['air_loop_cooling_capacity']['max_warning']
509
+ air_loop_cooling_capacity_max_error = sizing_benchmarks['air_loop_cooling_capacity']['max_error']
510
+ air_loop_cooling_capacity_units_ip = sizing_benchmarks['air_loop_cooling_capacity']['units']
511
+
512
+ # check cooling capacity of air loops in the model
513
+ floor_area_served = std.air_loop_hvac_floor_area_served(air_loop)
514
+ capacity = std.air_loop_hvac_total_cooling_capacity(air_loop)
515
+ model_normalized_capacity_si = capacity / floor_area_served
516
+ model_normalized_capacity_ip = OpenStudio.convert(model_normalized_capacity_si, 'W/m^2', 'Btu/ft^2*h').get / 12_000.0
517
+
518
+ # want to display in tons/ft^2 so invert number and display for checks
519
+ model_tons_per_area_ip = 1.0 / model_normalized_capacity_ip
520
+ if model_tons_per_area_ip < air_loop_cooling_capacity_min_error
521
+ check_elems << OpenStudio::Attribute.new('flag', "Cooling Capacity of #{model_tons_per_area_ip.round} #{air_loop_cooling_capacity_units_ip} for #{air_loop.name.get} is below #{air_loop_cooling_capacity_min_error.round} #{air_loop_cooling_capacity_units_ip}.")
522
+ elsif model_tons_per_area_ip < air_loop_cooling_capacity_min_warning
523
+ check_elems << OpenStudio::Attribute.new('flag', "Cooling Capacity of #{model_tons_per_area_ip.round} #{air_loop_cooling_capacity_units_ip} for #{air_loop.name.get} is below #{air_loop_cooling_capacity_min_warning.round} #{air_loop_cooling_capacity_units_ip}.")
524
+ elsif model_tons_per_area_ip > air_loop_cooling_capacity_max_warning
525
+ check_elems << OpenStudio::Attribute.new('flag', "Cooling Capacity of #{model_tons_per_area_ip.round} #{air_loop_cooling_capacity_units_ip} for #{air_loop.name.get} is above #{air_loop_cooling_capacity_max_warning.round} #{air_loop_cooling_capacity_units_ip}.")
526
+ elsif model_tons_per_area_ip > air_loop_cooling_capacity_max_error
527
+ check_elems << OpenStudio::Attribute.new('flag', "Cooling Capacity of #{model_tons_per_area_ip.round} #{air_loop_cooling_capacity_units_ip} for #{air_loop.name.get} is above #{air_loop_cooling_capacity_max_error.round} #{air_loop_cooling_capacity_units_ip}.")
528
+ end
529
+ end
530
+
531
+ # check heating capacity of thermal zones in the model with exterior exposure
532
+ report_name = 'HVACSizingSummary'
533
+ table_name = 'Zone Sensible Heating'
534
+ column_name = 'User Design Load per Area'
535
+ min_error = sizing_benchmarks['zone_heating_capacity']['min_error']
536
+ min_warning = sizing_benchmarks['zone_heating_capacity']['min_warning']
537
+ max_warning = sizing_benchmarks['zone_heating_capacity']['max_warning']
538
+ max_error = sizing_benchmarks['zone_heating_capacity']['max_error']
539
+ units_ip = sizing_benchmarks['zone_heating_capacity']['units']
540
+
541
+ @model.getThermalZones.sort.each do |thermal_zone|
542
+ next if thermal_zone.canBePlenum
543
+ next if thermal_zone.exteriorSurfaceArea == 0.0
544
+
545
+ # check actual against target
546
+ query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}' and RowName= '#{thermal_zone.name.get.upcase}' and ColumnName= '#{column_name}'"
547
+ results = @sql.execAndReturnFirstDouble(query)
548
+ model_zone_heating_capacity_ip = OpenStudio.convert(results.to_f, 'W/m^2', units_ip).get
549
+ if model_zone_heating_capacity_ip < min_error
550
+ check_elems << OpenStudio::Attribute.new('flag', "Heating Capacity of #{model_zone_heating_capacity_ip.round(2)} Btu/ft^2*h for #{thermal_zone.name.get} is below #{min_error.round(1)} Btu/ft^2*h.")
551
+ elsif model_zone_heating_capacity_ip < min_warning
552
+ check_elems << OpenStudio::Attribute.new('flag', "Heating Capacity of #{model_zone_heating_capacity_ip.round(2)} Btu/ft^2*h for #{thermal_zone.name.get} is below #{min_warning.round(1)} Btu/ft^2*h.")
553
+ elsif model_zone_heating_capacity_ip > max_warning
554
+ check_elems << OpenStudio::Attribute.new('flag', "Heating Capacity of #{model_zone_heating_capacity_ip.round(2)} Btu/ft^2*h for #{thermal_zone.name.get} is above #{max_warning.round(1)} Btu/ft^2*h.")
555
+ elsif model_zone_heating_capacity_ip > max_error
556
+ check_elems << OpenStudio::Attribute.new('flag', "Heating Capacity of #{model_zone_heating_capacity_ip.round(2)} Btu/ft^2*h for #{thermal_zone.name.get} is above #{max_error.round(1)} Btu/ft^2*h.")
557
+ end
558
+ end
559
+ rescue StandardError => e
560
+ # brief description of ruby error
561
+ check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
562
+
563
+ # backtrace of ruby error for diagnostic use
564
+ if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
565
+ end
566
+
567
+ # add check_elms to new attribute
568
+ check_elem = OpenStudio::Attribute.new('check', check_elems)
569
+
570
+ return check_elem
571
+ end
572
+
573
+ # Check the mechanical system efficiencies against a standard
574
+ #
575
+ # @param category [String] category to bin this check into
576
+ # @param target_standard [String] standard template, e.g. '90.1-2013'
577
+ # @param min_pass_pct [Double] threshold for throwing an error for percent difference
578
+ # @param max_pass_pct [Double] threshold for throwing an error for percent difference
579
+ # @param name_only [Boolean] If true, only return the name of this check
580
+ # @return [OpenStudio::Attribute] OpenStudio Attribute object containing check results
581
+ def self.check_hvac_efficiency(category, target_standard, min_pass_pct: 0.3, max_pass_pct: 0.3, name_only: false)
582
+ component_type_array = ['ChillerElectricEIR', 'CoilCoolingDXSingleSpeed', 'CoilCoolingDXTwoSpeed', 'CoilHeatingDXSingleSpeed', 'BoilerHotWater', 'FanConstantVolume', 'FanVariableVolume', 'PumpConstantSpeed', 'PumpVariableSpeed']
583
+
584
+ # summary of the check
585
+ check_elems = OpenStudio::AttributeVector.new
586
+ check_elems << OpenStudio::Attribute.new('name', 'Mechanical System Efficiency')
587
+ check_elems << OpenStudio::Attribute.new('category', category)
588
+
589
+ if target_standard.include?('90.1-2013')
590
+ check_elems << OpenStudio::Attribute.new('description', "Check against #{target_standard} Tables 6.8.1 A-K for the following component types: #{component_type_array.join(', ')}.")
591
+ else
592
+ check_elems << OpenStudio::Attribute.new('description', "Check against #{target_standard} for the following component types: #{component_type_array.join(', ')}.")
593
+ end
594
+
595
+ # stop here if only name is requested this is used to populate display name for arguments
596
+ if name_only == true
597
+ results = []
598
+ check_elems.each do |elem|
599
+ results << elem.valueAsString
600
+ end
601
+ return results
602
+ end
603
+
604
+ std = Standard.build(target_standard)
605
+
606
+ begin
607
+ # check ChillerElectricEIR objects (will also have curve check in different script)
608
+ @model.getChillerElectricEIRs.sort.each do |component|
609
+ # eff values from model
610
+ reference_COP = component.referenceCOP
611
+
612
+ # get eff values from standards (if name doesn't have expected strings find object returns first object of multiple)
613
+ standard_minimum_full_load_efficiency = std.chiller_electric_eir_standard_minimum_full_load_efficiency(component)
614
+
615
+ # check actual against target
616
+ if standard_minimum_full_load_efficiency.nil?
617
+ check_elems << OpenStudio::Attribute.new('flag', "Can't find target full load efficiency for #{component.name}.")
618
+ elsif reference_COP < standard_minimum_full_load_efficiency * (1.0 - min_pass_pct)
619
+ check_elems << OpenStudio::Attribute.new('flag', "COP of #{reference_COP.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the expected value of #{standard_minimum_full_load_efficiency.round(2)}.")
620
+ elsif reference_COP > standard_minimum_full_load_efficiency * (1.0 + max_pass_pct)
621
+ check_elems << OpenStudio::Attribute.new('flag', "COP of #{reference_COP.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the expected value of #{standard_minimum_full_load_efficiency.round(2)}.")
622
+ end
623
+ end
624
+
625
+ # check CoilCoolingDXSingleSpeed objects (will also have curve check in different script)
626
+ @model.getCoilCoolingDXSingleSpeeds.each do |component|
627
+ # eff values from model
628
+ rated_COP = component.ratedCOP.get
629
+
630
+ # get eff values from standards
631
+ standard_minimum_cop = std.coil_cooling_dx_single_speed_standard_minimum_cop(component)
632
+
633
+ # check actual against target
634
+ if standard_minimum_cop.nil?
635
+ check_elems << OpenStudio::Attribute.new('flag', "Can't find target COP for #{component.name}.")
636
+ elsif rated_COP < standard_minimum_cop * (1.0 - min_pass_pct)
637
+ check_elems << OpenStudio::Attribute.new('flag', "The COP of #{rated_COP.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the expected value of #{standard_minimum_cop.round(2)} for #{target_standard}.")
638
+ elsif rated_COP > standard_minimum_cop * (1.0 + max_pass_pct)
639
+ check_elems << OpenStudio::Attribute.new('flag', "The COP of #{rated_COP.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the expected value of #{standard_minimum_cop.round(2)} for #{target_standard}.")
640
+ end
641
+ end
642
+
643
+ # check CoilCoolingDXTwoSpeed objects (will also have curve check in different script)
644
+ @model.getCoilCoolingDXTwoSpeeds.sort.each do |component|
645
+ # eff values from model
646
+ rated_high_speed_COP = component.ratedHighSpeedCOP.get
647
+ rated_low_speed_COP = component.ratedLowSpeedCOP.get
648
+
649
+ # get eff values from standards
650
+ standard_minimum_cop = std.coil_cooling_dx_two_speed_standard_minimum_cop(component)
651
+
652
+ # check actual against target
653
+ if standard_minimum_cop.nil?
654
+ check_elems << OpenStudio::Attribute.new('flag', "Can't find target COP for #{component.name}.")
655
+ elsif rated_high_speed_COP < standard_minimum_cop * (1.0 - min_pass_pct)
656
+ check_elems << OpenStudio::Attribute.new('flag', "The high speed COP of #{rated_high_speed_COP.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the expected value of #{standard_minimum_cop.round(2)} for #{target_standard}.")
657
+ elsif rated_high_speed_COP > standard_minimum_cop * (1.0 + max_pass_pct)
658
+ check_elems << OpenStudio::Attribute.new('flag', "The high speed COP of #{rated_high_speed_COP.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the expected value of #{standard_minimum_cop.round(2)} for #{target_standard}.")
659
+ end
660
+ if standard_minimum_cop.nil?
661
+ check_elems << OpenStudio::Attribute.new('flag', "Can't find target COP for #{component.name}.")
662
+ elsif rated_low_speed_COP < standard_minimum_cop * (1.0 - min_pass_pct)
663
+ check_elems << OpenStudio::Attribute.new('flag', "The low speed COP of #{rated_low_speed_COP.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the expected value of #{standard_minimum_cop.round(2)} for #{target_standard}.")
664
+ elsif rated_low_speed_COP > standard_minimum_cop * (1.0 + max_pass_pct)
665
+ check_elems << OpenStudio::Attribute.new('flag', "The low speed COP of #{rated_low_speed_COP.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the expected value of #{standard_minimum_cop.round(2)} for #{target_standard}.")
666
+ end
667
+ end
668
+
669
+ # check CoilHeatingDXSingleSpeed objects
670
+ # @todo need to test this once json file populated for this data
671
+ @model.getCoilHeatingDXSingleSpeeds.sort.each do |component|
672
+ # eff values from model
673
+ rated_COP = component.ratedCOP
674
+
675
+ # get eff values from standards
676
+ standard_minimum_cop = std.coil_heating_dx_single_speed_standard_minimum_cop(component)
677
+
678
+ # check actual against target
679
+ if standard_minimum_cop.nil?
680
+ check_elems << OpenStudio::Attribute.new('flag', "Can't find target COP for #{component.name}.")
681
+ elsif rated_COP < standard_minimum_cop * (1.0 - min_pass_pct)
682
+ check_elems << OpenStudio::Attribute.new('flag', "The COP of #{rated_COP.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the expected value of #{standard_minimum_cop.round(2)} for #{target_standard}.")
683
+ elsif rated_COP > standard_minimum_cop * (1.0 + max_pass_pct)
684
+ check_elems << OpenStudio::Attribute.new('flag', "The COP of #{rated_COP.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the expected value of #{standard_minimum_cop.round(2)}. for #{target_standard}")
685
+ end
686
+ end
687
+
688
+ # check BoilerHotWater
689
+ @model.getBoilerHotWaters.sort.each do |component|
690
+ # eff values from model
691
+ nominal_thermal_efficiency = component.nominalThermalEfficiency
692
+
693
+ # get eff values from standards
694
+ standard_minimum_thermal_efficiency = std.boiler_hot_water_standard_minimum_thermal_efficiency(component)
695
+
696
+ # check actual against target
697
+ if standard_minimum_thermal_efficiency.nil?
698
+ check_elems << OpenStudio::Attribute.new('flag', "Can't find target thermal efficiency for #{component.name}.")
699
+ elsif nominal_thermal_efficiency < standard_minimum_thermal_efficiency * (1.0 - min_pass_pct)
700
+ check_elems << OpenStudio::Attribute.new('flag', "Nominal thermal efficiency of #{nominal_thermal_efficiency.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the expected value of #{standard_minimum_thermal_efficiency.round(2)} for #{target_standard}.")
701
+ elsif nominal_thermal_efficiency > standard_minimum_thermal_efficiency * (1.0 + max_pass_pct)
702
+ check_elems << OpenStudio::Attribute.new('flag', "Nominal thermal efficiency of #{nominal_thermal_efficiency.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the expected value of #{standard_minimum_thermal_efficiency.round(2)} for #{target_standard}.")
703
+ end
704
+ end
705
+
706
+ # check FanConstantVolume
707
+ @model.getFanConstantVolumes.sort.each do |component|
708
+ # eff values from model
709
+ motor_eff = component.motorEfficiency
710
+
711
+ # get eff values from standards
712
+ motor_bhp = std.fan_brake_horsepower(component)
713
+ standard_minimum_motor_efficiency_and_size = std.fan_standard_minimum_motor_efficiency_and_size(component, motor_bhp)[0]
714
+
715
+ # check actual against target
716
+ if standard_minimum_motor_efficiency_and_size.nil?
717
+ check_elems << OpenStudio::Attribute.new('flag', "Can't find target motor efficiency for #{component.name}.")
718
+ elsif motor_eff < standard_minimum_motor_efficiency_and_size * (1.0 - min_pass_pct)
719
+ check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{target_standard}.")
720
+ elsif motor_eff > standard_minimum_motor_efficiency_and_size * (1.0 + max_pass_pct)
721
+ check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{target_standard}.")
722
+ end
723
+ end
724
+
725
+ # check FanVariableVolume
726
+ @model.getFanVariableVolumes.sort.each do |component|
727
+ # eff values from model
728
+ motor_eff = component.motorEfficiency
729
+
730
+ # get eff values from standards
731
+ motor_bhp = std.fan_brake_horsepower(component)
732
+ standard_minimum_motor_efficiency_and_size = std.fan_standard_minimum_motor_efficiency_and_size(component, motor_bhp)[0]
733
+
734
+ # check actual against target
735
+ if standard_minimum_motor_efficiency_and_size.nil?
736
+ check_elems << OpenStudio::Attribute.new('flag', "Can't find target motor efficiency for #{component.name}.")
737
+ elsif motor_eff < standard_minimum_motor_efficiency_and_size * (1.0 - min_pass_pct)
738
+ check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{target_standard}.")
739
+ elsif motor_eff > standard_minimum_motor_efficiency_and_size * (1.0 + max_pass_pct)
740
+ check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{target_standard}.")
741
+ end
742
+ end
743
+
744
+ # check PumpConstantSpeed
745
+ @model.getPumpConstantSpeeds.sort.each do |component|
746
+ # eff values from model
747
+ motor_eff = component.motorEfficiency
748
+
749
+ # get eff values from standards
750
+ motor_bhp = std.pump_brake_horsepower(component)
751
+ next if motor_bhp == 0.0
752
+
753
+ standard_minimum_motor_efficiency_and_size = std.pump_standard_minimum_motor_efficiency_and_size(component, motor_bhp)[0]
754
+
755
+ # check actual against target
756
+ if standard_minimum_motor_efficiency_and_size.nil?
757
+ check_elems << OpenStudio::Attribute.new('flag', "Can't find target motor efficiency for #{component.name}.")
758
+ elsif motor_eff < standard_minimum_motor_efficiency_and_size * (1.0 - min_pass_pct)
759
+ check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{target_standard}.")
760
+ elsif motor_eff > standard_minimum_motor_efficiency_and_size * (1.0 + max_pass_pct)
761
+ check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{target_standard}.")
762
+ end
763
+ end
764
+
765
+ # check PumpVariableSpeed
766
+ @model.getPumpVariableSpeeds.sort.each do |component|
767
+ # eff values from model
768
+ motor_eff = component.motorEfficiency
769
+
770
+ # get eff values from standards
771
+ motor_bhp = std.pump_brake_horsepower(component)
772
+ next if motor_bhp == 0.0
773
+
774
+ standard_minimum_motor_efficiency_and_size = std.pump_standard_minimum_motor_efficiency_and_size(component, motor_bhp)[0]
775
+
776
+ # check actual against target
777
+ if standard_minimum_motor_efficiency_and_size.nil?
778
+ check_elems << OpenStudio::Attribute.new('flag', "Can't find target motor efficiency for #{component.name}.")
779
+ elsif motor_eff < standard_minimum_motor_efficiency_and_size * (1.0 - min_pass_pct)
780
+ check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{target_standard}.")
781
+ elsif motor_eff > standard_minimum_motor_efficiency_and_size * (1.0 + max_pass_pct)
782
+ check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{target_standard}.")
783
+ end
784
+ end
785
+ rescue StandardError => e
786
+ # brief description of ruby error
787
+ check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
788
+
789
+ # backtrace of ruby error for diagnostic use
790
+ if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
791
+ end
792
+
793
+ # add check_elms to new attribute
794
+ check_elem = OpenStudio::Attribute.new('check', check_elems)
795
+
796
+ return check_elem
797
+ end
798
+
799
+ # Check the mechanical system part load efficiencies against a standard
800
+ #
801
+ # @param category [String] category to bin this check into
802
+ # @param target_standard [String] standard template, e.g. '90.1-2013'
803
+ # @param min_pass_pct [Double] threshold for throwing an error for percent difference
804
+ # @param max_pass_pct [Double] threshold for throwing an error for percent difference
805
+ # @param name_only [Boolean] If true, only return the name of this check
806
+ # @return [OpenStudio::Attribute] OpenStudio Attribute object containing check results
807
+ def self.check_hvac_part_load_efficiency(category, target_standard, min_pass_pct: 0.3, max_pass_pct: 0.3, name_only: false)
808
+ component_type_array = ['ChillerElectricEIR', 'CoilCoolingDXSingleSpeed', 'CoilCoolingDXTwoSpeed', 'CoilHeatingDXSingleSpeed']
809
+
810
+ # summary of the check
811
+ check_elems = OpenStudio::AttributeVector.new
812
+ check_elems << OpenStudio::Attribute.new('name', 'Mechanical System Part Load Efficiency')
813
+ check_elems << OpenStudio::Attribute.new('category', category)
814
+ check_elems << OpenStudio::Attribute.new('description', "Check 40% and 80% part load efficency against #{target_standard} for the following compenent types: #{component_type_array.join(', ')}. Checking EIR Function of Part Load Ratio curve for chiller and EIR Function of Flow Fraction for DX coils.")
815
+
816
+ # stop here if only name is requested this is used to populate display name for arguments
817
+ if name_only == true
818
+ results = []
819
+ check_elems.each do |elem|
820
+ results << elem.valueAsString
821
+ end
822
+ return results
823
+ end
824
+
825
+ std = Standard.build(target_standard)
826
+
827
+ # @todo add in check for VAV fan
828
+ begin
829
+ # @todo dynamically generate a list of possible options from the standards json
830
+ chiller_air_cooled_condenser_types = ['WithCondenser', 'WithoutCondenser']
831
+ chiller_water_cooled_compressor_types = ['Reciprocating', 'Scroll', 'Rotary Screw', 'Centrifugal']
832
+ absorption_types = ['Single Effect', 'Double Effect Indirect Fired', 'Double Effect Direct Fired']
833
+
834
+ # check getChillerElectricEIRs objects (will also have curve check in different script)
835
+ @model.getChillerElectricEIRs.sort.each do |component|
836
+ # get curve and evaluate
837
+ electric_input_to_cooling_output_ratio_function_of_PLR = component.electricInputToCoolingOutputRatioFunctionOfPLR
838
+ curve_40_pct = electric_input_to_cooling_output_ratio_function_of_PLR.evaluate(0.4)
839
+ curve_80_pct = electric_input_to_cooling_output_ratio_function_of_PLR.evaluate(0.8)
840
+
841
+ # find ac properties
842
+ search_criteria = std.chiller_electric_eir_find_search_criteria(component)
843
+
844
+ # extend search_criteria for absorption_type
845
+ absorption_types.each do |absorption_type|
846
+ if component.name.to_s.include?(absorption_type)
847
+ search_criteria['absorption_type'] = absorption_type
848
+ next
849
+ end
850
+ end
851
+ # extend search_criteria for condenser type or compressor type
852
+ if search_criteria['cooling_type'] == 'AirCooled'
853
+ chiller_air_cooled_condenser_types.each do |condenser_type|
854
+ if component.name.to_s.include?(condenser_type)
855
+ search_criteria['condenser_type'] = condenser_type
856
+ next
857
+ end
858
+ end
859
+ # if no match and also no absorption_type then issue warning
860
+ if !search_criteria.key?('condenser_type') || search_criteria['condenser_type'].nil?
861
+ if !search_criteria.key?('absorption_type') || search_criteria['absorption_type'].nil?
862
+ check_elems << OpenStudio::Attribute.new('flag', "Can't find unique search criteria for #{component.name}. #{search_criteria}")
863
+ next # don't go past here
864
+ end
865
+ end
866
+ elsif search_criteria['cooling_type'] == 'WaterCooled'
867
+ chiller_air_cooled_condenser_types.each do |compressor_type|
868
+ if component.name.to_s.include?(compressor_type)
869
+ search_criteria['compressor_type'] = compressor_type
870
+ next
871
+ end
872
+ end
873
+ # if no match and also no absorption_type then issue warning
874
+ if !search_criteria.key?('compressor_type') || search_criteria['compressor_type'].nil?
875
+ if !search_criteria.key?('absorption_type') || search_criteria['absorption_type'].nil?
876
+ check_elems << OpenStudio::Attribute.new('flag', "Can't find unique search criteria for #{component.name}. #{search_criteria}")
877
+ next # don't go past here
878
+ end
879
+ end
880
+ end
881
+
882
+ # lookup chiller
883
+ capacity_w = std.chiller_electric_eir_find_capacity(component)
884
+ capacity_tons = OpenStudio.convert(capacity_w, 'W', 'ton').get
885
+ chlr_props = std.model_find_object(std.standards_data['chillers'], search_criteria, capacity_tons, Date.today)
886
+ if chlr_props.nil?
887
+ check_elems << OpenStudio::Attribute.new('flag', "Didn't find chiller for #{component.name}. #{search_criteria}")
888
+ next # don't go past here in loop if can't find curve
889
+ end
890
+
891
+ # temp model to hold temp curve
892
+ model_temp = OpenStudio::Model::Model.new
893
+
894
+ # create temp curve
895
+ target_curve_name = chlr_props['eirfplr']
896
+ if target_curve_name.nil?
897
+ check_elems << OpenStudio::Attribute.new('flag', "Can't find target eirfplr curve for #{component.name}")
898
+ next # don't go past here in loop if can't find curve
899
+ end
900
+ temp_curve = std.model_add_curve(model_temp, target_curve_name)
901
+
902
+ target_curve_40_pct = temp_curve.evaluate(0.4)
903
+ target_curve_80_pct = temp_curve.evaluate(0.8)
904
+
905
+ # check curve at two points
906
+ if curve_40_pct < target_curve_40_pct * (1.0 - min_pass_pct)
907
+ check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the typical value of #{target_curve_40_pct.round(2)} for #{target_standard}.")
908
+ elsif curve_40_pct > target_curve_40_pct * (1.0 + max_pass_pct)
909
+ check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the typical value of #{target_curve_40_pct.round(2)} for #{target_standard}.")
910
+ end
911
+ if curve_80_pct < target_curve_80_pct * (1.0 - min_pass_pct)
912
+ check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the typical value of #{target_curve_80_pct.round(2)} for #{target_standard}.")
913
+ elsif curve_80_pct > target_curve_80_pct * (1.0 + max_pass_pct)
914
+ check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the typical value of #{target_curve_80_pct.round(2)} for #{target_standard}.")
915
+ end
916
+ end
917
+
918
+ # check getCoilCoolingDXSingleSpeeds objects (will also have curve check in different script)
919
+ @model.getCoilCoolingDXSingleSpeeds.sort.each do |component|
920
+ # get curve and evaluate
921
+ eir_function_of_flow_fraction_curve = component.energyInputRatioFunctionOfFlowFractionCurve
922
+ curve_40_pct = eir_function_of_flow_fraction_curve.evaluate(0.4)
923
+ curve_80_pct = eir_function_of_flow_fraction_curve.evaluate(0.8)
924
+
925
+ # find ac properties
926
+ search_criteria = std.coil_dx_find_search_criteria(component)
927
+ capacity_w = std.coil_cooling_dx_single_speed_find_capacity(component)
928
+ capacity_btu_per_hr = OpenStudio.convert(capacity_w, 'W', 'Btu/hr').get
929
+ if std.coil_dx_heat_pump?(component)
930
+ ac_props = std.model_find_object(std.standards_data['heat_pumps'], search_criteria, capacity_btu_per_hr, Date.today)
931
+ else
932
+ ac_props = std.model_find_object(std.standards_data['unitary_acs'], search_criteria, capacity_btu_per_hr, Date.today)
933
+ end
934
+
935
+ # temp model to hold temp curve
936
+ model_temp = OpenStudio::Model::Model.new
937
+
938
+ # create temp curve
939
+ target_curve_name = ac_props['cool_eir_fflow']
940
+ if target_curve_name.nil?
941
+ check_elems << OpenStudio::Attribute.new('flag', "Can't find target cool_eir_fflow curve for #{component.name}")
942
+ next # don't go past here in loop if can't find curve
943
+ end
944
+ temp_curve = std.model_add_curve(model_temp, target_curve_name)
945
+ target_curve_40_pct = temp_curve.evaluate(0.4)
946
+ target_curve_80_pct = temp_curve.evaluate(0.8)
947
+
948
+ # check curve at two points
949
+ if curve_40_pct < target_curve_40_pct * (1.0 - min_pass_pct)
950
+ check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the typical value of #{target_curve_40_pct.round(2)} for #{target_standard}.")
951
+ elsif curve_40_pct > target_curve_40_pct * (1.0 + max_pass_pct)
952
+ check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the typical value of #{target_curve_40_pct.round(2)} for #{target_standard}.")
953
+ end
954
+ if curve_80_pct < target_curve_80_pct * (1.0 - min_pass_pct)
955
+ check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the typical value of #{target_curve_80_pct.round(2)} for #{target_standard}.")
956
+ elsif curve_80_pct > target_curve_80_pct * (1.0 + max_pass_pct)
957
+ check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the typical value of #{target_curve_80_pct.round(2)} for #{target_standard}.")
958
+ end
959
+ end
960
+
961
+ # check CoilCoolingDXTwoSpeed objects (will also have curve check in different script)
962
+ @model.getCoilCoolingDXTwoSpeeds.sort.each do |component|
963
+ # get curve and evaluate
964
+ eir_function_of_flow_fraction_curve = component.energyInputRatioFunctionOfFlowFractionCurve
965
+ curve_40_pct = eir_function_of_flow_fraction_curve.evaluate(0.4)
966
+ curve_80_pct = eir_function_of_flow_fraction_curve.evaluate(0.8)
967
+
968
+ # find ac properties
969
+ search_criteria = std.coil_dx_find_search_criteria(component)
970
+ capacity_w = std.coil_cooling_dx_two_speed_find_capacity(component)
971
+ capacity_btu_per_hr = OpenStudio.convert(capacity_w, 'W', 'Btu/hr').get
972
+ ac_props = std.model_find_object(std.standards_data['unitary_acs'], search_criteria, capacity_btu_per_hr, Date.today)
973
+
974
+ # temp model to hold temp curve
975
+ model_temp = OpenStudio::Model::Model.new
976
+
977
+ # create temp curve
978
+ target_curve_name = ac_props['cool_eir_fflow']
979
+ if target_curve_name.nil?
980
+ check_elems << OpenStudio::Attribute.new('flag', "Can't find target cool_eir_flow curve for #{component.name}")
981
+ next # don't go past here in loop if can't find curve
982
+ end
983
+ temp_curve = std.model_add_curve(model_temp, target_curve_name)
984
+ target_curve_40_pct = temp_curve.evaluate(0.4)
985
+ target_curve_80_pct = temp_curve.evaluate(0.8)
986
+
987
+ # check curve at two points
988
+ if curve_40_pct < target_curve_40_pct * (1.0 - min_pass_pct)
989
+ check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the typical value of #{target_curve_40_pct.round(2)} for #{target_standard}.")
990
+ elsif curve_40_pct > target_curve_40_pct * (1.0 + max_pass_pct)
991
+ check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the typical value of #{target_curve_40_pct.round(2)} for #{target_standard}.")
992
+ end
993
+ if curve_80_pct < target_curve_80_pct * (1.0 - min_pass_pct)
994
+ check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the typical value of #{target_curve_80_pct.round(2)} for #{target_standard}.")
995
+ elsif curve_80_pct > target_curve_80_pct * (1.0 + max_pass_pct)
996
+ check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the typical value of #{target_curve_80_pct.round(2)} for #{target_standard}.")
997
+ end
998
+ end
999
+
1000
+ # check CoilCoolingDXTwoSpeed objects (will also have curve check in different script)
1001
+ @model.getCoilHeatingDXSingleSpeeds.sort.each do |component|
1002
+ # get curve and evaluate
1003
+ eir_function_of_flow_fraction_curve = component.energyInputRatioFunctionofFlowFractionCurve # why lowercase of here but not in CoilCoolingDX objects
1004
+ curve_40_pct = eir_function_of_flow_fraction_curve.evaluate(0.4)
1005
+ curve_80_pct = eir_function_of_flow_fraction_curve.evaluate(0.8)
1006
+
1007
+ # find ac properties
1008
+ search_criteria = std.coil_dx_find_search_criteria(component)
1009
+ capacity_w = std.coil_heating_dx_single_speed_find_capacity(component)
1010
+ capacity_btu_per_hr = OpenStudio.convert(capacity_w, 'W', 'Btu/hr').get
1011
+ ac_props = std.model_find_object(std.standards_data['heat_pumps_heating'], search_criteria, capacity_btu_per_hr, Date.today)
1012
+ if ac_props.nil?
1013
+ target_curve_name = nil
1014
+ else
1015
+ target_curve_name = ac_props['heat_eir_fflow']
1016
+ end
1017
+
1018
+ # temp model to hold temp curve
1019
+ model_temp = OpenStudio::Model::Model.new
1020
+
1021
+ # create temp curve
1022
+ if target_curve_name.nil?
1023
+ check_elems << OpenStudio::Attribute.new('flag', "Can't find target curve for #{component.name}")
1024
+ next # don't go past here in loop if can't find curve
1025
+ end
1026
+ temp_curve = std.model_add_curve(model_temp, target_curve_name)
1027
+
1028
+ # Ensure that the curve was found in standards before attempting to evaluate
1029
+ if temp_curve.nil?
1030
+ check_elems << OpenStudio::Attribute.new('flag', "Can't find coefficients of curve called #{target_curve_name} for #{component.name}, cannot check part-load performance.")
1031
+ next
1032
+ end
1033
+
1034
+ target_curve_40_pct = temp_curve.evaluate(0.4)
1035
+ target_curve_80_pct = temp_curve.evaluate(0.8)
1036
+
1037
+ # check curve at two points
1038
+ if curve_40_pct < target_curve_40_pct * (1.0 - min_pass_pct)
1039
+ check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the typical value of #{target_curve_40_pct.round(2)} for #{target_standard}.")
1040
+ elsif curve_40_pct > target_curve_40_pct * (1.0 + max_pass_pct)
1041
+ check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the typical value of #{target_curve_40_pct.round(2)} for #{target_standard}.")
1042
+ end
1043
+ if curve_80_pct < target_curve_80_pct * (1.0 - min_pass_pct)
1044
+ check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the typical value of #{target_curve_80_pct.round(2)} for #{target_standard}.")
1045
+ elsif curve_80_pct > target_curve_80_pct * (1.0 + max_pass_pct)
1046
+ check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the typical value of #{target_curve_80_pct.round(2)} for #{target_standard}.")
1047
+ end
1048
+ end
1049
+
1050
+ # check
1051
+ @model.getFanVariableVolumes.sort.each do |component|
1052
+ # skip if not on multi-zone system.
1053
+ if component.airLoopHVAC.is_initialized
1054
+ airloop = component.airLoopHVAC.get
1055
+
1056
+ next unless airloop.thermalZones.size > 1.0
1057
+ end
1058
+
1059
+ # skip of brake horsepower is 0
1060
+ next if std.fan_brake_horsepower(component) == 0.0
1061
+
1062
+ # temp model for use by temp model and target curve
1063
+ model_temp = OpenStudio::Model::Model.new
1064
+
1065
+ # get coeficents for fan
1066
+ model_fan_coefs = []
1067
+ model_fan_coefs << component.fanPowerCoefficient1.get
1068
+ model_fan_coefs << component.fanPowerCoefficient2.get
1069
+ model_fan_coefs << component.fanPowerCoefficient3.get
1070
+ model_fan_coefs << component.fanPowerCoefficient4.get
1071
+ model_fan_coefs << component.fanPowerCoefficient5.get
1072
+
1073
+ # make model curve
1074
+ model_curve = OpenStudio::Model::CurveQuartic.new(model_temp)
1075
+ model_curve.setCoefficient1Constant(model_fan_coefs[0])
1076
+ model_curve.setCoefficient2x(model_fan_coefs[1])
1077
+ model_curve.setCoefficient3xPOW2(model_fan_coefs[2])
1078
+ model_curve.setCoefficient4xPOW3(model_fan_coefs[3])
1079
+ model_curve.setCoefficient5xPOW4(model_fan_coefs[4])
1080
+ curve_40_pct = model_curve.evaluate(0.4)
1081
+ curve_80_pct = model_curve.evaluate(0.8)
1082
+
1083
+ # get target coefs
1084
+ target_fan = OpenStudio::Model::FanVariableVolume.new(model_temp)
1085
+ std.fan_variable_volume_set_control_type(target_fan, 'Multi Zone VAV with VSD and Static Pressure Reset')
1086
+
1087
+ # get coeficents for fan
1088
+ target_fan_coefs = []
1089
+ target_fan_coefs << target_fan.fanPowerCoefficient1.get
1090
+ target_fan_coefs << target_fan.fanPowerCoefficient2.get
1091
+ target_fan_coefs << target_fan.fanPowerCoefficient3.get
1092
+ target_fan_coefs << target_fan.fanPowerCoefficient4.get
1093
+ target_fan_coefs << target_fan.fanPowerCoefficient5.get
1094
+
1095
+ # make model curve
1096
+ target_curve = OpenStudio::Model::CurveQuartic.new(model_temp)
1097
+ target_curve.setCoefficient1Constant(target_fan_coefs[0])
1098
+ target_curve.setCoefficient2x(target_fan_coefs[1])
1099
+ target_curve.setCoefficient3xPOW2(target_fan_coefs[2])
1100
+ target_curve.setCoefficient4xPOW3(target_fan_coefs[3])
1101
+ target_curve.setCoefficient5xPOW4(target_fan_coefs[4])
1102
+ target_curve_40_pct = target_curve.evaluate(0.4)
1103
+ target_curve_80_pct = target_curve.evaluate(0.8)
1104
+
1105
+ # check curve at two points
1106
+ if curve_40_pct < target_curve_40_pct * (1.0 - min_pass_pct)
1107
+ check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the typical value of #{target_curve_40_pct.round(2)} for #{target_standard}.")
1108
+ elsif curve_40_pct > target_curve_40_pct * (1.0 + max_pass_pct)
1109
+ check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the typical value of #{target_curve_40_pct.round(2)} for #{target_standard}.")
1110
+ end
1111
+ if curve_80_pct < target_curve_80_pct * (1.0 - min_pass_pct)
1112
+ check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the typical value of #{target_curve_80_pct.round(2)} for #{target_standard}.")
1113
+ elsif curve_80_pct > target_curve_80_pct * (1.0 + max_pass_pct)
1114
+ check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the typical value of #{target_curve_80_pct.round(2)} for #{target_standard}.")
1115
+ end
1116
+ end
1117
+ rescue StandardError => e
1118
+ # brief description of ruby error
1119
+ check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
1120
+
1121
+ # backtrace of ruby error for diagnostic use
1122
+ if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
1123
+ end
1124
+
1125
+ # add check_elms to new attribute
1126
+ check_elem = OpenStudio::Attribute.new('check', check_elems)
1127
+
1128
+ return check_elem
1129
+ end
1130
+
1131
+ # Check primary plant loop heating and cooling equipment capacity against coil loads to find equipment that is significantly oversized or undersized.
1132
+ #
1133
+ # @param category [String] category to bin this check into
1134
+ # @param target_standard [String] standard template, e.g. '90.1-2013'
1135
+ # @param max_pct_delta [Double] threshold for throwing an error for percent difference
1136
+ # @param name_only [Boolean] If true, only return the name of this check
1137
+ # @return [OpenStudio::Attribute] OpenStudio Attribute object containing check results
1138
+ def self.check_plant_loop_capacity(category, target_standard, max_pct_delta: 0.3, name_only: false)
1139
+ # summary of the check
1140
+ check_elems = OpenStudio::AttributeVector.new
1141
+ check_elems << OpenStudio::Attribute.new('name', 'Plant Capacity')
1142
+ check_elems << OpenStudio::Attribute.new('category', category)
1143
+ check_elems << OpenStudio::Attribute.new('description', 'Check that plant equipment capacity matches loads.')
1144
+
1145
+ # stop here if only name is requested this is used to populate display name for arguments
1146
+ if name_only == true
1147
+ results = []
1148
+ check_elems.each do |elem|
1149
+ results << elem.valueAsString
1150
+ end
1151
+ return results
1152
+ end
1153
+
1154
+ std = Standard.build(target_standard)
1155
+
1156
+ begin
1157
+ # Check the heating and cooling capacity of the plant loops against their coil loads
1158
+ @model.getPlantLoops.sort.each do |plant_loop|
1159
+ # Heating capacity
1160
+ htg_cap_w = std.plant_loop_total_heating_capacity(plant_loop)
1161
+
1162
+ # Cooling capacity
1163
+ clg_cap_w = std.plant_loop_total_cooling_capacity(plant_loop)
1164
+
1165
+ # Sum the load for each coil on the loop
1166
+ htg_load_w = 0.0
1167
+ clg_load_w = 0.0
1168
+ plant_loop.demandComponents.each do |dc|
1169
+ obj_type = dc.iddObjectType.valueName.to_s
1170
+ case obj_type
1171
+ when 'OS_Coil_Heating_Water'
1172
+ coil = dc.to_CoilHeatingWater.get
1173
+ if coil.ratedCapacity.is_initialized
1174
+ htg_load_w += coil.ratedCapacity.get
1175
+ elsif coil.autosizedRatedCapacity.is_initialized
1176
+ htg_load_w += coil.autosizedRatedCapacity.get
1177
+ end
1178
+ when 'OS_Coil_Cooling_Water'
1179
+ coil = dc.to_CoilCoolingWater.get
1180
+ if coil.autosizedDesignCoilLoad.is_initialized
1181
+ clg_load_w += coil.autosizedDesignCoilLoad.get
1182
+ end
1183
+ end
1184
+ end
1185
+
1186
+ # Don't check loops with no loads. These are probably SWH or non-typical loops that can't be checked by simple methods.
1187
+ # Heating
1188
+ if htg_load_w > 0
1189
+ htg_cap_kbtu_per_hr = OpenStudio.convert(htg_cap_w, 'W', 'kBtu/hr').get.round(1)
1190
+ htg_load_kbtu_per_hr = OpenStudio.convert(htg_load_w, 'W', 'kBtu/hr').get.round(1)
1191
+ if ((htg_cap_w - htg_load_w) / htg_cap_w).abs > max_pct_delta
1192
+ check_elems << OpenStudio::Attribute.new('flag', "For #{plant_loop.name}, the total heating capacity of #{htg_cap_kbtu_per_hr} kBtu/hr is more than #{(max_pct_delta * 100.0).round(2)}% different from the combined coil load of #{htg_load_kbtu_per_hr} kBtu/hr. This could indicate significantly oversized or undersized equipment.")
1193
+ end
1194
+ end
1195
+
1196
+ # Cooling
1197
+ if clg_load_w > 0
1198
+ clg_cap_tons = OpenStudio.convert(clg_cap_w, 'W', 'ton').get.round(1)
1199
+ clg_load_tons = OpenStudio.convert(clg_load_w, 'W', 'ton').get.round(1)
1200
+ if ((clg_cap_w - clg_load_w) / clg_cap_w).abs > max_pct_delta
1201
+ check_elems << OpenStudio::Attribute.new('flag', "For #{plant_loop.name}, the total cooling capacity of #{clg_load_tons} tons is more than #{(max_pct_delta * 100.0).round(2)}% different from the combined coil load of #{clg_load_tons} tons. This could indicate significantly oversized or undersized equipment.")
1202
+ end
1203
+ end
1204
+ end
1205
+ rescue StandardError => e
1206
+ # brief description of ruby error
1207
+ check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
1208
+
1209
+ # backtrace of ruby error for diagnostic use
1210
+ if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
1211
+ end
1212
+
1213
+ # add check_elms to new attribute
1214
+ check_elem = OpenStudio::Attribute.new('check', check_elems)
1215
+
1216
+ return check_elem
1217
+ end
1218
+
1219
+ # Check the plant loop operational vs. sizing temperatures and make sure everything is coordinated.
1220
+ # This identifies problems caused by sizing to one set of conditions and operating at a different set.
1221
+ #
1222
+ # @param category [String] category to bin this check into
1223
+ # @param max_sizing_temp_delta [Double] threshold for throwing an error for design sizing temperatures
1224
+ # @param max_operating_temp_delta [Double] threshold for throwing an error on operating temperatures
1225
+ # @param name_only [Boolean] If true, only return the name of this check
1226
+ # @return [OpenStudio::Attribute] OpenStudio Attribute object containing check results
1227
+ def self.check_plant_loop_temperatures(category, max_sizing_temp_delta: 2.0, max_operating_temp_delta: 5.0, name_only: false)
1228
+ # summary of the check
1229
+ check_elems = OpenStudio::AttributeVector.new
1230
+ check_elems << OpenStudio::Attribute.new('name', 'Plant Loop Temperatures')
1231
+ check_elems << OpenStudio::Attribute.new('category', category)
1232
+ check_elems << OpenStudio::Attribute.new('description', 'Check that plant loop sizing and operation temperatures are coordinated.')
1233
+
1234
+ # stop here if only name is requested this is used to populate display name for arguments
1235
+ if name_only == true
1236
+ results = []
1237
+ check_elems.each do |elem|
1238
+ results << elem.valueAsString
1239
+ end
1240
+ return results
1241
+ end
1242
+
1243
+ begin
1244
+ # get the weather file run period (as opposed to design day run period)
1245
+ ann_env_pd = nil
1246
+ @sql.availableEnvPeriods.each do |env_pd|
1247
+ env_type = @sql.environmentType(env_pd)
1248
+ if env_type.is_initialized
1249
+ if env_type.get == OpenStudio::EnvironmentType.new('WeatherRunPeriod')
1250
+ ann_env_pd = env_pd
1251
+ break
1252
+ end
1253
+ end
1254
+ end
1255
+
1256
+ # only try to get the annual timeseries if an annual simulation was run
1257
+ if ann_env_pd.nil?
1258
+ check_elems << OpenStudio::Attribute.new('flag', 'Cannot find the annual simulation run period, cannot check equipment part load ratios.')
1259
+ return check_elems
1260
+ end
1261
+
1262
+ # Check each plant loop in the model
1263
+ @model.getPlantLoops.sort.each do |plant_loop|
1264
+ supply_outlet_node_name = plant_loop.supplyOutletNode.name.to_s
1265
+ design_supply_temperature = plant_loop.sizingPlant.designLoopExitTemperature
1266
+ design_supply_temperature = OpenStudio.convert(design_supply_temperature, 'C', 'F').get
1267
+ design_temperature_difference = plant_loop.sizingPlant.loopDesignTemperatureDifference
1268
+ design_temperature_difference = OpenStudio.convert(design_temperature_difference, 'K', 'R').get
1269
+
1270
+ # get min and max temperatures from setpoint manager
1271
+ spm_name = ''
1272
+ spm_type = '<unspecified>'
1273
+ spm_min_temp_f = nil
1274
+ spm_max_temp_f = nil
1275
+ spms = plant_loop.supplyOutletNode.setpointManagers
1276
+ unless spms.empty?
1277
+ spm = spms[0] # assume first setpoint manager is only setpoint manager
1278
+ spm_name = spm.name
1279
+ spm_type = spm.iddObjectType.valueName.to_s
1280
+ spm_temps_f = OpenstudioStandards::HVAC.setpoint_manager_min_max_temperature(spm)
1281
+ spm_min_temp_f = spm_temps_f['min_temp']
1282
+ spm_max_temp_f = spm_temps_f['max_temp']
1283
+ end
1284
+
1285
+ # check setpoint manager temperatures against design temperatures
1286
+ case plant_loop.sizingPlant.loopType
1287
+ when 'Heating'
1288
+ if spm_max_temp_f
1289
+ if (spm_max_temp_f - design_supply_temperature).abs > max_sizing_temp_delta
1290
+ check_elems << OpenStudio::Attribute.new('flag', "Minor Error: #{plant_loop.name} sizing uses a #{design_supply_temperature.round(1)}F supply water temperature, but the setpoint manager operates up to #{spm_max_temp_f.round(1)}F.")
1291
+ end
1292
+ end
1293
+ when 'Cooling'
1294
+ if spm_min_temp_f
1295
+ if (spm_min_temp_f - design_supply_temperature).abs > max_sizing_temp_delta
1296
+ check_elems << OpenStudio::Attribute.new('flag', "Minor Error: #{plant_loop.name} sizing uses a #{design_supply_temperature.round(1)}F supply water temperature, but the setpoint manager operates down to #{spm_min_temp_f.round(1)}F.")
1297
+ end
1298
+ end
1299
+ end
1300
+
1301
+ # get supply water temperatures for supply outlet node
1302
+ supply_temp_timeseries = @sql.timeSeries(ann_env_pd, 'Timestep', 'System Node Temperature', supply_outlet_node_name)
1303
+ if supply_temp_timeseries.empty?
1304
+ check[:items] << { type: 'warning', msg: "No supply node temperature timeseries found for '#{plant_loop.name}'" }
1305
+ next
1306
+ else
1307
+ # convert to ruby array
1308
+ temperatures = []
1309
+ supply_temp_vector = supply_temp_timeseries.get.values
1310
+ for i in (0..supply_temp_vector.size - 1)
1311
+ temperatures << supply_temp_vector[i]
1312
+ end
1313
+ end
1314
+
1315
+ # get supply water flow rates for supply outlet node
1316
+ supply_flow_timeseries = @sql.timeSeries(ann_env_pd, 'Timestep', 'System Node Standard Density Volume Flow Rate', supply_outlet_node_name)
1317
+ if supply_flow_timeseries.empty?
1318
+ check_elems << OpenStudio::Attribute.new('flag', "Warning: No supply node temperature timeseries found for '#{plant_loop.name}'")
1319
+ next
1320
+ else
1321
+ # convert to ruby array
1322
+ flowrates = []
1323
+ supply_flow_vector = supply_flow_timeseries.get.values
1324
+ for i in (0..supply_flow_vector.size - 1)
1325
+ flowrates << supply_flow_vector[i].to_f
1326
+ end
1327
+ end
1328
+
1329
+ # check reasonableness of supply water temperatures when supply water flow rate is operating
1330
+ operating_temperatures = temperatures.select.with_index { |_t, k| flowrates[k] > 1e-8 }
1331
+ operating_temperatures = operating_temperatures.map { |t| (t * 1.8 + 32.0) }
1332
+
1333
+ if operating_temperatures.empty?
1334
+ check_elems << OpenStudio::Attribute.new('flag', "Warning: Flowrates are all zero in supply node timeseries for '#{plant_loop.name}'")
1335
+ next
1336
+ end
1337
+
1338
+ runtime_fraction = operating_temperatures.size.to_f / temperatures.size.to_f
1339
+ temps_out_of_bounds = []
1340
+ case plant_loop.sizingPlant.loopType
1341
+ when 'Heating'
1342
+ design_return_temperature = design_supply_temperature - design_temperature_difference
1343
+ expected_max = spm_max_temp_f.nil? ? design_supply_temperature : [design_supply_temperature, spm_max_temp_f].max
1344
+ expected_min = spm_min_temp_f.nil? ? design_return_temperature : [design_return_temperature, spm_min_temp_f].min
1345
+ temps_out_of_bounds = (operating_temperatures.select { |t| (((t + max_operating_temp_delta) < expected_min) || ((t - max_operating_temp_delta) > expected_max)) })
1346
+ when 'Cooling'
1347
+ design_return_temperature = design_supply_temperature + design_temperature_difference
1348
+ expected_max = spm_max_temp_f.nil? ? design_return_temperature : [design_return_temperature, spm_max_temp_f].max
1349
+ expected_min = spm_min_temp_f.nil? ? design_supply_temperature : [design_supply_temperature, spm_min_temp_f].min
1350
+ temps_out_of_bounds = (operating_temperatures.select { |t| (((t + max_operating_temp_delta) < expected_min) || ((t - max_operating_temp_delta) > expected_max)) })
1351
+ when 'Condenser'
1352
+ design_return_temperature = design_supply_temperature + design_temperature_difference
1353
+ expected_max = spm_max_temp_f.nil? ? design_return_temperature : [design_return_temperature, spm_max_temp_f].max
1354
+ temps_out_of_bounds = (operating_temperatures.select { |t| ((t < 35.0) || (t > 100.0) || ((t - max_operating_temp_delta) > expected_max)) })
1355
+ end
1356
+
1357
+ next if temps_out_of_bounds.empty?
1358
+
1359
+ min_op_temp_f = temps_out_of_bounds.min
1360
+ max_op_temp_f = temps_out_of_bounds.max
1361
+ # avg_F = temps_out_of_bounds.inject(:+).to_f / temps_out_of_bounds.size
1362
+ spm_min_temp_f = spm_min_temp_f.round(1) unless spm_min_temp_f.nil?
1363
+ spm_max_temp_f = spm_max_temp_f.round(1) unless spm_max_temp_f.nil?
1364
+ err = []
1365
+ err << 'Major Error:'
1366
+ err << 'Expected supply water temperatures out of bounds for'
1367
+ err << "#{plant_loop.sizingPlant.loopType} plant loop '#{plant_loop.name}'"
1368
+ err << "with a #{design_supply_temperature.round(1)}F design supply temperature and"
1369
+ err << "#{design_return_temperature.round(1)}F design return temperature and"
1370
+ err << "a setpoint manager '#{spm_name}' of type '#{spm_type}' with a"
1371
+ err << "#{spm_min_temp_f}F minimum setpoint temperature and"
1372
+ err << "#{spm_max_temp_f}F maximum setpoint temperature."
1373
+ err << "Out of #{operating_temperatures.size}/#{temperatures.size} (#{(runtime_fraction * 100.0).round(1)}%) operating supply water temperatures"
1374
+ err << "#{temps_out_of_bounds.size}/#{operating_temperatures.size} (#{((temps_out_of_bounds.size.to_f / operating_temperatures.size) * 100.0).round(1)}%)"
1375
+ err << "are out of bounds with #{min_op_temp_f.round(1)}F min and #{max_op_temp_f.round(1)}F max."
1376
+ check_elems << OpenStudio::Attribute.new('flag', err.join(' ').gsub(/\n/, ''))
1377
+ end
1378
+ rescue StandardError => e
1379
+ # brief description of ruby error
1380
+ check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
1381
+
1382
+ # backtrace of ruby error for diagnostic use
1383
+ if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
1384
+ end
1385
+
1386
+ # add check_elms to new attribute
1387
+ check_elem = OpenStudio::Attribute.new('check', check_elems)
1388
+
1389
+ return check_elem
1390
+ end
1391
+
1392
+ # Check the pumping power (W/gpm) for each pump in the model to identify unrealistically sized pumps.
1393
+ #
1394
+ # @param category [String] category to bin this check into
1395
+ # @param target_standard [String] standard template, e.g. '90.1-2013'
1396
+ # @param max_pct_delta [Double] threshold for throwing an error for percent difference
1397
+ # @param name_only [Boolean] If true, only return the name of this check
1398
+ # @return [OpenStudio::Attribute] OpenStudio Attribute object containing check results
1399
+ def self.check_pump_power(category, target_standard, max_pct_delta: 0.3, name_only: false)
1400
+ # summary of the check
1401
+ check_elems = OpenStudio::AttributeVector.new
1402
+ check_elems << OpenStudio::Attribute.new('name', 'Pump Power')
1403
+ check_elems << OpenStudio::Attribute.new('category', category)
1404
+ check_elems << OpenStudio::Attribute.new('description', 'Check that pump power vs flow makes sense.')
1405
+
1406
+ # stop here if only name is requested this is used to populate display name for arguments
1407
+ if name_only == true
1408
+ results = []
1409
+ check_elems.each do |elem|
1410
+ results << elem.valueAsString
1411
+ end
1412
+ return results
1413
+ end
1414
+
1415
+ std = Standard.build(target_standard)
1416
+
1417
+ begin
1418
+ # Check each plant loop
1419
+ @model.getPlantLoops.sort.each do |plant_loop|
1420
+ # Set the expected/typical W/gpm
1421
+ loop_type = plant_loop.sizingPlant.loopType
1422
+ case loop_type
1423
+ when 'Heating'
1424
+ expected_w_per_gpm = 19.0
1425
+ when 'Cooling'
1426
+ expected_w_per_gpm = 22.0
1427
+ when 'Condenser'
1428
+ expected_w_per_gpm = 19.0
1429
+ end
1430
+
1431
+ # Check the W/gpm for each pump on each plant loop
1432
+ plant_loop.supplyComponents.each do |component|
1433
+ # Get the W/gpm for the pump
1434
+ obj_type = component.iddObjectType.valueName.to_s
1435
+ case obj_type
1436
+ when 'OS_Pump_ConstantSpeed'
1437
+ actual_w_per_gpm = std.pump_rated_w_per_gpm(component.to_PumpConstantSpeed.get)
1438
+ when 'OS_Pump_VariableSpeed'
1439
+ actual_w_per_gpm = std.pump_rated_w_per_gpm(component.to_PumpVariableSpeed.get)
1440
+ when 'OS_HeaderedPumps_ConstantSpeed'
1441
+ actual_w_per_gpm = std.pump_rated_w_per_gpm(component.to_HeaderedPumpsConstantSpeed.get)
1442
+ when 'OS_HeaderedPumps_VariableSpeed'
1443
+ actual_w_per_gpm = std.pump_rated_w_per_gpm(component.to_HeaderedPumpsVariableSpeed.get)
1444
+ else
1445
+ next # Skip non-pump objects
1446
+ end
1447
+
1448
+ # Compare W/gpm to expected/typical values
1449
+ if ((expected_w_per_gpm - actual_w_per_gpm) / actual_w_per_gpm).abs > max_pct_delta
1450
+ if plant_loop.name.get.to_s.downcase.include? 'service water loop'
1451
+ # some service water loops use just water main pressure and have a dummy pump
1452
+ check_elems << OpenStudio::Attribute.new('flag', "Warning: For #{component.name} on #{plant_loop.name}, the pumping power is #{actual_w_per_gpm.round(1)} W/gpm.")
1453
+ else
1454
+ check_elems << OpenStudio::Attribute.new('flag', "For #{component.name} on #{plant_loop.name}, the actual pumping power of #{actual_w_per_gpm.round(1)} W/gpm is more than #{(max_pct_delta * 100.0).round(2)}% different from the expected #{expected_w_per_gpm} W/gpm for a #{loop_type} plant loop.")
1455
+ end
1456
+ end
1457
+ end
1458
+ end
1459
+ rescue StandardError => e
1460
+ # brief description of ruby error
1461
+ check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
1462
+
1463
+ # backtrace of ruby error for diagnostic use
1464
+ if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
1465
+ end
1466
+
1467
+ # add check_elms to new attribute
1468
+ check_elem = OpenStudio::Attribute.new('check', check_elems)
1469
+
1470
+ return check_elem
1471
+ end
1472
+
1473
+ # Check for excess simulataneous heating and cooling
1474
+ #
1475
+ # @param category [String] category to bin this check into
1476
+ # @param max_pass_pct [Double] threshold for throwing an error for percent difference
1477
+ # @param name_only [Boolean] If true, only return the name of this check
1478
+ # @return [OpenStudio::Attribute] OpenStudio Attribute object containing check results
1479
+ def self.check_simultaneous_heating_and_cooling(category, max_pass_pct: 0.1, name_only: false)
1480
+ # summary of the check
1481
+ check_elems = OpenStudio::AttributeVector.new
1482
+ check_elems << OpenStudio::Attribute.new('name', 'Simultaneous Heating and Cooling')
1483
+ check_elems << OpenStudio::Attribute.new('category', category)
1484
+ check_elems << OpenStudio::Attribute.new('description', 'Check for simultaneous heating and cooling by looping through all Single Duct VAV Reheat Air Terminals and analyzing hourly data when there is a cooling load. ')
1485
+
1486
+ # stop here if only name is requested this is used to populate display name for arguments
1487
+ if name_only == true
1488
+ results = []
1489
+ check_elems.each do |elem|
1490
+ results << elem.valueAsString
1491
+ end
1492
+ return results
1493
+ end
1494
+
1495
+ begin
1496
+ # get the weather file run period (as opposed to design day run period)
1497
+ ann_env_pd = nil
1498
+ @sql.availableEnvPeriods.each do |env_pd|
1499
+ env_type = @sql.environmentType(env_pd)
1500
+ if env_type.is_initialized
1501
+ if env_type.get == OpenStudio::EnvironmentType.new('WeatherRunPeriod')
1502
+ ann_env_pd = env_pd
1503
+ break
1504
+ end
1505
+ end
1506
+ end
1507
+
1508
+ # only try to get the annual timeseries if an annual simulation was run
1509
+ if ann_env_pd.nil?
1510
+ check_elems << OpenStudio::Attribute.new('flag', 'Cannot find the annual simulation run period, cannot determine simultaneous heating and cooling.')
1511
+ return check_elem
1512
+ end
1513
+
1514
+ # For each VAV reheat terminal, calculate
1515
+ # the annual total % reheat hours.
1516
+ @model.getAirTerminalSingleDuctVAVReheats.sort.each do |term|
1517
+ # Reheat coil heating rate
1518
+ rht_coil = term.reheatCoil
1519
+ key_value = rht_coil.name.get.to_s.upcase # must be in all caps.
1520
+ time_step = 'Hourly' # "Zone Timestep", "Hourly", "HVAC System Timestep"
1521
+ variable_name = 'Heating Coil Heating Rate'
1522
+ variable_name_alt = 'Heating Coil Air Heating Rate'
1523
+ rht_rate_ts = @sql.timeSeries(ann_env_pd, time_step, variable_name, key_value) # key value would go at the end if we used it.
1524
+
1525
+ # try and alternate variable name
1526
+ if rht_rate_ts.empty?
1527
+ rht_rate_ts = @sql.timeSeries(ann_env_pd, time_step, variable_name_alt, key_value) # key value would go at the end if we used it.
1528
+ end
1529
+
1530
+ if rht_rate_ts.empty?
1531
+ check_elems << OpenStudio::Attribute.new('flag', "Heating Coil (Air) Heating Rate Timeseries not found for #{key_value}.")
1532
+ else
1533
+
1534
+ rht_rate_ts = rht_rate_ts.get.values
1535
+ # Put timeseries into array
1536
+ rht_rate_vals = []
1537
+ for i in 0..(rht_rate_ts.size - 1)
1538
+ rht_rate_vals << rht_rate_ts[i]
1539
+ end
1540
+
1541
+ # Zone Air Terminal Sensible Heating Rate
1542
+ key_value = "ADU #{term.name.get.to_s.upcase}" # must be in all caps.
1543
+ time_step = 'Hourly' # "Zone Timestep", "Hourly", "HVAC System Timestep"
1544
+ variable_name = 'Zone Air Terminal Sensible Cooling Rate'
1545
+ clg_rate_ts = @sql.timeSeries(ann_env_pd, time_step, variable_name, key_value) # key value would go at the end if we used it.
1546
+ if clg_rate_ts.empty?
1547
+ check_elems << OpenStudio::Attribute.new('flag', "Zone Air Terminal Sensible Cooling Rate Timeseries not found for #{key_value}.")
1548
+ else
1549
+
1550
+ clg_rate_ts = clg_rate_ts.get.values
1551
+ # Put timeseries into array
1552
+ clg_rate_vals = []
1553
+ for i in 0..(clg_rate_ts.size - 1)
1554
+ clg_rate_vals << clg_rate_ts[i]
1555
+ end
1556
+
1557
+ # Loop through each timestep and calculate the hourly
1558
+ # % reheat value.
1559
+ ann_rht_hrs = 0
1560
+ ann_clg_hrs = 0
1561
+ ann_pcts = []
1562
+ rht_rate_vals.zip(clg_rate_vals).each do |rht_w, clg_w|
1563
+ # Skip hours with no cooling (in heating mode)
1564
+ next if clg_w == 0
1565
+
1566
+ pct_overcool_rht = rht_w / (rht_w + clg_w)
1567
+ ann_rht_hrs += pct_overcool_rht # implied * 1hr b/c hrly results
1568
+ ann_clg_hrs += 1
1569
+ ann_pcts << pct_overcool_rht.round(3)
1570
+ end
1571
+
1572
+ # Calculate annual % reheat hours
1573
+ ann_pct_reheat = ((ann_rht_hrs / ann_clg_hrs) * 100).round(1)
1574
+
1575
+ # Compare to limit
1576
+ if ann_pct_reheat > max_pass_pct * 100.0
1577
+ check_elems << OpenStudio::Attribute.new('flag', "#{term.name} has #{ann_pct_reheat}% overcool-reheat, which is greater than the limit of #{max_pass_pct * 100.0}%. This terminal is in cooling mode for #{ann_clg_hrs} hours of the year.")
1578
+ end
1579
+
1580
+ end
1581
+
1582
+ end
1583
+ end
1584
+ rescue StandardError => e
1585
+ # brief description of ruby error
1586
+ check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
1587
+
1588
+ # backtrace of ruby error for diagnostic use
1589
+ if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
1590
+ end
1591
+
1592
+ # add check_elms to new attribute
1593
+ check_elem = OpenStudio::Attribute.new('check', check_elems)
1594
+
1595
+ return check_elem
1596
+ end
1597
+
1598
+ # Bin the hourly part load ratios into 10% bins
1599
+ #
1600
+ # @param hourly_part_load_ratios
1601
+ # @return [Array<Integer>] Array of 11 integers for each bin
1602
+ def self.hourly_part_load_ratio_bins(hourly_part_load_ratios)
1603
+ bins = Array.new(11, 0)
1604
+ hourly_part_load_ratios.each do |plr|
1605
+ if plr <= 0
1606
+ bins[0] += 1
1607
+ elsif plr > 0 && plr <= 0.1
1608
+ bins[1] += 1
1609
+ elsif plr > 0.1 && plr <= 0.2
1610
+ bins[2] += 1
1611
+ elsif plr > 0.2 && plr <= 0.3
1612
+ bins[3] += 1
1613
+ elsif plr > 0.3 && plr <= 0.4
1614
+ bins[4] += 1
1615
+ elsif plr > 0.4 && plr <= 0.5
1616
+ bins[5] += 1
1617
+ elsif plr > 0.5 && plr <= 0.6
1618
+ bins[6] += 1
1619
+ elsif plr > 0.6 && plr <= 0.7
1620
+ bins[7] += 1
1621
+ elsif plr > 0.7 && plr <= 0.8
1622
+ bins[8] += 1
1623
+ elsif plr > 0.8 && plr <= 0.9
1624
+ bins[9] += 1
1625
+ elsif plr > 0.9 # add over-100% PLRs to final bin
1626
+ bins[10] += 1
1627
+ end
1628
+ end
1629
+
1630
+ # Convert bins from hour counts to % of operating hours.
1631
+ bins.each_with_index do |bin, i|
1632
+ bins[i] = bins[i] * 1.0 / hourly_part_load_ratios.size
1633
+ end
1634
+
1635
+ return bins
1636
+ end
1637
+
1638
+ # Checks part loads ratios for a piece of equipment using the part load timeseries
1639
+ #
1640
+ # @param sql [OpenStudio::SqlFile] OpenStudio SqlFile
1641
+ # @param ann_env_pd [String] EnvPeriod, typically 'WeatherRunPeriod'
1642
+ # @param time_step [String] timestep, typically 'Hourly'
1643
+ # @param variable_name [String] part load ratio variable name
1644
+ # @param equipment [OpenStudio::Model::ModelObject] OpenStudio ModelObject, usually an HVACComponent
1645
+ # @param design_power [Double] equipment design power, typically in watts
1646
+ # @param units [String] design_power units, typically 'W', default ''
1647
+ # @param expect_low_plr [Boolean] toggle for whether to expect very low part load ratios and not report a message if found
1648
+ # @return [String] string with error message, or nil if none
1649
+ def self.hvac_equipment_part_load_ratio_message(sql, ann_env_pd, time_step, variable_name, equipment, design_power, units: '', expect_low_plr: false)
1650
+ msg = nil
1651
+ key_value = equipment.name.get.to_s.upcase # must be in all caps
1652
+ ts = sql.timeSeries(ann_env_pd, time_step, variable_name, key_value)
1653
+ if ts.empty?
1654
+ msg = "Warning: #{variable_name} Timeseries not found for #{key_value}."
1655
+ return msg
1656
+ end
1657
+
1658
+ if design_power.zero?
1659
+ return msg
1660
+ end
1661
+
1662
+ # Convert to array
1663
+ ts = ts.get.values
1664
+ plrs = []
1665
+ for i in 0..(ts.size - 1)
1666
+ plrs << ts[i] / design_power.to_f
1667
+ end
1668
+
1669
+ # Bin part load ratios
1670
+ bins = OpenstudioStandards::HVAC.hourly_part_load_ratio_bins(plrs)
1671
+ frac_hrs_above_90 = bins[10]
1672
+ frac_hrs_above_80 = frac_hrs_above_90 + bins[9]
1673
+ frac_hrs_above_70 = frac_hrs_above_80 + bins[8]
1674
+ frac_hrs_above_60 = frac_hrs_above_70 + bins[7]
1675
+ frac_hrs_above_50 = frac_hrs_above_60 + bins[6]
1676
+ frac_hrs_zero = bins[0]
1677
+
1678
+ pretty_bins = bins.map { |x| (x * 100).round(2) }
1679
+
1680
+ # Check top-end part load ratio bins
1681
+ if expect_low_plr
1682
+ msg = "Warning: For #{equipment.name} with design size #{design_power.round(2)} #{units} is expected to have a low part load ratio. Bins of PLR [0%,0%-10%,...]: #{pretty_bins}."
1683
+ elsif frac_hrs_zero == 1.0
1684
+ msg = "Warning: For #{equipment.name}, all hrs are zero; equipment never runs."
1685
+ elsif frac_hrs_above_50 < 0.01
1686
+ msg = "Major Error: For #{equipment.name} with design size #{design_power.round(2)} #{units}, #{(frac_hrs_above_50 * 100).round(2)}% of hrs are above 50% part load. This indicates significantly oversized equipment. Bins of PLR [0%,0%-10%,...]: #{pretty_bins}."
1687
+ elsif frac_hrs_above_60 < 0.01
1688
+ msg = "Minor Error: For #{equipment.name} with design size #{design_power.round(2)} #{units}, #{(frac_hrs_above_60 * 100).round(2)}% of hrs are above 60% part load. This indicates significantly oversized equipment. Bins of PLR [0%,0%-10%,...]: #{pretty_bins}."
1689
+ elsif frac_hrs_above_80 < 0.01
1690
+ msg = "Warning: For #{equipment.name} with design size #{design_power.round(2)} #{units}, #{(frac_hrs_above_80 * 100).round(2)}% of hrs are above 80% part load. This indicates oversized equipment. Bins of PLR [0%,0%-10%,...]: #{pretty_bins}."
1691
+ elsif frac_hrs_above_90 > 0.05
1692
+ msg = "Warning: For #{equipment.name} with design size #{design_power.round(2)} #{units}, #{(frac_hrs_above_90 * 100).round(2)}% of hrs are above 90% part load. This indicates undersized equipment. Bins of PLR [0%,0%-10%,...]: #{pretty_bins}."
1693
+ elsif frac_hrs_above_90 > 0.1
1694
+ msg = "Minor Error: For #{equipment.name} with design size #{design_power.round(2)} #{units}, #{(frac_hrs_above_90 * 100).round(2)}% of hrs are above 90% part load. This indicates significantly undersized equipment. Bins of PLR [0%,0%-10%,...]: #{pretty_bins}."
1695
+ elsif frac_hrs_above_90 > 0.2
1696
+ msg = "Major Error: For #{equipment.name} with design size #{design_power.round(2)} #{units}, #{(frac_hrs_above_90 * 100).round(2)}% of hrs are above 90% part load. This indicates significantly undersized equipment. Bins of PLR [0%,0%-10%,...]: #{pretty_bins}."
1697
+ end
1698
+ return msg
1699
+ end
1700
+
1701
+ # Check primary heating and cooling equipment part load ratios to find equipment that is significantly oversized or undersized.
1702
+ #
1703
+ # @param category [String] category to bin this check into
1704
+ # @param name_only [Boolean] If true, only return the name of this check
1705
+ # @return [OpenStudio::Attribute] OpenStudio Attribute object containing check results
1706
+ def self.check_hvac_equipment_part_load_ratios(category, name_only: false)
1707
+ # summary of the check
1708
+ check_elems = OpenStudio::AttributeVector.new
1709
+ check_elems << OpenStudio::Attribute.new('name', 'Part Load')
1710
+ check_elems << OpenStudio::Attribute.new('category', category)
1711
+ check_elems << OpenStudio::Attribute.new('description', 'Check that equipment operates at reasonable part load ranges.')
1712
+
1713
+ # stop here if only name is requested this is used to populate display name for arguments
1714
+ if name_only == true
1715
+ results = []
1716
+ check_elems.each do |elem|
1717
+ results << elem.valueAsString
1718
+ end
1719
+ return results
1720
+ end
1721
+
1722
+ begin
1723
+ # Establish limits for % of operating hrs expected above 90% part load
1724
+ expected_pct_hrs_above_90 = 0.1
1725
+
1726
+ # get the weather file run period (as opposed to design day run period)
1727
+ ann_env_pd = nil
1728
+ @sql.availableEnvPeriods.each do |env_pd|
1729
+ env_type = @sql.environmentType(env_pd)
1730
+ if env_type.is_initialized
1731
+ if env_type.get == OpenStudio::EnvironmentType.new('WeatherRunPeriod')
1732
+ ann_env_pd = env_pd
1733
+ break
1734
+ end
1735
+ end
1736
+ end
1737
+
1738
+ # only try to get the annual timeseries if an annual simulation was run
1739
+ if ann_env_pd.nil?
1740
+ check_elems << OpenStudio::Attribute.new('flag', 'Cannot find the annual simulation run period, cannot check equipment part load ratios.')
1741
+ return check_elem
1742
+ end
1743
+
1744
+ # Boilers
1745
+ @model.getBoilerHotWaters.sort.each do |boiler|
1746
+ msg = OpenstudioStandards::HVAC.hvac_equipment_part_load_ratio_message(@sql, ann_env_pd, 'Hourly', 'Boiler Part Load Ratio', boiler, 1.0)
1747
+ unless msg.nil?
1748
+ check_elems << OpenStudio::Attribute.new('flag', msg)
1749
+ end
1750
+ end
1751
+
1752
+ # Chillers
1753
+ @model.getChillerElectricEIRs.sort.each do |chiller|
1754
+ msg = OpenstudioStandards::HVAC.hvac_equipment_part_load_ratio_message(@sql, ann_env_pd, 'Hourly', 'Chiller Part Load Ratio', chiller, 1.0)
1755
+ unless msg.nil?
1756
+ check_elems << OpenStudio::Attribute.new('flag', msg)
1757
+ end
1758
+ end
1759
+
1760
+ # Cooling Towers (Single Speed)
1761
+ @model.getCoolingTowerSingleSpeeds.sort.each do |cooling_tower|
1762
+ # Get the design fan power
1763
+ if cooling_tower.fanPoweratDesignAirFlowRate.is_initialized
1764
+ design_power = cooling_tower.fanPoweratDesignAirFlowRate.get
1765
+ elsif cooling_tower.autosizedFanPoweratDesignAirFlowRate.is_initialized
1766
+ design_power = cooling_tower.autosizedFanPoweratDesignAirFlowRate.get
1767
+ else
1768
+ check_elems << OpenStudio::Attribute.new('flag', "Could not determine peak power for #{cooling_tower.name}, cannot check part load ratios.")
1769
+ next
1770
+ end
1771
+
1772
+ msg = OpenstudioStandards::HVAC.hvac_equipment_part_load_ratio_message(@sql, ann_env_pd, 'Hourly', 'Cooling Tower Fan Electric Power', cooling_tower, design_power, units: 'W')
1773
+ unless msg.nil?
1774
+ check_elems << OpenStudio::Attribute.new('flag', msg)
1775
+ end
1776
+ end
1777
+
1778
+ # Cooling Towers (Two Speed)
1779
+ @model.getCoolingTowerTwoSpeeds.sort.each do |cooling_tower|
1780
+ # Get the design fan power
1781
+ if cooling_tower.highFanSpeedFanPower.is_initialized
1782
+ design_power = cooling_tower.highFanSpeedFanPower.get
1783
+ elsif cooling_tower.autosizedHighFanSpeedFanPower.is_initialized
1784
+ design_power = cooling_tower.autosizedHighFanSpeedFanPower.get
1785
+ else
1786
+ check_elems << OpenStudio::Attribute.new('flag', "Could not determine peak power for #{cooling_tower.name}, cannot check part load ratios.")
1787
+ next
1788
+ end
1789
+
1790
+ msg = OpenstudioStandards::HVAC.hvac_equipment_part_load_ratio_message(@sql, ann_env_pd, 'Hourly', 'Cooling Tower Fan Electric Power', cooling_tower, design_power, units: 'W')
1791
+ unless msg.nil?
1792
+ check_elems << OpenStudio::Attribute.new('flag', msg)
1793
+ end
1794
+ end
1795
+
1796
+ # Cooling Towers (Variable Speed)
1797
+ @model.getCoolingTowerVariableSpeeds.sort.each do |cooling_tower|
1798
+ # Get the design fan power
1799
+ if cooling_tower.designFanPower.is_initialized
1800
+ design_power = cooling_tower.designFanPower.get
1801
+ elsif cooling_tower.autosizedDesignFanPower.is_initialized
1802
+ design_power = cooling_tower.autosizedDesignFanPower.get
1803
+ else
1804
+ check_elems << OpenStudio::Attribute.new('flag', "Could not determine peak power for #{cooling_tower.name}, cannot check part load ratios.")
1805
+ next
1806
+ end
1807
+
1808
+ msg = OpenstudioStandards::HVAC.hvac_equipment_part_load_ratio_message(@sql, ann_env_pd, 'Hourly', 'Cooling Tower Fan Electric Power', cooling_tower, design_power, units: 'W')
1809
+ unless msg.nil?
1810
+ check_elems << OpenStudio::Attribute.new('flag', msg)
1811
+ end
1812
+ end
1813
+
1814
+ # DX Cooling Coils (Single Speed)
1815
+ @model.getCoilCoolingDXSingleSpeeds.sort.each do |dx_coil|
1816
+ # Get the design coil capacity
1817
+ if dx_coil.ratedTotalCoolingCapacity.is_initialized
1818
+ design_power = dx_coil.ratedTotalCoolingCapacity.get
1819
+ elsif dx_coil.autosizedRatedTotalCoolingCapacity.is_initialized
1820
+ design_power = dx_coil.autosizedRatedTotalCoolingCapacity.get
1821
+ else
1822
+ check_elems << OpenStudio::Attribute.new('flag', "Could not determine capacity for #{dx_coil.name}, cannot check part load ratios.")
1823
+ next
1824
+ end
1825
+
1826
+ msg = OpenstudioStandards::HVAC.hvac_equipment_part_load_ratio_message(@sql, ann_env_pd, 'Hourly', 'Cooling Coil Total Cooling Rate', dx_coil, design_power, units: 'W')
1827
+ unless msg.nil?
1828
+ check_elems << OpenStudio::Attribute.new('flag', msg)
1829
+ end
1830
+ end
1831
+
1832
+ # DX Cooling Coils (Two Speed)
1833
+ @model.getCoilCoolingDXTwoSpeeds.sort.each do |dx_coil|
1834
+ # Get the design coil capacity
1835
+ if dx_coil.ratedHighSpeedTotalCoolingCapacity.is_initialized
1836
+ design_power = dx_coil.ratedHighSpeedTotalCoolingCapacity.get
1837
+ elsif dx_coil.autosizedRatedHighSpeedTotalCoolingCapacity.is_initialized
1838
+ design_power = dx_coil.autosizedRatedHighSpeedTotalCoolingCapacity.get
1839
+ else
1840
+ check_elems << OpenStudio::Attribute.new('flag', "Could not determine capacity for #{dx_coil.name}, cannot check part load ratios.")
1841
+ next
1842
+ end
1843
+
1844
+ msg = OpenstudioStandards::HVAC.hvac_equipment_part_load_ratio_message(@sql, ann_env_pd, 'Hourly', 'Cooling Coil Total Cooling Rate', dx_coil, design_power, units: 'W')
1845
+ unless msg.nil?
1846
+ check_elems << OpenStudio::Attribute.new('flag', msg)
1847
+ end
1848
+ end
1849
+
1850
+ # DX Cooling Coils (Variable Speed)
1851
+ @model.getCoilCoolingDXVariableSpeeds.sort.each do |dx_coil|
1852
+ # Get the design coil capacity
1853
+ if dx_coil.grossRatedTotalCoolingCapacityAtSelectedNominalSpeedLevel.is_initialized
1854
+ design_power = dx_coil.grossRatedTotalCoolingCapacityAtSelectedNominalSpeedLevel.get
1855
+ elsif dx_coil.autosizedGrossRatedTotalCoolingCapacityAtSelectedNominalSpeedLevel.is_initialized
1856
+ design_power = dx_coil.autosizedGrossRatedTotalCoolingCapacityAtSelectedNominalSpeedLevel.get
1857
+ else
1858
+ check_elems << OpenStudio::Attribute.new('flag', "Could not determine capacity for #{dx_coil.name}, cannot check part load ratios.")
1859
+ next
1860
+ end
1861
+
1862
+ msg = OpenstudioStandards::HVAC.hvac_equipment_part_load_ratio_message(@sql, ann_env_pd, 'Hourly', 'Cooling Coil Total Cooling Rate', dx_coil, design_power, units: 'W')
1863
+ unless msg.nil?
1864
+ check_elems << OpenStudio::Attribute.new('flag', msg)
1865
+ end
1866
+ end
1867
+
1868
+ # Gas Heating Coils
1869
+ @model.getCoilHeatingGass.sort.each do |gas_coil|
1870
+ # Get the design coil capacity
1871
+ if gas_coil.nominalCapacity.is_initialized
1872
+ design_power = gas_coil.nominalCapacity.get
1873
+ elsif gas_coil.autosizedNominalCapacity.is_initialized
1874
+ design_power = gas_coil.autosizedNominalCapacity.get
1875
+ else
1876
+ check_elems << OpenStudio::Attribute.new('flag', "Could not determine capacity for #{gas_coil.name}, cannot check part load ratios.")
1877
+ next
1878
+ end
1879
+
1880
+ if (gas_coil.name.to_s.include? 'Backup') || (gas_coil.name.to_s.include? 'Supplemental')
1881
+ msg = OpenstudioStandards::HVAC.hvac_equipment_part_load_ratio_message(@sql, ann_env_pd, 'Hourly', 'Heating Coil Heating Rate', gas_coil, design_power, units: 'W', expect_low_plr: true)
1882
+ else
1883
+ msg = OpenstudioStandards::HVAC.hvac_equipment_part_load_ratio_message(@sql, ann_env_pd, 'Hourly', 'Heating Coil Heating Rate', gas_coil, design_power, units: 'W')
1884
+ end
1885
+ unless msg.nil?
1886
+ check_elems << OpenStudio::Attribute.new('flag', msg)
1887
+ end
1888
+ end
1889
+
1890
+ # Electric Heating Coils
1891
+ @model.getCoilHeatingElectrics.sort.each do |electric_coil|
1892
+ # Get the design coil capacity
1893
+ if electric_coil.nominalCapacity.is_initialized
1894
+ design_power = electric_coil.nominalCapacity.get
1895
+ elsif electric_coil.autosizedNominalCapacity.is_initialized
1896
+ design_power = electric_coil.autosizedNominalCapacity.get
1897
+ else
1898
+ check_elems << OpenStudio::Attribute.new('flag', "Could not determine capacity for #{electric_coil.name}, cannot check part load ratios.")
1899
+ next
1900
+ end
1901
+
1902
+ if (electric_coil.name.to_s.include? 'Backup') || (electric_coil.name.to_s.include? 'Supplemental')
1903
+ msg = OpenstudioStandards::HVAC.hvac_equipment_part_load_ratio_message(@sql, ann_env_pd, 'Hourly', 'Heating Coil Heating Rate', electric_coil, design_power, units: 'W', expect_low_plr: true)
1904
+ else
1905
+ msg = OpenstudioStandards::HVAC.hvac_equipment_part_load_ratio_message(@sql, ann_env_pd, 'Hourly', 'Heating Coil Heating Rate', electric_coil, design_power, units: 'W')
1906
+ end
1907
+ unless msg.nil?
1908
+ check_elems << OpenStudio::Attribute.new('flag', msg)
1909
+ end
1910
+ end
1911
+
1912
+ # DX Heating Coils (Single Speed)
1913
+ @model.getCoilHeatingDXSingleSpeeds.sort.each do |dx_coil|
1914
+ # Get the design coil capacity
1915
+ if dx_coil.ratedTotalHeatingCapacity.is_initialized
1916
+ design_power = dx_coil.ratedTotalHeatingCapacity.get
1917
+ elsif dx_coil.autosizedRatedTotalHeatingCapacity.is_initialized
1918
+ design_power = dx_coil.autosizedRatedTotalHeatingCapacity.get
1919
+ else
1920
+ check_elems << OpenStudio::Attribute.new('flag', "Could not determine capacity for #{dx_coil.name}, cannot check part load ratios.")
1921
+ next
1922
+ end
1923
+
1924
+ msg = OpenstudioStandards::HVAC.hvac_equipment_part_load_ratio_message(@sql, ann_env_pd, 'Hourly', 'Heating Coil Heating Rate', dx_coil, design_power, units: 'W')
1925
+ unless msg.nil?
1926
+ check_elems << OpenStudio::Attribute.new('flag', msg)
1927
+ end
1928
+ end
1929
+ rescue StandardError => e
1930
+ # brief description of ruby error
1931
+ check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
1932
+
1933
+ # backtrace of ruby error for diagnostic use
1934
+ if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
1935
+ end
1936
+
1937
+ # add check_elms to new attribute
1938
+ check_elem = OpenStudio::Attribute.new('check', check_elems)
1939
+
1940
+ return check_elem
1941
+ end
1942
+ end
1943
+ end