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.
- checksums.yaml +4 -4
- data/data/inventory/thermal_bridging.csv +90 -0
- data/data/standards/OpenStudio_Standards-deer-comstock.xlsx +0 -0
- data/data/standards/manage_OpenStudio_Standards.rb +1 -1
- data/data/standards/test_performance_expected_dd_results.csv +2014 -1891
- data/lib/openstudio-standards/btap/analysis.rb +8 -8
- data/lib/openstudio-standards/btap/bridging.rb +664 -645
- data/lib/openstudio-standards/btap/btap.model.rb +14 -14
- data/lib/openstudio-standards/btap/btap.rb +7 -7
- data/lib/openstudio-standards/btap/btap_result.rb +1 -1
- data/lib/openstudio-standards/btap/economics.rb +23 -23
- data/lib/openstudio-standards/btap/envelope.rb +8 -8
- data/lib/openstudio-standards/btap/equest.rb +1 -1
- data/lib/openstudio-standards/btap/geometry.rb +2 -2
- data/lib/openstudio-standards/btap/mpc.rb +7 -7
- data/lib/openstudio-standards/btap/schedules.rb +1 -1
- data/lib/openstudio-standards/btap/simmanager.rb +4 -4
- data/lib/openstudio-standards/btap/spaceloads.rb +26 -26
- data/lib/openstudio-standards/btap/utilities.rb +6 -6
- data/lib/openstudio-standards/btap/vintagizer.rb +1 -1
- data/lib/openstudio-standards/constructions/information.rb +83 -0
- data/lib/openstudio-standards/constructions/materials/modify.rb +72 -0
- data/lib/openstudio-standards/constructions/modify.rb +80 -0
- data/lib/openstudio-standards/create_typical/create_typical.rb +983 -0
- data/lib/openstudio-standards/create_typical/enumerations.rb +484 -0
- data/lib/openstudio-standards/create_typical/space_type_blend.rb +791 -0
- data/lib/openstudio-standards/create_typical/space_type_ratios.rb +494 -0
- data/lib/openstudio-standards/daylighting/space.rb +47 -0
- data/lib/openstudio-standards/geometry/create.rb +801 -0
- data/lib/openstudio-standards/geometry/create_bar.rb +2171 -0
- data/lib/openstudio-standards/geometry/information.rb +462 -0
- data/lib/openstudio-standards/geometry/modify.rb +48 -0
- data/lib/openstudio-standards/hvac/air_loop/information.rb +79 -0
- data/lib/openstudio-standards/hvac/cbecs_hvac.rb +616 -0
- data/lib/openstudio-standards/hvac/setpoint_managers/information.rb +91 -0
- data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2004/ashrae_90_1_2004.AirTerminalSingleDuctVAVReheat.rb +1 -1
- data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2007/ashrae_90_1_2007.AirTerminalSingleDuctVAVReheat.rb +1 -1
- data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2010/ashrae_90_1_2010.AirTerminalSingleDuctVAVReheat.rb +1 -1
- data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2010/ashrae_90_1_2010.Model.rb +1 -1
- data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.AirTerminalSingleDuctVAVReheat.rb +1 -1
- data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.Model.rb +2 -2
- data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.hvac_systems.rb +1 -1
- data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.AirTerminalSingleDuctVAVReheat.rb +1 -1
- data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Model.rb +4 -36
- data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.hvac_systems.rb +1 -1
- data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.AirTerminalSingleDuctVAVReheat.rb +1 -1
- data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.Model.rb +4 -36
- data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.Space.rb +3 -3
- data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.hvac_systems.rb +1 -1
- data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_1980_2004/doe_ref_1980_2004.AirTerminalSingleDuctVAVReheat.rb +1 -1
- data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_1980_2004/doe_ref_1980_2004.Model.elevators.rb +1 -1
- data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_pre_1980/doe_ref_pre_1980.AirTerminalSingleDuctVAVReheat.rb +1 -1
- data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_pre_1980/doe_ref_pre_1980.CoilHeatingGas.rb +1 -1
- data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_pre_1980/doe_ref_pre_1980.Model.elevators.rb +1 -1
- data/lib/openstudio-standards/prototypes/ashrae_90_1/nrel_nze_ready_2017/nrel_zne_ready_2017.AirTerminalSingleDuctVAVReheat.rb +1 -1
- data/lib/openstudio-standards/prototypes/ashrae_90_1/ze_aedg_multifamily/ze_aedg_multifamily.AirTerminalSingleDuctVAVReheat.rb +1 -1
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.College.rb +7 -7
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.Courthouse.rb +8 -8
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.FullServiceRestaurant.rb +14 -14
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.HighRiseApartment.rb +9 -9
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.Hospital.rb +16 -16
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.Laboratory.rb +7 -7
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.LargeDataCenterHighITE.rb +8 -8
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.LargeDataCenterLowITE.rb +8 -8
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.LargeHotel.rb +11 -11
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.LargeOffice.rb +7 -7
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.LargeOfficeDetailed.rb +9 -9
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.MediumOffice.rb +8 -8
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.MediumOfficeDetailed.rb +11 -11
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.MidriseApartment.rb +9 -9
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.Outpatient.rb +19 -19
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.PrimarySchool.rb +10 -10
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.QuickServiceRestaurant.rb +13 -13
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.RetailStandalone.rb +6 -6
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.RetailStripmall.rb +6 -6
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SecondarySchool.rb +9 -9
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SmallDataCenterHighITE.rb +8 -8
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SmallDataCenterLowITE.rb +8 -8
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SmallHotel.rb +8 -8
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SmallOffice.rb +8 -8
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SmallOfficeDetailed.rb +11 -11
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SuperMarket.rb +10 -10
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SuperTallBuilding.rb +19 -19
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.TallBuilding.rb +18 -18
- data/lib/openstudio-standards/prototypes/common/buildings/Prototype.Warehouse.rb +6 -6
- data/lib/openstudio-standards/prototypes/common/do_not_edit_metaclasses.rb +957 -957
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.AirConditionerVariableRefrigerantFlow.rb +1 -1
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.AirTerminalSingleDuctVAVReheat.rb +1 -1
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.CoilCoolingWaterToAirHeatPumpEquationFit.rb +84 -16
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.CoilHeatingDXSingleSpeed.rb +1 -1
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.CoilHeatingGas.rb +1 -1
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.CoilHeatingWaterToAirHeatPumpEquationFit.rb +61 -10
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.ControllerWaterCoil.rb +1 -1
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.CoolingTower.rb +1 -1
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.Fan.rb +1 -1
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.FanConstantVolume.rb +1 -1
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.FanOnOff.rb +1 -1
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.FanVariableVolume.rb +1 -1
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.FanZoneExhaust.rb +1 -1
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.HeatExchangerAirToAirSensibleAndLatent.rb +2 -2
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.Model.exterior_lights.rb +4 -4
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.Model.hvac.rb +4 -4
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb +43 -30
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.Model.swh.rb +1 -1
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.ServiceWaterHeating.rb +18 -11
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.SizingSystem.rb +1 -1
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.hvac_systems.rb +774 -117
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.radiant_system_controls.rb +340 -481
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.refrigeration.rb +3 -3
- data/lib/openstudio-standards/prototypes/common/objects/Prototype.utilities.rb +3 -3
- data/lib/openstudio-standards/prototypes/common/prototype_metaprogramming.rb +22 -22
- data/lib/openstudio-standards/prototypes/deer/deer.Model.rb +1 -1
- data/lib/openstudio-standards/qaqc/calibration.rb +131 -0
- data/lib/openstudio-standards/qaqc/create_results.rb +983 -0
- data/lib/openstudio-standards/qaqc/envelope.rb +399 -0
- data/lib/openstudio-standards/qaqc/eui.rb +213 -0
- data/lib/openstudio-standards/qaqc/hvac.rb +1943 -0
- data/lib/openstudio-standards/qaqc/internal_loads.rb +568 -0
- data/lib/openstudio-standards/qaqc/reporting.rb +141 -0
- data/lib/openstudio-standards/qaqc/schedules.rb +129 -0
- data/lib/openstudio-standards/qaqc/service_water_heating.rb +273 -0
- data/lib/openstudio-standards/qaqc/weather_files.rb +497 -0
- data/lib/openstudio-standards/qaqc/zone_conditions.rb +278 -0
- data/lib/openstudio-standards/schedules/create.rb +364 -0
- data/lib/openstudio-standards/schedules/information.rb +169 -0
- data/lib/openstudio-standards/schedules/modify.rb +445 -0
- data/lib/openstudio-standards/standards/Standards.AirLoopHVAC.rb +110 -71
- data/lib/openstudio-standards/standards/Standards.AirTerminalSingleDuctParallelPIUReheat.rb +3 -3
- data/lib/openstudio-standards/standards/Standards.AirTerminalSingleDuctVAVReheat.rb +4 -4
- data/lib/openstudio-standards/standards/Standards.BoilerHotWater.rb +2 -1
- data/lib/openstudio-standards/standards/Standards.ChillerElectricEIR.rb +16 -10
- data/lib/openstudio-standards/standards/Standards.CoilCoolingDXSingleSpeed.rb +4 -4
- data/lib/openstudio-standards/standards/Standards.CoilCoolingDXTwoSpeed.rb +1 -1
- data/lib/openstudio-standards/standards/Standards.CoilCoolingWaterToAirHeatPumpEquationFit.rb +1 -1
- data/lib/openstudio-standards/standards/Standards.CoilDX.rb +4 -4
- data/lib/openstudio-standards/standards/Standards.CoilHeatingDXMultiSpeed.rb +1 -1
- data/lib/openstudio-standards/standards/Standards.CoilHeatingDXSingleSpeed.rb +5 -5
- data/lib/openstudio-standards/standards/Standards.CoilHeatingGas.rb +1 -1
- data/lib/openstudio-standards/standards/Standards.CoilHeatingGasMultiStage.rb +1 -1
- data/lib/openstudio-standards/standards/Standards.CoilHeatingWaterToAirHeatPumpEquationFit.rb +1 -1
- data/lib/openstudio-standards/standards/Standards.Construction.rb +17 -18
- data/lib/openstudio-standards/standards/Standards.CoolingTower.rb +6 -6
- data/lib/openstudio-standards/standards/Standards.CoolingTowerSingleSpeed.rb +1 -1
- data/lib/openstudio-standards/standards/Standards.CoolingTowerTwoSpeed.rb +1 -1
- data/lib/openstudio-standards/standards/Standards.CoolingTowerVariableSpeed.rb +1 -1
- data/lib/openstudio-standards/standards/Standards.Fan.rb +6 -12
- data/lib/openstudio-standards/standards/Standards.FanVariableVolume.rb +2 -2
- data/lib/openstudio-standards/standards/Standards.FluidCooler.rb +1 -1
- data/lib/openstudio-standards/standards/Standards.HeaderedPumpsVariableSpeed.rb +1 -1
- data/lib/openstudio-standards/standards/Standards.HeatExchangerSensLat.rb +3 -3
- data/lib/openstudio-standards/standards/Standards.Model.rb +411 -261
- data/lib/openstudio-standards/standards/Standards.PlanarSurface.rb +2 -2
- data/lib/openstudio-standards/standards/Standards.PlantLoop.rb +94 -29
- data/lib/openstudio-standards/standards/Standards.Pump.rb +2 -2
- data/lib/openstudio-standards/standards/Standards.ScheduleConstant.rb +2 -2
- data/lib/openstudio-standards/standards/Standards.ScheduleRuleset.rb +14 -14
- data/lib/openstudio-standards/standards/Standards.Space.rb +37 -30
- data/lib/openstudio-standards/standards/Standards.SpaceType.rb +38 -29
- data/lib/openstudio-standards/standards/Standards.SubSurface.rb +7 -7
- data/lib/openstudio-standards/standards/Standards.Surface.rb +13 -13
- data/lib/openstudio-standards/standards/Standards.ThermalZone.rb +109 -66
- data/lib/openstudio-standards/standards/Standards.WaterHeaterMixed.rb +11 -4
- data/lib/openstudio-standards/standards/Standards.ZoneHVACComponent.rb +6 -6
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/ashrae_90_1_2004.AirLoopHVAC.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/ashrae_90_1_2004.PlantLoop.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/ashrae_90_1_2004.Space.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/ashrae_90_1_2007.Space.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/ashrae_90_1_2010.AirLoopHVAC.rb +4 -4
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/ashrae_90_1_2010.AirTerminalSingleDuctVAVReheat.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/ashrae_90_1_2010.Space.rb +4 -4
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.AirLoopHVAC.rb +4 -4
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.AirTerminalSingleDuctVAVReheat.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.CoolingTowerVariableSpeed.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.Model.rb +5 -21
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.Space.rb +4 -4
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.WaterHeaterMixed.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.ZoneHVACComponent.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.AirLoopHVAC.rb +36 -4
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.AirTerminalSingleDuctVAVReheat.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.CoolingTowerVariableSpeed.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Space.rb +4 -4
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.ZoneHVACComponent.rb +1 -1
- 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
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.AirLoopHVAC.rb +53 -10
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.AirTerminalSingleDuctVAVReheat.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.CoolingTowerVariableSpeed.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.Space.rb +6 -6
- data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.ZoneHVACComponent.rb +2 -2
- 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
- data/lib/openstudio-standards/standards/ashrae_90_1/data/ashrae_90_1.curves.json +211 -211
- data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_1980_2004/data/doe_ref_1980_2004.economizers.json +14 -14
- data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_1980_2004/doe_ref_1980_2004.AirLoopHVAC.rb +4 -4
- data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_1980_2004/doe_ref_1980_2004.Model.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_1980_2004/doe_ref_1980_2004.PlantLoop.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_pre_1980/data/doe_ref_pre_1980.economizers.json +14 -14
- data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_pre_1980/doe_ref_pre_1980.AirLoopHVAC.rb +4 -4
- data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_pre_1980/doe_ref_pre_1980.Model.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_pre_1980/doe_ref_pre_1980.PlantLoop.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/nrel_zne_ready_2017/nrel_zne_ready_2017.AirLoopHVAC.rb +6 -6
- data/lib/openstudio-standards/standards/ashrae_90_1/nrel_zne_ready_2017/nrel_zne_ready_2017.AirTerminalSingleDuctVAVReheat.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/nrel_zne_ready_2017/nrel_zne_ready_2017.CoolingTowerVariableSpeed.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/nrel_zne_ready_2017/nrel_zne_ready_2017.PlantLoop.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/nrel_zne_ready_2017/nrel_zne_ready_2017.Space.rb +4 -4
- data/lib/openstudio-standards/standards/ashrae_90_1/ze_aedg_multifamily/ze_aedg_multifamily.AirLoopHVAC.rb +6 -6
- data/lib/openstudio-standards/standards/ashrae_90_1/ze_aedg_multifamily/ze_aedg_multifamily.AirTerminalSingleDuctVAVReheat.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/ze_aedg_multifamily/ze_aedg_multifamily.CoolingTowerVariableSpeed.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/ze_aedg_multifamily/ze_aedg_multifamily.PlantLoop.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1/ze_aedg_multifamily/ze_aedg_multifamily.Space.rb +4 -4
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb +22 -28
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirTerminalSingleDuctParallelPIUReheat.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirTerminalSingleDuctVAVReheat.rb +2 -2
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.BoilerHotWater.rb +1 -74
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.ChillerElectricEIR.rb +7 -59
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilCoolingDXSingleSpeed.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilCoolingDXTwoSpeed.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilDX.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilHeatingDXSingleSpeed.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilHeatingGas.rb +1 -21
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.DesignSpecificationOutdoorAir.rb +101 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.FanConstantVolume.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.FanOnOff.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.FanVariableVolume.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.HeatExchangerSensLat.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb +643 -526
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.PlanarSurface.rb +8 -2
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.PlantLoop.rb +17 -77
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Space.rb +74 -16
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.SpaceType.rb +96 -44
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Surface.rb +6 -6
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.ThermalZone.rb +18 -6
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.ZoneHVACComponent.rb +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb +328 -74
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/ashrae_90_1_prm_2019.Model.rb +0 -118
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/ashrae_90_1_prm_2019.rb +2 -1
- 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
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/baseline_outdoor_air.md +35 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/set_plug_load_measures.md +1 -1
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/ashrae_90_1_prm.UserData.rb +228 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/userdata_enums.rb +131 -0
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/userdata_space.csv +1 -1
- data/lib/openstudio-standards/standards/cbes/cbes.AirLoopHVAC.rb +5 -5
- data/lib/openstudio-standards/standards/cbes/cbes.Model.rb +1 -1
- data/lib/openstudio-standards/standards/cbes/cbes.PlantLoop.rb +1 -1
- data/lib/openstudio-standards/standards/cbes/cbes.Space.rb +1 -1
- data/lib/openstudio-standards/standards/cbes/cbes_t24_2005/cbes_t24_2005.Space.rb +1 -1
- data/lib/openstudio-standards/standards/cbes/cbes_t24_2008/cbes_t24_2008.Space.rb +1 -1
- data/lib/openstudio-standards/standards/deer/deer.AirLoopHVAC.rb +109 -27
- data/lib/openstudio-standards/standards/deer/deer.Space.rb +1 -1
- data/lib/openstudio-standards/standards/deer/deer_1985/data/deer_1985.economizers.json +246 -4
- data/lib/openstudio-standards/standards/deer/deer_1996/data/deer_1996.economizers.json +246 -4
- data/lib/openstudio-standards/standards/deer/deer_2003/data/deer_2003.economizers.json +246 -4
- data/lib/openstudio-standards/standards/deer/deer_2003/deer_2003.ThermalZone.rb +18 -18
- data/lib/openstudio-standards/standards/deer/deer_2007/data/deer_2007.economizers.json +246 -4
- data/lib/openstudio-standards/standards/deer/deer_2007/deer_2007.ThermalZone.rb +18 -18
- data/lib/openstudio-standards/standards/deer/deer_2011/data/deer_2011.economizers.json +246 -4
- data/lib/openstudio-standards/standards/deer/deer_2011/deer_2011.ThermalZone.rb +18 -18
- data/lib/openstudio-standards/standards/deer/deer_2014/data/deer_2014.economizers.json +248 -6
- data/lib/openstudio-standards/standards/deer/deer_2014/deer_2014.Space.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2014/deer_2014.ThermalZone.rb +18 -18
- data/lib/openstudio-standards/standards/deer/deer_2015/data/deer_2015.economizers.json +248 -6
- data/lib/openstudio-standards/standards/deer/deer_2015/deer_2015.Space.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2015/deer_2015.ThermalZone.rb +18 -18
- data/lib/openstudio-standards/standards/deer/deer_2017/data/deer_2017.economizers.json +248 -6
- data/lib/openstudio-standards/standards/deer/deer_2017/deer_2017.Space.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2017/deer_2017.ThermalZone.rb +18 -18
- data/lib/openstudio-standards/standards/deer/deer_2020/data/deer_2020.economizers.json +248 -6
- data/lib/openstudio-standards/standards/deer/deer_2020/deer_2020.AirLoopHVAC.rb +18 -5
- data/lib/openstudio-standards/standards/deer/deer_2020/deer_2020.FanVariableVolume.rb +1 -1
- data/lib/openstudio-standards/standards/deer/deer_2020/deer_2020.Space.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2020/deer_2020.ThermalZone.rb +18 -18
- data/lib/openstudio-standards/standards/deer/deer_2025/data/deer_2025.economizers.json +248 -6
- data/lib/openstudio-standards/standards/deer/deer_2025/deer_2025.AirLoopHVAC.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2025/deer_2025.FanVariableVolume.rb +1 -1
- data/lib/openstudio-standards/standards/deer/deer_2025/deer_2025.Space.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2030/data/deer_2030.economizers.json +248 -6
- data/lib/openstudio-standards/standards/deer/deer_2030/data/deer_2030.heat_pumps.json +2 -2
- data/lib/openstudio-standards/standards/deer/deer_2030/deer_2030.AirLoopHVAC.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2030/deer_2030.FanVariableVolume.rb +1 -1
- data/lib/openstudio-standards/standards/deer/deer_2030/deer_2030.Space.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2035/data/deer_2035.economizers.json +248 -6
- data/lib/openstudio-standards/standards/deer/deer_2035/deer_2035.AirLoopHVAC.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2035/deer_2035.FanVariableVolume.rb +1 -1
- data/lib/openstudio-standards/standards/deer/deer_2035/deer_2035.Space.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2040/data/deer_2040.economizers.json +248 -6
- data/lib/openstudio-standards/standards/deer/deer_2040/deer_2040.AirLoopHVAC.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2040/deer_2040.FanVariableVolume.rb +1 -1
- data/lib/openstudio-standards/standards/deer/deer_2040/deer_2040.Space.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2045/data/deer_2045.economizers.json +260 -0
- data/lib/openstudio-standards/standards/deer/deer_2045/deer_2045.AirLoopHVAC.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2045/deer_2045.FanVariableVolume.rb +1 -1
- data/lib/openstudio-standards/standards/deer/deer_2045/deer_2045.Space.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2050/data/deer_2050.economizers.json +248 -6
- data/lib/openstudio-standards/standards/deer/deer_2050/deer_2050.AirLoopHVAC.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2050/deer_2050.FanVariableVolume.rb +1 -1
- data/lib/openstudio-standards/standards/deer/deer_2050/deer_2050.Space.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2055/data/deer_2055.economizers.json +248 -6
- data/lib/openstudio-standards/standards/deer/deer_2055/deer_2055.AirLoopHVAC.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2055/deer_2055.FanVariableVolume.rb +1 -1
- data/lib/openstudio-standards/standards/deer/deer_2055/deer_2055.Space.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2060/data/deer_2060.economizers.json +248 -6
- data/lib/openstudio-standards/standards/deer/deer_2060/deer_2060.AirLoopHVAC.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2060/deer_2060.FanVariableVolume.rb +1 -1
- data/lib/openstudio-standards/standards/deer/deer_2060/deer_2060.Space.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2065/data/deer_2065.economizers.json +248 -6
- data/lib/openstudio-standards/standards/deer/deer_2065/deer_2065.AirLoopHVAC.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2065/deer_2065.FanVariableVolume.rb +1 -1
- data/lib/openstudio-standards/standards/deer/deer_2065/deer_2065.Space.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2070/data/deer_2070.economizers.json +248 -6
- data/lib/openstudio-standards/standards/deer/deer_2070/deer_2070.AirLoopHVAC.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2070/deer_2070.FanVariableVolume.rb +1 -1
- data/lib/openstudio-standards/standards/deer/deer_2070/deer_2070.Space.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2075/data/deer_2075.economizers.json +248 -6
- data/lib/openstudio-standards/standards/deer/deer_2075/deer_2075.AirLoopHVAC.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_2075/deer_2075.FanVariableVolume.rb +1 -1
- data/lib/openstudio-standards/standards/deer/deer_2075/deer_2075.Space.rb +3 -3
- data/lib/openstudio-standards/standards/deer/deer_pre_1975/data/deer_pre_1975.economizers.json +246 -4
- data/lib/openstudio-standards/standards/necb/BTAP1980TO2010/data/space_types.json +447 -223
- data/lib/openstudio-standards/standards/necb/BTAPPRE1980/building_envelope.rb +1 -1
- data/lib/openstudio-standards/standards/necb/BTAPPRE1980/data/space_types.json +447 -223
- data/lib/openstudio-standards/standards/necb/BTAPPRE1980/hvac_systems.rb +5 -2
- data/lib/openstudio-standards/standards/necb/ECMS/data/chiller_types.json +25 -0
- data/lib/openstudio-standards/standards/necb/ECMS/data/chillers.json +44 -0
- data/lib/openstudio-standards/standards/necb/ECMS/data/curves.json +225 -0
- data/lib/openstudio-standards/standards/necb/ECMS/ecms.rb +2 -2
- data/lib/openstudio-standards/standards/necb/ECMS/hvac_systems.rb +193 -73
- data/lib/openstudio-standards/standards/necb/ECMS/pv_ground.rb +1 -1
- data/lib/openstudio-standards/standards/necb/NECB2011/autozone.rb +10 -4
- data/lib/openstudio-standards/standards/necb/NECB2011/beps_compliance_path.rb +7 -7
- data/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb +4 -5
- data/lib/openstudio-standards/standards/necb/NECB2011/data/chiller_types.json +32 -0
- data/lib/openstudio-standards/standards/necb/NECB2011/data/chillers.json +1 -1
- data/lib/openstudio-standards/standards/necb/NECB2011/data/constants.json +36 -0
- data/lib/openstudio-standards/standards/necb/NECB2011/data/fuel_type_sets.json +7 -7
- data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/NorthernEducation.osm +47587 -0
- data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/NorthernHealthCare.osm +49764 -0
- data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/Warehouse.osm +283 -297
- data/lib/openstudio-standards/standards/necb/NECB2011/data/space_type_unit_definitions.txt +2 -1
- data/lib/openstudio-standards/standards/necb/NECB2011/data/space_types.json +447 -223
- data/lib/openstudio-standards/standards/necb/NECB2011/data/standards_data.rb +3 -3
- data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_1_multi_speed.rb +1 -1
- data/lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb +49 -27
- data/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +400 -202
- data/lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb +4 -4
- data/lib/openstudio-standards/standards/necb/NECB2015/data/space_types.json +637 -318
- data/lib/openstudio-standards/standards/necb/NECB2015/hvac_systems.rb +18 -1
- data/lib/openstudio-standards/standards/necb/NECB2015/necb_2015.rb +3 -3
- data/lib/openstudio-standards/standards/necb/NECB2017/data/space_types.json +637 -318
- data/lib/openstudio-standards/standards/necb/NECB2017/hvac_systems.rb +1 -1
- data/lib/openstudio-standards/standards/necb/NECB2017/necb_2017.rb +3 -3
- data/lib/openstudio-standards/standards/necb/NECB2020/building_envelope.rb +1 -1
- data/lib/openstudio-standards/standards/necb/NECB2020/data/space_types.json +615 -307
- data/lib/openstudio-standards/standards/necb/NECB2020/service_water_heating.rb +4 -4
- data/lib/openstudio-standards/standards/necb/common/btap_data.rb +10 -5
- data/lib/openstudio-standards/standards/necb/common/btap_datapoint.rb +1 -1
- data/lib/openstudio-standards/utilities/assertion.rb +128 -0
- data/lib/openstudio-standards/utilities/logging.rb +2 -3
- data/lib/openstudio-standards/utilities/object_info.rb +39 -18
- data/lib/openstudio-standards/utilities/schedule_translator.rb +8 -6
- data/lib/openstudio-standards/utilities/simulation.rb +24 -11
- data/lib/openstudio-standards/utilities/sqlfile.rb +10 -5
- data/lib/openstudio-standards/version.rb +1 -1
- data/lib/openstudio-standards/weather/Weather.Model.rb +8 -9
- data/lib/openstudio-standards/weather/Weather.stat_file.rb +3 -3
- data/lib/openstudio-standards/weather/information.rb +35 -0
- data/lib/openstudio-standards.rb +69 -5
- metadata +52 -16
- data/data/standards/OpenStudio_Standards-deer-ALL-comstock(space_types).xlsx +0 -0
- data/lib/openstudio-standards/hvac_sizing/Siz.AirLoopHVAC.rb +0 -59
- data/lib/openstudio-standards/hvac_sizing/Siz.CoilCoolingWater.rb +0 -13
- data/lib/openstudio-standards/hvac_sizing/Siz.HVACComponent.rb +0 -36
- data/lib/openstudio-standards/hvac_sizing/Siz.HeatingCoolingFuels.rb +0 -898
- data/lib/openstudio-standards/hvac_sizing/Siz.Model.rb +0 -126
- data/lib/openstudio-standards/hvac_sizing/Siz.ThermalZone.rb +0 -356
- data/lib/openstudio-standards/prototypes/ashrae_90_1/nrel_nze_ready_2017/nrel_zne_ready_2017.Model.rb +0 -35
- data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoolingTower.rb +0 -110
- 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
|