LCOV - code coverage report
Current view: top level - EnergyPlus - HVACControllers.cc (source / functions) Coverage Total Hit
Test: lcov.output.filtered Lines: 50.4 % 1154 582
Test Date: 2025-06-02 07:23:51 Functions: 62.9 % 35 22

            Line data    Source code
       1              : // EnergyPlus, Copyright (c) 1996-2025, The Board of Trustees of the University of Illinois,
       2              : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
       3              : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
       4              : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
       5              : // contributors. All rights reserved.
       6              : //
       7              : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
       8              : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
       9              : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
      10              : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
      11              : // derivative works, and perform publicly and display publicly, and to permit others to do so.
      12              : //
      13              : // Redistribution and use in source and binary forms, with or without modification, are permitted
      14              : // provided that the following conditions are met:
      15              : //
      16              : // (1) Redistributions of source code must retain the above copyright notice, this list of
      17              : //     conditions and the following disclaimer.
      18              : //
      19              : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
      20              : //     conditions and the following disclaimer in the documentation and/or other materials
      21              : //     provided with the distribution.
      22              : //
      23              : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
      24              : //     the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
      25              : //     used to endorse or promote products derived from this software without specific prior
      26              : //     written permission.
      27              : //
      28              : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
      29              : //     without changes from the version obtained under this License, or (ii) Licensee makes a
      30              : //     reference solely to the software portion of its product, Licensee must refer to the
      31              : //     software as "EnergyPlus version X" software, where "X" is the version number Licensee
      32              : //     obtained under this License and may not use a different name for the software. Except as
      33              : //     specifically required in this Section (4), Licensee shall not use in a company name, a
      34              : //     product name, in advertising, publicity, or other promotional activities any name, trade
      35              : //     name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
      36              : //     similar designation, without the U.S. Department of Energy's prior written consent.
      37              : //
      38              : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
      39              : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
      40              : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
      41              : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
      42              : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
      43              : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
      44              : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
      45              : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
      46              : // POSSIBILITY OF SUCH DAMAGE.
      47              : 
      48              : // ObjexxFCL Headers
      49              : #include <ObjexxFCL/Array.functions.hh>
      50              : #include <ObjexxFCL/Array2D.hh>
      51              : #include <ObjexxFCL/Fmath.hh>
      52              : #include <ObjexxFCL/numeric.hh>
      53              : #include <ObjexxFCL/string.functions.hh>
      54              : 
      55              : // EnergyPlus Headers
      56              : #include <EnergyPlus/Autosizing/Base.hh>
      57              : #include <EnergyPlus/Data/EnergyPlusData.hh>
      58              : #include <EnergyPlus/DataAirSystems.hh>
      59              : #include <EnergyPlus/DataConvergParams.hh>
      60              : #include <EnergyPlus/DataEnvironment.hh>
      61              : #include <EnergyPlus/DataHVACGlobals.hh>
      62              : #include <EnergyPlus/DataLoopNode.hh>
      63              : #include <EnergyPlus/DataPrecisionGlobals.hh>
      64              : #include <EnergyPlus/DataSizing.hh>
      65              : #include <EnergyPlus/DataSystemVariables.hh>
      66              : #include <EnergyPlus/EMSManager.hh>
      67              : #include <EnergyPlus/FaultsManager.hh>
      68              : #include <EnergyPlus/FluidProperties.hh>
      69              : #include <EnergyPlus/General.hh>
      70              : #include <EnergyPlus/HVACControllers.hh>
      71              : #include <EnergyPlus/IOFiles.hh>
      72              : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
      73              : #include <EnergyPlus/MixedAir.hh>
      74              : #include <EnergyPlus/NodeInputManager.hh>
      75              : #include <EnergyPlus/PlantUtilities.hh>
      76              : #include <EnergyPlus/RootFinder.hh>
      77              : #include <EnergyPlus/SetPointManager.hh>
      78              : #include <EnergyPlus/UtilityRoutines.hh>
      79              : #include <EnergyPlus/WaterCoils.hh>
      80              : 
      81              : namespace EnergyPlus::HVACControllers {
      82              : // Module containing the controller simulation routines for the air loop
      83              : 
      84              : // MODULE INFORMATION:
      85              : //       AUTHOR         Richard J. Liesen
      86              : //       DATE WRITTEN   July 1998
      87              : //       MODIFIED       Feb 2006, Dimitri Curtil (LBNL)
      88              : //                      - Added tracing mechanism for debugging convergence process.
      89              : //                        - Trace operation of each individual controller in a file named
      90              : //                          'controller.<Controller Name>.csv'
      91              : //                        - Trace operation of all controllers per air loop in a file named
      92              : //                          'controller.<Air Loop Name>.csv'
      93              : //                      - Added operations to enable cold start/speculative warm restart
      94              : //                        and final check.
      95              : //       MODIFIED       March 2006, Dimitri Curtil (LBNL)
      96              : //                      - Added mechanism to track runtime performance statistics.
      97              : //                      - Added routine to dump controller statistics to a file named
      98              : //                        'statistics.HVACControllers.csv'
      99              : //                      - Integrated smart root finder from MODULE RootFinder implemented in
     100              : //                        file RootFinder.cc.
     101              : //       MODIFIED       April 2006, Dimitri Curtil (LBNL)
     102              : //                      - Added speedup optimization scheme to reuse air loop solution
     103              : //                        obtained at the current HVAC iteration from solving the previous controller
     104              : //                        on the loop (see ReuseIntermediateSolutionFlag). Of course this works only
     105              : //                        if there are 2 or more controllers on the same air loop.
     106              : //                      - Added speedup optimization scheme to reuse solution obtained
     107              : //                        at the previous HVAC iteration for this controller during the
     108              : //                        bracketing phase (see ReusePreviousSolutionFlag).
     109              : //       MODIFIED       May 2006, Dimitri Curtil (LBNL)
     110              : //                      - Added mechanism to monitor min/max bounds to ensure that they remain invariant
     111              : //                        between successive controller iterations.
     112              : //                      - Modified setpoint calculation to force the setpoint to be computed only once.
     113              : //                      - Modified setpoint calculation for TEMPandHUMRAT control strategy to
     114              : //                        force the setpoint to be computed once the air loop has been evaluated with
     115              : //                        the max actuated value.
     116              : //       MODIFIED       June 2006, Dimitri Curtil (LBNL)
     117              : //                      - Renamed parameter variables so as to use lower caps.
     118              : //                      - Replaced $ edit descriptor in WRITE statements with ADVANCE='No'
     119              : //                      - Replaced the preprocessing directives TRACK_AIRLOOP, TRACE_AIRLOOP,
     120              : //                        TRACE_CONTROLLER with corresponding environment variables defined
     121              : //                        in DataSystemVariables.cc.
     122              : //       MODIFIED       Feb. 2010, Brent Griffith (NREL)
     123              : //                       - changed plant loop interactions, Demand Side Update Phase 3
     124              : 
     125              : // PURPOSE OF THIS MODULE:
     126              : // To encapsulate the data and algorithms required to manage the Controller System Component.
     127              : 
     128              : // METHODOLOGY EMPLOYED:
     129              : // The main entry point if the SUBROUTINE ManageControllers().
     130              : // 1. For proper operation, the subroutine must first be called with either the
     131              : //    iControllerOpColdStart or iControllerOpWarmRestart operation code to initialize
     132              : //    the various controllers.
     133              : // 2. Then the actuated variable for each controller is computed iteratively using
     134              : //    root finding techniques that aim at forcing the sensed variable to be
     135              : //    "equal" (within the user-specified tolerance) to the desired setpoint.
     136              : //    This step is achieved by calling ManageController() with the iControllerOpIterate
     137              : //    operation code.
     138              : // 3. Finally, after all controllers have been successfully simulated,  the subroutine has
     139              : //    to be called one last time with the iControllerOpEnd operation code to ensure that
     140              : //    the sequential solution indeed represents a valid global solution across all controllers
     141              : //    simultaneously.
     142              : // The following pseudo-code shows the typical calling sequence for the SUBROUTINE
     143              : // ManageControllers :
     144              : // - for each controller on air loop
     145              : //   - CALL ManageControllers( Operation=<iControllerOpColdStart or iControllerOpWarmRestart> )
     146              : // - simulate air loop components with the initial values for all actuated variables
     147              : // - for each controller on air loop
     148              : //   - CALL ManageControllers( Operation=iControllerOpIterate, IsConvergedFlag )
     149              : //   - if NOT IsConvergedFlag then
     150              : //     - exit loop with error if too many iterations performed
     151              : //     - simulate air loop components with the new candidate value for the actuated variable of
     152              : //       the current controller
     153              : // - simulate air loop components with the final values for all actuated variables
     154              : // - for each controller on air loop
     155              : //   - CALL ManageControllers( Operation=iControllerOpEnd, IsConvergedFlag )
     156              : //   - if NOT IsConvergedFlag then
     157              : //     - exit loop with error indicating no "global" convergence with final solution.
     158              : // Check the subroutines SolveAirLoopControllers() and ReSolveAirLoopControllers()
     159              : // invoked in the subroutine SimAirLoop() for the actual calling sequences.
     160              : 
     161              : // OTHER NOTES:
     162              : // To enable runtime statistics tracking for each air loop, define the environment variable
     163              : // TRACK_AIRLOOP=YES or TRACK_AIRLOOP=Y.
     164              : // To enable generating a trace file with the converged solution for all controllers on each air loop,
     165              : // define the environment variable TRACE_AIRLOOP=YES or TRACE_AIRLOOP=Y.
     166              : // To enable generating an individual, detailed trace file for each controller, define the
     167              : // environment variable TRACE_CONTROLLER=YES or TRACE_CONTROLLER=Y.
     168              : // See DataSystemVariables.cc for the definitions of the environment variables used to debug
     169              : // the air loop simulation.
     170              : 
     171              : // Number of significant digits to display in error messages for floating-point numbers
     172              : constexpr int NumSigDigits = 15;
     173              : constexpr std::array<std::string_view, static_cast<int>(CtrlVarType::Num)> ctrlVarNamesUC = {
     174              :     "INVALID-NONE", "TEMPERATURE", "HUMIDITYRATIO", "TEMPERATUREANDHUMIDITYRATIO", "INVALID-FLOW"};
     175              : constexpr std::array<std::string_view, static_cast<int>(ControllerAction::Num)> actionNamesUC = {"", "REVERSE", "NORMAL"};
     176              : 
     177            3 : std::string ControlVariableTypes(CtrlVarType const &c)
     178              : {
     179            3 :     switch (c) {
     180            0 :     case CtrlVarType::NoControlVariable:
     181            0 :         return "No control variable";
     182            3 :     case CtrlVarType::Temperature:
     183            6 :         return "Temperature";
     184            0 :     case CtrlVarType::HumidityRatio:
     185            0 :         return "Humidity ratio";
     186            0 :     case CtrlVarType::TemperatureAndHumidityRatio:
     187            0 :         return "Temperature and humidity ratio";
     188            0 :     case CtrlVarType::Flow:
     189            0 :         return "Flow rate";
     190            0 :     default:
     191            0 :         assert(false);
     192              :     }
     193              :     return "no controller type found";
     194              : }
     195              : 
     196     55412594 : void ManageControllers(EnergyPlusData &state,
     197              :                        std::string const &ControllerName,
     198              :                        int &ControllerIndex,
     199              :                        bool const FirstHVACIteration,
     200              :                        int const AirLoopNum,
     201              :                        DataHVACControllers::ControllerOperation const Operation,
     202              :                        bool &IsConvergedFlag,
     203              :                        bool &IsUpToDateFlag,
     204              :                        bool const BypassOAController,
     205              :                        ObjexxFCL::Optional_bool AllowWarmRestartFlag)
     206              : {
     207              : 
     208              :     // SUBROUTINE INFORMATION:
     209              :     //       AUTHOR         Richard Liesen
     210              :     //       DATE WRITTEN   July 1998
     211              :     //       MODIFIED       Dimitri Curtil, February 2006
     212              :     //                      - Added air loop information
     213              :     //                      - Added tracing to csv files
     214              :     //                      - Added primitive operations to replace mixed
     215              :     //                        bag of ResetController, FirstCallConvergenceTest, ...
     216              : 
     217              :     // PURPOSE OF THIS SUBROUTINE:
     218              :     // This subroutine manages Controller component simulation.
     219              : 
     220              :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
     221              :     // The Controller that you are currently loading input into
     222              :     int ControlNum;
     223              : 
     224              :     // Obtains and Allocates Controller related parameters from input file
     225     55412594 :     if (state.dataHVACControllers->GetControllerInputFlag) { // First time subroutine has been entered
     226            0 :         GetControllerInput(state);
     227            0 :         state.dataHVACControllers->GetControllerInputFlag = false;
     228              :     }
     229              : 
     230     55412594 :     if (ControllerIndex == 0) {
     231            0 :         ControlNum = Util::FindItemInList(ControllerName, state.dataHVACControllers->ControllerProps, &ControllerPropsType::ControllerName);
     232            0 :         if (ControlNum == 0) {
     233            0 :             ShowFatalError(
     234              :                 state,
     235            0 :                 format("ManageControllers: Invalid controller={}. The only valid controller type for an AirLoopHVAC is Controller:WaterCoil.",
     236              :                        ControllerName));
     237              :         }
     238            0 :         ControllerIndex = ControlNum;
     239              :     } else {
     240     55412594 :         ControlNum = ControllerIndex;
     241     55412594 :         if (ControlNum > state.dataHVACControllers->NumControllers || ControlNum < 1) {
     242            0 :             ShowFatalError(state,
     243            0 :                            format("ManageControllers: Invalid ControllerIndex passed={}, Number of controllers={}, Controller name={}",
     244              :                                   ControlNum,
     245            0 :                                   state.dataHVACControllers->NumControllers,
     246              :                                   ControllerName));
     247              :         }
     248     55412594 :         if (state.dataHVACControllers->CheckEquipName(ControlNum)) {
     249          902 :             if (ControllerName != state.dataHVACControllers->ControllerProps(ControlNum).ControllerName) {
     250            0 :                 ShowFatalError(
     251              :                     state,
     252            0 :                     format("ManageControllers: Invalid ControllerIndex passed={}, Controller name={}, stored Controller Name for that index={}",
     253              :                            ControlNum,
     254              :                            ControllerName,
     255            0 :                            state.dataHVACControllers->ControllerProps(ControlNum).ControllerName));
     256              :             }
     257          902 :             state.dataHVACControllers->CheckEquipName(ControlNum) = false;
     258              :         }
     259              :     }
     260              : 
     261     55412594 :     auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
     262              : 
     263     55412594 :     if (controllerProps.BypassControllerCalc && BypassOAController) {
     264      1905678 :         IsUpToDateFlag = true;
     265      1905678 :         IsConvergedFlag = true;
     266      1905678 :         if (present(AllowWarmRestartFlag)) {
     267       635226 :             AllowWarmRestartFlag = true;
     268              :         }
     269      2224518 :         return;
     270              :     }
     271              : 
     272              :     // Find the correct ControllerNumber with the AirLoop & CompNum from AirLoop Derived Type
     273              :     // ControlNum = AirLoopEquip(AirLoopNum)%ComponentOfTypeNum(CompNum)
     274              : 
     275              :     // detect if plant is locked and flow cannot change
     276     53506916 :     if (controllerProps.ActuatedNodePlantLoc.loopNum > 0) {
     277              : 
     278     53506014 :         if (state.dataPlnt->PlantLoop(controllerProps.ActuatedNodePlantLoc.loopNum)
     279     53506014 :                 .LoopSide(controllerProps.ActuatedNodePlantLoc.loopSideNum)
     280     53506014 :                 .FlowLock == DataPlant::FlowLock::Locked) {
     281              :             // plant is rigid so controller cannot change anything.
     282              :             // Update the current Controller to the outlet nodes
     283       318840 :             UpdateController(state, ControlNum);
     284              : 
     285       318840 :             IsConvergedFlag = true;
     286       318840 :             return;
     287              :         }
     288              :     }
     289              : 
     290              :     // Detect if speculative warm restart is supported by this computer
     291     53188076 :     if (present(AllowWarmRestartFlag)) {
     292              :         // NOTE: Never allow speculative warm restart with dual humidity ratio and temperature control
     293              :         //       because the actual setpoint depends on the current temperature and max hum ratio at
     294              :         //       the sensed node, and therefore might not be known until after one air loop simulation.
     295     10395381 :         if (controllerProps.ControlVar == CtrlVarType::TemperatureAndHumidityRatio) {
     296       624430 :             AllowWarmRestartFlag = false;
     297              :         } else {
     298      9770951 :             AllowWarmRestartFlag = true;
     299              :         }
     300              :     }
     301              : 
     302     53188076 :     if (controllerProps.InitFirstPass) {
     303              :         // Coil must first be sized to:
     304              :         // Initialize controllerProps%MinActuated and controllerProps%MaxActuated
     305          902 :         InitController(state, ControlNum, IsConvergedFlag);
     306          902 :         controllerProps.InitFirstPass = false;
     307              :     }
     308              : 
     309              :     // Perform requested operation
     310              :     // Note that InitController() is not called upon START/RESTART ops in order to avoid
     311              :     // side-effects on the calculation of Node(ActuatedNode)%MassFlowRateMaxAvail used to
     312              :     // determine controllerProps%MaxAvailActuated.
     313              :     // Plant upgrades for V7 added init to these cases because MassFlowRateMaxAvail is better controlled
     314     53188076 :     switch (Operation) {
     315     10395381 :     case DataHVACControllers::ControllerOperation::ColdStart: {
     316              :         // For temperature and humidity control reset humidity control override if it was set
     317     10395381 :         if (controllerProps.HumRatCtrlOverride) {
     318        70166 :             controllerProps.HumRatCtrlOverride = false;
     319              :             // Put the controller tolerance (offset) back to it's original value
     320        70166 :             RootFinder::SetupRootFinder(state,
     321        70166 :                                         state.dataHVACControllers->RootFinders(ControlNum),
     322              :                                         DataRootFinder::Slope::Decreasing,
     323              :                                         DataRootFinder::RootFinderMethod::Brent,
     324              :                                         DataPrecisionGlobals::constant_zero,
     325              :                                         1.0e-6,
     326              :                                         controllerProps.Offset);
     327              :         }
     328              : 
     329              :         // If a iControllerOpColdStart call, reset the actuator inlet flows
     330     10395381 :         ResetController(state, ControlNum, false, IsConvergedFlag);
     331              :         // Update the current Controller to the outlet nodes
     332     10395381 :         UpdateController(state, ControlNum);
     333     10395381 :     } break;
     334      5216889 :     case DataHVACControllers::ControllerOperation::WarmRestart: {
     335              :         // If a iControllerOpWarmRestart call, set the actuator inlet flows to previous solution
     336      5216889 :         ResetController(state, ControlNum, true, IsConvergedFlag);
     337              :         // Update the current Controller to the outlet nodes
     338      5216889 :         UpdateController(state, ControlNum);
     339      5216889 :     } break;
     340     21963536 :     case DataHVACControllers::ControllerOperation::Iterate: {
     341              :         // With the correct ControlNum Initialize all Controller related parameters
     342     21963536 :         InitController(state, ControlNum, IsConvergedFlag);
     343              : 
     344              :         // No initialization needed: should have been done before
     345              :         // Simulate the correct Controller with the current ControlNum
     346     21963536 :         int ControllerType = controllerProps.ControllerType_Num;
     347              : 
     348     21963536 :         if (ControllerType == ControllerSimple_Type) { // 'Controller:WaterCoil'
     349     21963536 :             CalcSimpleController(state, ControlNum, FirstHVACIteration, IsConvergedFlag, IsUpToDateFlag, ControllerName);
     350              :         } else {
     351            0 :             ShowFatalError(state, format("Invalid controller type in ManageControllers={}", controllerProps.ControllerType));
     352              :         }
     353              : 
     354              :         // Update the current Controller to the outlet nodes
     355     21963536 :         UpdateController(state, ControlNum);
     356              : 
     357     21963536 :         CheckTempAndHumRatCtrl(state, ControlNum, IsConvergedFlag);
     358              : 
     359     21963536 :     } break;
     360     15612270 :     case DataHVACControllers::ControllerOperation::End: {
     361              :         // With the correct ControlNum Initialize all Controller related parameters
     362     15612270 :         InitController(state, ControlNum, IsConvergedFlag);
     363              : 
     364              :         // No initialization needed: should have been done before
     365              :         // Check convergence for the correct Controller with the current ControlNum
     366     15612270 :         int ControllerType = controllerProps.ControllerType_Num;
     367              : 
     368     15612270 :         if (ControllerType == ControllerSimple_Type) { // 'Controller:WaterCoil'
     369     15612270 :             CheckSimpleController(state, ControlNum, IsConvergedFlag);
     370     15612270 :             SaveSimpleController(state, ControlNum, FirstHVACIteration, IsConvergedFlag);
     371              :         } else {
     372            0 :             ShowFatalError(state, format("Invalid controller type in ManageControllers={}", controllerProps.ControllerType));
     373              :         }
     374              : 
     375     15612270 :     } break;
     376            0 :     default: {
     377            0 :         ShowFatalError(state, format("ManageControllers: Invalid Operation passed={}, Controller name={}", Operation, ControllerName));
     378            0 :     } break;
     379              :     }
     380              : 
     381              :     // Write detailed diagnostic for individual controller
     382              :     // To enable generating an individual, detailed trace file for each controller on each air loop,
     383              :     // define the environment variable TRACE_CONTROLLER=YES or TRACE_CONTROLLER=Y
     384     53188076 :     if (state.dataSysVars->TraceHVACControllerEnvFlag) {
     385            0 :         TraceIndividualController(
     386            0 :             state, ControlNum, FirstHVACIteration, state.dataAirLoop->AirLoopControlInfo(AirLoopNum).AirLoopPass, Operation, IsConvergedFlag);
     387              :     }
     388              : }
     389              : 
     390              : // Get Input Section of the Module
     391              : //******************************************************************************
     392              : 
     393          326 : void GetControllerInput(EnergyPlusData &state)
     394              : {
     395              : 
     396              :     // SUBROUTINE INFORMATION:
     397              :     //       AUTHOR         Richard Liesen
     398              :     //       DATE WRITTEN   July 1998
     399              :     //       MODIFIED       February 2006, Dimitri Curtil
     400              :     //                      - Added processing for air loop controller stats
     401              : 
     402              :     // PURPOSE OF THIS SUBROUTINE:
     403              :     // This subroutine is the main routine to call other input routines and Get routines
     404              : 
     405              :     // METHODOLOGY EMPLOYED:
     406              :     // Uses the status flags to trigger events.
     407              : 
     408              :     // SUBROUTINE PARAMETER DEFINITIONS:
     409          326 :     int NumPrimaryAirSys = state.dataHVACGlobal->NumPrimaryAirSys;
     410              :     static constexpr std::string_view RoutineName("HVACControllers: GetControllerInput: "); // include trailing blank space
     411              : 
     412              :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
     413              :     int NumAlphas;
     414              :     int NumNums;
     415              :     int NumArgs;
     416              :     bool ActuatorNodeNotFound; // true if no water coil inlet node match for actuator node
     417          326 :     Array1D<Real64> NumArray;
     418          326 :     Array1D_string AlphArray;
     419          326 :     Array1D_string cAlphaFields;   // Alpha field names
     420          326 :     Array1D_string cNumericFields; // Numeric field names
     421          326 :     Array1D_bool lAlphaBlanks;     // Logical array, alpha field input BLANK = .TRUE.
     422          326 :     Array1D_bool lNumericBlanks;   // Logical array, numeric field input BLANK = .TRUE.
     423          326 :     bool ErrorsFound(false);
     424              : 
     425              :     // All the controllers are loaded into the same derived type, both the PI and Limit
     426              :     // These controllers are separate objects and loaded sequentially, but will
     427              :     // be retrieved by name as they are needed.
     428              : 
     429          326 :     std::string const CurrentModuleObject = "Controller:WaterCoil";
     430          326 :     int NumSimpleControllers = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, CurrentModuleObject);
     431          326 :     state.dataHVACControllers->NumControllers = NumSimpleControllers;
     432              : 
     433              :     // Allocate stats data structure for each air loop and controller if needed
     434          326 :     if (state.dataSysVars->TrackAirLoopEnvFlag || state.dataSysVars->TraceAirLoopEnvFlag || state.dataSysVars->TraceHVACControllerEnvFlag) {
     435            0 :         if (NumPrimaryAirSys > 0) {
     436            0 :             state.dataHVACControllers->NumAirLoopStats = NumPrimaryAirSys;
     437            0 :             state.dataHVACControllers->AirLoopStats.allocate(state.dataHVACControllers->NumAirLoopStats);
     438              : 
     439              :             // Allocate controller statistics data for each controller on each air loop
     440            0 :             for (int AirLoopNum = 1; AirLoopNum <= NumPrimaryAirSys; ++AirLoopNum) {
     441            0 :                 state.dataHVACControllers->AirLoopStats(AirLoopNum)
     442            0 :                     .ControllerStats.allocate(state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers);
     443              :             }
     444              :         }
     445              :     }
     446              : 
     447          326 :     if (state.dataHVACControllers->NumControllers == 0) {
     448           29 :         return;
     449              :     }
     450              :     // Condition of no controllers will be taken care of elsewhere, if necessary
     451              : 
     452          297 :     state.dataHVACControllers->ControllerProps.allocate(state.dataHVACControllers->NumControllers);
     453          297 :     state.dataHVACControllers->RootFinders.allocate(state.dataHVACControllers->NumControllers);
     454          297 :     state.dataHVACControllers->CheckEquipName.dimension(state.dataHVACControllers->NumControllers, true);
     455              : 
     456          297 :     state.dataInputProcessing->inputProcessor->getObjectDefMaxArgs(state, CurrentModuleObject, NumArgs, NumAlphas, NumNums);
     457          297 :     AlphArray.allocate(NumAlphas);
     458          297 :     cAlphaFields.allocate(NumAlphas);
     459          297 :     cNumericFields.allocate(NumNums);
     460          297 :     NumArray.dimension(NumNums, 0.0);
     461          297 :     lAlphaBlanks.dimension(NumAlphas, true);
     462          297 :     lNumericBlanks.dimension(NumNums, true);
     463              : 
     464              :     // Now find and load all of the simple controllers.
     465          297 :     if (NumSimpleControllers > 0) {
     466              :         int IOStat;
     467         1199 :         for (int Num = 1; Num <= NumSimpleControllers; ++Num) {
     468          902 :             auto &controllerProps = state.dataHVACControllers->ControllerProps(Num);
     469          902 :             state.dataInputProcessing->inputProcessor->getObjectItem(state,
     470              :                                                                      CurrentModuleObject,
     471              :                                                                      Num,
     472              :                                                                      AlphArray,
     473              :                                                                      NumAlphas,
     474              :                                                                      NumArray,
     475              :                                                                      NumNums,
     476              :                                                                      IOStat,
     477              :                                                                      lNumericBlanks,
     478              :                                                                      lAlphaBlanks,
     479              :                                                                      cAlphaFields,
     480              :                                                                      cNumericFields);
     481              : 
     482          902 :             controllerProps.ControllerName = AlphArray(1);
     483          902 :             controllerProps.ControllerType = CurrentModuleObject;
     484              : 
     485          902 :             controllerProps.ControlVar = static_cast<EnergyPlus::HVACControllers::CtrlVarType>(getEnumValue(ctrlVarNamesUC, AlphArray(2)));
     486          902 :             if (controllerProps.ControlVar == HVACControllers::CtrlVarType::Invalid) {
     487            0 :                 ShowSevereError(state, format("{}{}=\"{}\".", RoutineName, CurrentModuleObject, AlphArray(1)));
     488            0 :                 ShowContinueError(state,
     489            0 :                                   format("...Invalid {}=\"{}\", must be Temperature, HumidityRatio, or TemperatureAndHumidityRatio.",
     490              :                                          cAlphaFields(2),
     491              :                                          AlphArray(2)));
     492            0 :                 ErrorsFound = true;
     493              :             }
     494              : 
     495          902 :             controllerProps.Action = static_cast<ControllerAction>(getEnumValue(actionNamesUC, AlphArray(3)));
     496          902 :             if (controllerProps.Action == ControllerAction::Invalid) {
     497            0 :                 ShowSevereError(state, format("{}{}=\"{}\".", RoutineName, CurrentModuleObject, AlphArray(1)));
     498            0 :                 ShowContinueError(state,
     499            0 :                                   format("...Invalid {}=\"{}{}", cAlphaFields(3), AlphArray(3), R"(", must be "Normal", "Reverse" or blank.)"));
     500            0 :                 ErrorsFound = true;
     501              :             }
     502              : 
     503          902 :             if (AlphArray(4) == "FLOW") {
     504          902 :                 controllerProps.ActuatorVar = HVACControllers::CtrlVarType::Flow;
     505              :             } else {
     506            0 :                 ShowSevereError(state, format("{}{}=\"{}\".", RoutineName, CurrentModuleObject, AlphArray(1)));
     507            0 :                 ShowContinueError(state, format("...Invalid {}=\"{}\", only FLOW is allowed.", cAlphaFields(4), AlphArray(4)));
     508            0 :                 ErrorsFound = true;
     509              :             }
     510          902 :             controllerProps.SensedNode = NodeInputManager::GetOnlySingleNode(state,
     511          902 :                                                                              AlphArray(5),
     512              :                                                                              ErrorsFound,
     513              :                                                                              DataLoopNode::ConnectionObjectType::ControllerWaterCoil,
     514          902 :                                                                              AlphArray(1),
     515              :                                                                              DataLoopNode::NodeFluidType::Blank,
     516              :                                                                              DataLoopNode::ConnectionType::Sensor,
     517              :                                                                              NodeInputManager::CompFluidStream::Primary,
     518              :                                                                              DataLoopNode::ObjectIsNotParent);
     519          902 :             controllerProps.ActuatedNode = NodeInputManager::GetOnlySingleNode(state,
     520          902 :                                                                                AlphArray(6),
     521              :                                                                                ErrorsFound,
     522              :                                                                                DataLoopNode::ConnectionObjectType::ControllerWaterCoil,
     523          902 :                                                                                AlphArray(1),
     524              :                                                                                DataLoopNode::NodeFluidType::Blank,
     525              :                                                                                DataLoopNode::ConnectionType::Actuator,
     526              :                                                                                NodeInputManager::CompFluidStream::Primary,
     527              :                                                                                DataLoopNode::ObjectIsNotParent);
     528          902 :             controllerProps.Offset = NumArray(1);
     529          902 :             controllerProps.MaxVolFlowActuated = NumArray(2);
     530          902 :             controllerProps.MinVolFlowActuated = NumArray(3);
     531              : 
     532          902 :             if (!MixedAir::CheckForControllerWaterCoil(state, DataAirLoop::ControllerKind::WaterCoil, AlphArray(1))) {
     533            0 :                 ShowSevereError(state,
     534            0 :                                 format("{}{}=\"{}\" not found on any AirLoopHVAC:ControllerList.", RoutineName, CurrentModuleObject, AlphArray(1)));
     535            0 :                 ErrorsFound = true;
     536              :             }
     537              : 
     538          902 :             if (controllerProps.SensedNode > 0) {
     539              : 
     540              :                 bool NodeNotFound; // flag true if the sensor node is on the coil air outlet node
     541          902 :                 if (controllerProps.ControlVar == HVACControllers::CtrlVarType::HumidityRatio ||
     542          902 :                     controllerProps.ControlVar == HVACControllers::CtrlVarType::TemperatureAndHumidityRatio) {
     543           55 :                     SetPointManager::ResetHumidityRatioCtrlVarType(state, controllerProps.SensedNode);
     544              :                 }
     545          902 :                 WaterCoils::CheckForSensorAndSetPointNode(state, controllerProps.SensedNode, controllerProps.ControlVar, NodeNotFound);
     546              : 
     547          902 :                 if (NodeNotFound) {
     548              :                     // the sensor node is not on the water coil air outlet node
     549            0 :                     ShowWarningError(state, format("{}{}=\"{}\". ", RoutineName, controllerProps.ControllerType, controllerProps.ControllerName));
     550            0 :                     ShowContinueError(state, " ..Sensor node not found on water coil air outlet node.");
     551            0 :                     ShowContinueError(state,
     552              :                                       " ..The sensor node may have been placed on a node downstream of the coil or on an airloop outlet node.");
     553              :                 } else {
     554              :                     // check if the setpoint is also on the same node where the sensor is placed on
     555          902 :                     bool EMSSetPointErrorFlag = false;
     556          902 :                     switch (controllerProps.ControlVar) {
     557          847 :                     case HVACControllers::CtrlVarType::Temperature: {
     558          847 :                         EMSManager::CheckIfNodeSetPointManagedByEMS(state, controllerProps.SensedNode, HVAC::CtrlVarType::Temp, EMSSetPointErrorFlag);
     559          847 :                         state.dataLoopNodes->NodeSetpointCheck(controllerProps.SensedNode).needsSetpointChecking = false;
     560          847 :                         if (EMSSetPointErrorFlag) {
     561          638 :                             if (!SetPointManager::NodeHasSPMCtrlVarType(state, controllerProps.SensedNode, HVAC::CtrlVarType::Temp)) {
     562            0 :                                 ShowContinueError(state, " ..Temperature setpoint not found on coil air outlet node.");
     563            0 :                                 ShowContinueError(
     564              :                                     state, " ..The setpoint may have been placed on a node downstream of the coil or on an airloop outlet node.");
     565            0 :                                 ShowContinueError(state, " ..Specify the setpoint and the sensor on the coil air outlet node when possible.");
     566              :                             }
     567              :                         }
     568          847 :                     } break;
     569            0 :                     case HVACControllers::CtrlVarType::HumidityRatio: {
     570            0 :                         EMSManager::CheckIfNodeSetPointManagedByEMS(
     571              :                             state, controllerProps.SensedNode, HVAC::CtrlVarType::MaxHumRat, EMSSetPointErrorFlag);
     572            0 :                         state.dataLoopNodes->NodeSetpointCheck(controllerProps.SensedNode).needsSetpointChecking = false;
     573            0 :                         if (EMSSetPointErrorFlag) {
     574            0 :                             if (!SetPointManager::NodeHasSPMCtrlVarType(state, controllerProps.SensedNode, HVAC::CtrlVarType::MaxHumRat)) {
     575            0 :                                 ShowContinueError(state, " ..Humidity ratio setpoint not found on coil air outlet node.");
     576            0 :                                 ShowContinueError(
     577              :                                     state, " ..The setpoint may have been placed on a node downstream of the coil or on an airloop outlet node.");
     578            0 :                                 ShowContinueError(state, " ..Specify the setpoint and the sensor on the coil air outlet node when possible.");
     579              :                             }
     580              :                         }
     581            0 :                     } break;
     582           55 :                     case HVACControllers::CtrlVarType::TemperatureAndHumidityRatio: {
     583           55 :                         EMSManager::CheckIfNodeSetPointManagedByEMS(state, controllerProps.SensedNode, HVAC::CtrlVarType::Temp, EMSSetPointErrorFlag);
     584           55 :                         state.dataLoopNodes->NodeSetpointCheck(controllerProps.SensedNode).needsSetpointChecking = false;
     585           55 :                         if (EMSSetPointErrorFlag) {
     586           30 :                             if (!SetPointManager::NodeHasSPMCtrlVarType(state, controllerProps.SensedNode, HVAC::CtrlVarType::Temp)) {
     587            0 :                                 ShowContinueError(state, " ..Temperature setpoint not found on coil air outlet node.");
     588            0 :                                 ShowContinueError(
     589              :                                     state, " ..The setpoint may have been placed on a node downstream of the coil or on an airloop outlet node.");
     590            0 :                                 ShowContinueError(state, " ..Specify the setpoint and the sensor on the coil air outlet node when possible.");
     591              :                             }
     592              :                         }
     593           55 :                         EMSSetPointErrorFlag = false;
     594           55 :                         EMSManager::CheckIfNodeSetPointManagedByEMS(
     595              :                             state, controllerProps.SensedNode, HVAC::CtrlVarType::MaxHumRat, EMSSetPointErrorFlag);
     596           55 :                         state.dataLoopNodes->NodeSetpointCheck(controllerProps.SensedNode).needsSetpointChecking = false;
     597           55 :                         if (EMSSetPointErrorFlag) {
     598           30 :                             if (!SetPointManager::NodeHasSPMCtrlVarType(state, controllerProps.SensedNode, HVAC::CtrlVarType::MaxHumRat)) {
     599            0 :                                 ShowContinueError(state, " ..Humidity ratio setpoint not found on coil air outlet node.");
     600            0 :                                 ShowContinueError(
     601              :                                     state, " ..The setpoint may have been placed on a node downstream of the coil or on an airloop outlet node.");
     602            0 :                                 ShowContinueError(state, " ..Specify the setpoint and the sensor on the coil air outlet node when possible.");
     603              :                             }
     604              :                         }
     605           55 :                     } break;
     606            0 :                     default:
     607            0 :                         break;
     608              :                     }
     609              :                 }
     610              :             }
     611              :         }
     612              :     }
     613              : 
     614              :     // check that actuator nodes are matched by a water coil inlet node
     615         1199 :     for (int Num = 1; Num <= NumSimpleControllers; ++Num) {
     616          902 :         auto &controllerProps = state.dataHVACControllers->ControllerProps(Num);
     617          902 :         WaterCoils::CheckActuatorNode(state, controllerProps.ActuatedNode, controllerProps.WaterCoilType, ActuatorNodeNotFound);
     618          902 :         if (ActuatorNodeNotFound) {
     619            0 :             ErrorsFound = true;
     620            0 :             ShowSevereError(state, format("{}{}=\"{}\":", RoutineName, CurrentModuleObject, controllerProps.ControllerName));
     621            0 :             ShowContinueError(state, "...the actuator node must also be a water inlet node of a water coil");
     622              :         } else { // Node found, check type and action
     623          902 :             if (controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterCooling ||
     624          593 :                 controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterDetailedFlatCooling) {
     625          471 :                 if (controllerProps.Action == ControllerAction::NoAction) {
     626            0 :                     controllerProps.Action = ControllerAction::Reverse;
     627          471 :                 } else if (controllerProps.Action == ControllerAction::NormalAction) {
     628            0 :                     ShowWarningError(state, format("{}{}=\"{}\":", RoutineName, CurrentModuleObject, controllerProps.ControllerName));
     629            0 :                     ShowContinueError(state, "...Normal action has been specified for a cooling coil - should be Reverse.");
     630            0 :                     ShowContinueError(state, "...overriding user input action with Reverse Action.");
     631            0 :                     controllerProps.Action = ControllerAction::Reverse;
     632              :                 }
     633          431 :             } else if (controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterSimpleHeating) {
     634          431 :                 if (controllerProps.Action == ControllerAction::NoAction) {
     635            0 :                     controllerProps.Action = ControllerAction::NormalAction;
     636          431 :                 } else if (controllerProps.Action == ControllerAction::Reverse) {
     637            0 :                     ShowWarningError(state, format("{}{}=\"{}\":", RoutineName, CurrentModuleObject, controllerProps.ControllerName));
     638            0 :                     ShowContinueError(state, "...Reverse action has been specified for a heating coil - should be Normal.");
     639            0 :                     ShowContinueError(state, "...overriding user input action with Normal Action.");
     640            0 :                     controllerProps.Action = ControllerAction::NormalAction;
     641              :                 }
     642              :             }
     643              :         }
     644              :     }
     645              : 
     646          297 :     AlphArray.deallocate();
     647          297 :     cAlphaFields.deallocate();
     648          297 :     cNumericFields.deallocate();
     649          297 :     NumArray.deallocate();
     650          297 :     lAlphaBlanks.deallocate();
     651          297 :     lNumericBlanks.deallocate();
     652              : 
     653              :     // CR 8253 check that the sensed nodes in the controllers are in flow order in controller List
     654          297 :     CheckControllerListOrder(state);
     655              : 
     656          297 :     if (ErrorsFound) {
     657            0 :         ShowFatalError(state, format("{}Errors found in getting {} input.", RoutineName, CurrentModuleObject));
     658              :     }
     659          500 : }
     660              : 
     661              : // End of Get Input subroutines for the Module
     662              : //******************************************************************************
     663              : 
     664              : // Beginning Initialization Section of the Module
     665              : //******************************************************************************
     666              : 
     667     15682453 : void ResetController(EnergyPlusData &state, int const ControlNum, bool const DoWarmRestartFlag, bool &IsConvergedFlag)
     668              : {
     669              : 
     670              :     // SUBROUTINE INFORMATION:
     671              :     //       AUTHOR         Fred Buhl
     672              :     //       DATE WRITTEN   April 2004
     673              :     //       MODIFIED       Dimitri Curtil (LBNL), Feb 2006
     674              :     //                      - Added capability for speculative warm restart
     675              :     //                      Brent Griffith (NREL), Feb 2010
     676              :     //                      - use SetActuatedBranchFlowRate in Plant Utilities (honor hardware min > 0.0)
     677              :     //                      - add FirstHVACIteration logic, don't reset if false,
     678              : 
     679              :     // PURPOSE OF THIS SUBROUTINE:
     680              :     // This subroutine resets the actuator inlet flows.
     681              : 
     682     15682453 :     auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
     683     15682453 :     auto &rootFinders = state.dataHVACControllers->RootFinders(ControlNum);
     684              : 
     685     15682453 :     Real64 NoFlowResetValue = 0.0;
     686     15682453 :     PlantUtilities::SetActuatedBranchFlowRate(state, NoFlowResetValue, controllerProps.ActuatedNode, controllerProps.ActuatedNodePlantLoc, true);
     687              : 
     688              :     //  ENDIF
     689              : 
     690              :     // Reset iteration counter and internal variables
     691     15682453 :     controllerProps.NumCalcCalls = 0;
     692     15682453 :     controllerProps.DeltaSensed = 0.0;
     693     15682453 :     controllerProps.SensedValue = 0.0;
     694     15682453 :     controllerProps.ActuatedValue = 0.0;
     695              : 
     696              :     // Reset setpoint-related quantities
     697     15682453 :     controllerProps.SetPointValue = 0.0;
     698     15682453 :     controllerProps.IsSetPointDefinedFlag = false;
     699              : 
     700              :     // MinAvailActuated and MaxAvailActuated set in InitController()
     701     15682453 :     controllerProps.MinAvailActuated = 0.0;
     702     15682453 :     controllerProps.MinAvailSensed = 0.0;
     703     15682453 :     controllerProps.MaxAvailActuated = 0.0;
     704     15682453 :     controllerProps.MaxAvailSensed = 0.0;
     705              : 
     706              :     // Restart from previous solution if speculative warm restart flag set
     707              :     // Keep same mode and next actuated value unchanged from last controller simulation.
     708     15682453 :     if (DoWarmRestartFlag) {
     709      5216889 :         controllerProps.DoWarmRestartFlag = true;
     710              :     } else {
     711     10465564 :         controllerProps.DoWarmRestartFlag = false;
     712              :         // If no speculative warm restart then reset stored mode and actucated value
     713     10465564 :         controllerProps.Mode = ControllerMode::None;
     714     10465564 :         controllerProps.NextActuatedValue = 0.0;
     715              :     }
     716              : 
     717              :     // Only set once per HVAC iteration.
     718              :     // Might be overwritten in the InitController() routine.
     719              :     // Allow reusing the previous solution while identifying brackets if
     720              :     // this is not the first HVAC step of the environment
     721     15682453 :     controllerProps.ReusePreviousSolutionFlag = true;
     722              :     // Always reset to false by default. Set in CalcSimpleController() on the first controller iteration.
     723     15682453 :     controllerProps.ReuseIntermediateSolutionFlag = false;
     724              :     // By default not converged
     725     15682453 :     IsConvergedFlag = false;
     726              : 
     727              :     // Reset root finder
     728              :     // This is independent of the processing in InitializeRootFinder() performed in Calc() routine.
     729     15682453 :     rootFinders.StatusFlag = DataRootFinder::RootFinderStatus::None;
     730     15682453 :     rootFinders.CurrentMethodType = DataRootFinder::RootFinderMethod::None;
     731              : 
     732     15682453 :     rootFinders.CurrentPoint.DefinedFlag = false;
     733     15682453 :     rootFinders.CurrentPoint.X = 0.0;
     734     15682453 :     rootFinders.CurrentPoint.Y = 0.0;
     735              : 
     736     15682453 :     rootFinders.MinPoint.DefinedFlag = false;
     737     15682453 :     rootFinders.MaxPoint.DefinedFlag = false;
     738     15682453 :     rootFinders.LowerPoint.DefinedFlag = false;
     739     15682453 :     rootFinders.UpperPoint.DefinedFlag = false;
     740     15682453 : }
     741              : 
     742     37576708 : void InitController(EnergyPlusData &state, int const ControlNum, bool &IsConvergedFlag)
     743              : {
     744              : 
     745              :     // SUBROUTINE INFORMATION:
     746              :     //       AUTHOR         Richard J. Liesen
     747              :     //       DATE WRITTEN   July 1998
     748              :     //       MODIFIED       Jan. 2004, Shirey/Raustad (FSEC),
     749              :     //       MODIFIED       Feb. 2006, Dimitri Curtil (LBNL), Moved first call convergence test code to ResetController()
     750              :     //                      Jul. 2016, R. Zhang (LBNL), Applied the water coil supply air temperature sensor offset fault model
     751              : 
     752              :     // PURPOSE OF THIS SUBROUTINE:
     753              :     // This subroutine is for  initializations of the Controller Components.
     754              : 
     755              :     // METHODOLOGY EMPLOYED:
     756              :     // Uses the status flags to trigger events.
     757              : 
     758              :     static constexpr std::string_view RoutineName("InitController");
     759              : 
     760     37576708 :     auto &thisController = state.dataHVACControllers->ControllerProps(ControlNum);
     761     37576708 :     Array1D_bool &MyEnvrnFlag(state.dataHVACControllers->MyEnvrnFlag);
     762     37576708 :     Array1D_bool &MySizeFlag(state.dataHVACControllers->MySizeFlag);
     763     37576708 :     Array1D_bool &MyPlantIndexsFlag(state.dataHVACControllers->MyPlantIndexsFlag);
     764              : 
     765     37576708 :     if (state.dataHVACControllers->InitControllerOneTimeFlag) {
     766              : 
     767          297 :         MyEnvrnFlag.allocate(state.dataHVACControllers->NumControllers);
     768          297 :         MySizeFlag.allocate(state.dataHVACControllers->NumControllers);
     769          297 :         MyPlantIndexsFlag.allocate(state.dataHVACControllers->NumControllers);
     770          297 :         MyEnvrnFlag = true;
     771          297 :         MySizeFlag = true;
     772          297 :         MyPlantIndexsFlag = true;
     773          297 :         state.dataHVACControllers->InitControllerOneTimeFlag = false;
     774              :     }
     775              : 
     776     37576708 :     if (!state.dataGlobal->SysSizingCalc && state.dataHVACControllers->InitControllerSetPointCheckFlag && state.dataHVACGlobal->DoSetPointTest) {
     777              :         // check for missing setpoints
     778         1199 :         for (int ControllerIndex = 1; ControllerIndex <= state.dataHVACControllers->NumControllers; ++ControllerIndex) {
     779          902 :             auto &controllerProps = state.dataHVACControllers->ControllerProps(ControllerIndex);
     780          902 :             int SensedNode = controllerProps.SensedNode;
     781          902 :             switch (controllerProps.ControlVar) {
     782          847 :             case HVACControllers::CtrlVarType::Temperature: { // 'Temperature'
     783          847 :                 if (state.dataLoopNodes->Node(SensedNode).TempSetPoint == DataLoopNode::SensedNodeFlagValue) {
     784           32 :                     if (!state.dataGlobal->AnyEnergyManagementSystemInModel) {
     785            0 :                         ShowSevereError(state,
     786            0 :                                         format("HVACControllers: Missing temperature setpoint for controller type={} Name=\"{}\"",
     787            0 :                                                controllerProps.ControllerType,
     788            0 :                                                controllerProps.ControllerName));
     789            0 :                         ShowContinueError(state, format("Node Referenced (by Controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
     790            0 :                         ShowContinueError(state,
     791              :                                           "  use a Setpoint Manager with Control Variable = \"Temperature\" to establish a setpoint at the "
     792              :                                           "controller sensed node.");
     793            0 :                         state.dataHVACGlobal->SetPointErrorFlag = true;
     794              :                     } else {
     795              :                         // call to check node is actuated by EMS
     796           32 :                         EMSManager::CheckIfNodeSetPointManagedByEMS(
     797           32 :                             state, SensedNode, HVAC::CtrlVarType::Temp, state.dataHVACGlobal->SetPointErrorFlag);
     798           32 :                         if (state.dataHVACGlobal->SetPointErrorFlag) {
     799            0 :                             ShowSevereError(state,
     800            0 :                                             format("HVACControllers: Missing temperature setpoint for controller type={} Name=\"{}\"",
     801            0 :                                                    controllerProps.ControllerType,
     802            0 :                                                    controllerProps.ControllerName));
     803            0 :                             ShowContinueError(state, format("Node Referenced (by Controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
     804            0 :                             ShowContinueError(state,
     805              :                                               "  use a Setpoint Manager with Control Variable = \"Temperature\" to establish a setpoint at "
     806              :                                               "the controller sensed node.");
     807            0 :                             ShowContinueError(state, "Or add EMS Actuator to provide temperature setpoint at this node");
     808              :                         }
     809              :                     }
     810              :                 } else {
     811              :                     //           Warn if humidity setpoint is detected (only for cooling coils) and control variable is TEMP.
     812          815 :                     if (state.dataLoopNodes->Node(SensedNode).HumRatMax != DataLoopNode::SensedNodeFlagValue &&
     813            0 :                         controllerProps.Action == ControllerAction::Reverse) {
     814            0 :                         ShowWarningError(
     815              :                             state,
     816            0 :                             format(
     817              :                                 "HVACControllers: controller type={} Name=\"{}\" has detected a maximum humidity ratio setpoint at the control node.",
     818            0 :                                 controllerProps.ControllerType,
     819            0 :                                 controllerProps.ControllerName));
     820            0 :                         ShowContinueError(state, format("Node referenced (by controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
     821            0 :                         ShowContinueError(state,
     822              :                                           "  set the controller control variable to TemperatureAndHumidityRatio if humidity control is desired.");
     823              :                         //              SetPointErrorFlag = .TRUE.
     824              :                     }
     825              :                 }
     826          847 :             } break;
     827            0 :             case HVACControllers::CtrlVarType::HumidityRatio: { // 'HumidityRatio'
     828            0 :                 controllerProps.HumRatCntrlType = SetPointManager::GetHumidityRatioVariableType(state, SensedNode);
     829            0 :                 if ((thisController.HumRatCntrlType == HVAC::CtrlVarType::HumRat &&
     830            0 :                      state.dataLoopNodes->Node(SensedNode).HumRatSetPoint == DataLoopNode::SensedNodeFlagValue) ||
     831            0 :                     (thisController.HumRatCntrlType == HVAC::CtrlVarType::MaxHumRat &&
     832            0 :                      state.dataLoopNodes->Node(SensedNode).HumRatMax == DataLoopNode::SensedNodeFlagValue)) {
     833            0 :                     if (!state.dataGlobal->AnyEnergyManagementSystemInModel) {
     834            0 :                         ShowSevereError(state,
     835            0 :                                         format("HVACControllers: Missing humidity ratio setpoint for controller type={} Name=\"{}\"",
     836            0 :                                                controllerProps.ControllerType,
     837            0 :                                                controllerProps.ControllerName));
     838            0 :                         ShowContinueError(state, format("Node referenced (by controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
     839            0 :                         ShowContinueError(state,
     840              :                                           "  use a SetpointManager with the field Control Variable = \"MaximumHumidityRatio\" to establish a "
     841              :                                           "setpoint at the controller sensed node.");
     842            0 :                         state.dataHVACGlobal->SetPointErrorFlag = true;
     843              :                     } else {
     844            0 :                         EMSManager::CheckIfNodeSetPointManagedByEMS(
     845            0 :                             state, SensedNode, HVAC::CtrlVarType::HumRat, state.dataHVACGlobal->SetPointErrorFlag);
     846            0 :                         if (state.dataHVACGlobal->SetPointErrorFlag) {
     847            0 :                             ShowSevereError(state,
     848            0 :                                             format("HVACControllers: Missing humidity ratio setpoint for controller type={} Name=\"{}\"",
     849            0 :                                                    controllerProps.ControllerType,
     850            0 :                                                    controllerProps.ControllerName));
     851            0 :                             ShowContinueError(state, format("Node referenced (by controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
     852            0 :                             ShowContinueError(state,
     853              :                                               "  use a SetpointManager with the field Control Variable = \"MaximumHumidityRatio\" to "
     854              :                                               "establish a setpoint at the controller sensed node.");
     855            0 :                             ShowContinueError(state, "Or add EMS Actuator to provide Humidity Ratio setpoint at this node");
     856              :                         }
     857              :                     }
     858              : 
     859            0 :                 } else if (thisController.HumRatCntrlType == HVAC::CtrlVarType::MinHumRat) {
     860            0 :                     ShowSevereError(state,
     861            0 :                                     format("HVACControllers: incorrect humidity ratio setpoint for controller type={} Name=\"{}\"",
     862            0 :                                            controllerProps.ControllerType,
     863            0 :                                            controllerProps.ControllerName));
     864            0 :                     ShowContinueError(state, format("Node referenced (by controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
     865            0 :                     ShowContinueError(state,
     866              :                                       "  use a SetpointManager with the field Control Variable = \"MaximumHumidityRatio\" to establish a "
     867              :                                       "setpoint at the controller sensed node.");
     868            0 :                     state.dataHVACGlobal->SetPointErrorFlag = true;
     869              :                 }
     870            0 :             } break;
     871           55 :             case HVACControllers::CtrlVarType::TemperatureAndHumidityRatio: { // 'TemperatureAndHumidityRatio'
     872           55 :                 if (state.dataLoopNodes->Node(SensedNode).TempSetPoint == DataLoopNode::SensedNodeFlagValue) {
     873            0 :                     if (!state.dataGlobal->AnyEnergyManagementSystemInModel) {
     874            0 :                         ShowSevereError(state,
     875            0 :                                         format("HVACControllers: Missing temperature setpoint for controller type={} Name=\"{}\"",
     876            0 :                                                controllerProps.ControllerType,
     877            0 :                                                controllerProps.ControllerName));
     878            0 :                         ShowContinueError(state, format("Node Referenced (by Controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
     879            0 :                         ShowContinueError(state,
     880              :                                           "  use a Setpoint Manager with Control Variable = \"Temperature\" to establish a setpoint at the "
     881              :                                           "controller sensed node.");
     882            0 :                         state.dataHVACGlobal->SetPointErrorFlag = true;
     883              :                     } else {
     884              :                         // call to check node is actuated by EMS
     885            0 :                         EMSManager::CheckIfNodeSetPointManagedByEMS(
     886            0 :                             state, SensedNode, HVAC::CtrlVarType::Temp, state.dataHVACGlobal->SetPointErrorFlag);
     887            0 :                         if (state.dataHVACGlobal->SetPointErrorFlag) {
     888            0 :                             ShowSevereError(state,
     889            0 :                                             format("HVACControllers: Missing temperature setpoint for controller type={} Name=\"{}\"",
     890            0 :                                                    controllerProps.ControllerType,
     891            0 :                                                    controllerProps.ControllerName));
     892            0 :                             ShowContinueError(state, format("Node Referenced (by Controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
     893            0 :                             ShowContinueError(state,
     894              :                                               "  use a Setpoint Manager with Control Variable = \"Temperature\" to establish a setpoint at "
     895              :                                               "the controller sensed node.");
     896            0 :                             ShowContinueError(state, "Or add EMS Actuator to provide temperature setpoint at this node");
     897              :                         }
     898              :                     }
     899              :                 }
     900           55 :                 if (state.dataLoopNodes->Node(SensedNode).HumRatMax == DataLoopNode::SensedNodeFlagValue) {
     901            0 :                     if (!state.dataGlobal->AnyEnergyManagementSystemInModel) {
     902            0 :                         ShowSevereError(state,
     903            0 :                                         format("HVACControllers: Missing maximum humidity ratio setpoint for controller type={} Name=\"{}\"",
     904            0 :                                                controllerProps.ControllerType,
     905            0 :                                                controllerProps.ControllerName));
     906            0 :                         ShowContinueError(state, format("Node Referenced (by Controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
     907            0 :                         ShowContinueError(state,
     908              :                                           "  use a SetpointManager with the field Control Variable = \"MaximumHumidityRatio\" to establish a "
     909              :                                           "setpoint at the controller sensed node.");
     910            0 :                         state.dataHVACGlobal->SetPointErrorFlag = true;
     911              :                     } else {
     912              :                         // call to check node is actuated by EMS
     913            0 :                         EMSManager::CheckIfNodeSetPointManagedByEMS(
     914            0 :                             state, SensedNode, HVAC::CtrlVarType::MaxHumRat, state.dataHVACGlobal->SetPointErrorFlag);
     915            0 :                         if (state.dataHVACGlobal->SetPointErrorFlag) {
     916            0 :                             ShowSevereError(state,
     917            0 :                                             format("HVACControllers: Missing maximum humidity ratio setpoint for controller type={} Name=\"{}\"",
     918            0 :                                                    controllerProps.ControllerType,
     919            0 :                                                    controllerProps.ControllerName));
     920            0 :                             ShowContinueError(state, format("Node Referenced (by Controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
     921            0 :                             ShowContinueError(state,
     922              :                                               "  use a SetpointManager with the field Control Variable = \"MaximumHumidityRatio\" to "
     923              :                                               "establish a setpoint at the controller sensed node.");
     924            0 :                             ShowContinueError(state, "Or add EMS Actuator to provide maximum Humidity Ratio setpoint at this node");
     925              :                         }
     926              :                     }
     927              :                 }
     928           55 :             } break;
     929            0 :             case HVACControllers::CtrlVarType::Flow: { // 'Flow'
     930            0 :                 if (state.dataLoopNodes->Node(SensedNode).MassFlowRateSetPoint == DataLoopNode::SensedNodeFlagValue) {
     931            0 :                     if (!state.dataGlobal->AnyEnergyManagementSystemInModel) {
     932            0 :                         ShowSevereError(state,
     933            0 :                                         format("HVACControllers: Missing mass flow rate setpoint for controller type={} Name=\"{}\"",
     934            0 :                                                controllerProps.ControllerType,
     935            0 :                                                controllerProps.ControllerName));
     936            0 :                         ShowContinueError(state, format("Node Referenced (in Controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
     937            0 :                         ShowContinueError(state,
     938              :                                           "  use a SetpointManager with the field Control Variable = \"MassFlowRate\" to establish a "
     939              :                                           "setpoint at the controller sensed node.");
     940            0 :                         state.dataHVACGlobal->SetPointErrorFlag = true;
     941              :                     } else {
     942              :                         // call to check node is actuated by EMS
     943            0 :                         EMSManager::CheckIfNodeSetPointManagedByEMS(
     944            0 :                             state, SensedNode, HVAC::CtrlVarType::MassFlowRate, state.dataHVACGlobal->SetPointErrorFlag);
     945            0 :                         if (state.dataHVACGlobal->SetPointErrorFlag) {
     946            0 :                             ShowSevereError(state,
     947            0 :                                             format("HVACControllers: Missing mass flow rate setpoint for controller type={} Name=\"{}\"",
     948            0 :                                                    controllerProps.ControllerType,
     949            0 :                                                    controllerProps.ControllerName));
     950            0 :                             ShowContinueError(state, format("Node Referenced (in Controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
     951            0 :                             ShowContinueError(state,
     952              :                                               "  use a SetpointManager with the field Control Variable = \"MassFlowRate\" to establish a "
     953              :                                               "setpoint at the controller sensed node.");
     954            0 :                             ShowContinueError(state, "Or add EMS Actuator to provide Mass Flow Rate setpoint at this node");
     955              :                         }
     956              :                     }
     957              :                 }
     958            0 :             } break;
     959            0 :             default:
     960            0 :                 break;
     961              :             }
     962              :         }
     963              : 
     964          297 :         state.dataHVACControllers->InitControllerSetPointCheckFlag = false;
     965              :     }
     966              : 
     967     37576708 :     if (allocated(state.dataPlnt->PlantLoop) && MyPlantIndexsFlag(ControlNum)) {
     968         1804 :         PlantUtilities::ScanPlantLoopsForNodeNum(
     969          902 :             state, thisController.ControllerName, thisController.ActuatedNode, thisController.ActuatedNodePlantLoc);
     970          902 :         MyPlantIndexsFlag(ControlNum) = false;
     971              :     }
     972              : 
     973     37576708 :     if (!state.dataGlobal->SysSizingCalc && MySizeFlag(ControlNum)) {
     974              : 
     975          902 :         SizeController(state, ControlNum);
     976              : 
     977              :         // Check to make sure that the Minimum Flow rate is less than the max.
     978          902 :         if (thisController.MaxVolFlowActuated == 0.0) {
     979           66 :             ShowWarningError(state,
     980           66 :                              format("{}: Controller:WaterCoil=\"{}\", Maximum Actuated Flow is zero.", RoutineName, thisController.ControllerName));
     981           33 :             thisController.MinVolFlowActuated = 0.0;
     982          869 :         } else if (thisController.MinVolFlowActuated >= thisController.MaxVolFlowActuated) {
     983            0 :             ShowFatalError(state,
     984            0 :                            format("{}: Controller:WaterCoil=\"{}\", Minimum control flow is > or = Maximum control flow.",
     985              :                                   RoutineName,
     986            0 :                                   thisController.ControllerName));
     987              :         }
     988              : 
     989              :         // Setup root finder after sizing calculation
     990          902 :         switch (thisController.Action) {
     991          431 :         case ControllerAction::NormalAction: {
     992          431 :             RootFinder::SetupRootFinder(state,
     993          431 :                                         state.dataHVACControllers->RootFinders(ControlNum),
     994              :                                         DataRootFinder::Slope::Increasing,
     995              :                                         DataRootFinder::RootFinderMethod::Brent,
     996              :                                         DataPrecisionGlobals::constant_zero,
     997              :                                         1.0e-6,
     998              :                                         thisController.Offset); // Slope type | Method type | TolX: no relative tolerance for X variables |
     999              :                                                                 // ATolX: absolute tolerance for X variables | ATolY: absolute tolerance for
    1000              :                                                                 // Y variables
    1001              : 
    1002          431 :         } break;
    1003          471 :         case ControllerAction::Reverse: {
    1004          471 :             RootFinder::SetupRootFinder(state,
    1005          471 :                                         state.dataHVACControllers->RootFinders(ControlNum),
    1006              :                                         DataRootFinder::Slope::Decreasing,
    1007              :                                         DataRootFinder::RootFinderMethod::Brent,
    1008              :                                         DataPrecisionGlobals::constant_zero,
    1009              :                                         1.0e-6,
    1010              :                                         thisController.Offset); // Slope type | Method type | TolX: no relative tolerance for X variables |
    1011              :                                                                 // ATolX: absolute tolerance for X variables | ATolY: absolute tolerance for
    1012              :                                                                 // Y variables
    1013          471 :         } break;
    1014            0 :         default: {
    1015            0 :             ShowFatalError(state, R"(InitController: Invalid controller action. Valid choices are "Normal" or "Reverse")");
    1016            0 :         } break;
    1017              :         }
    1018              : 
    1019          902 :         MySizeFlag(ControlNum) = false;
    1020              :     }
    1021              : 
    1022              :     // Set the sensed and actuated node numbers
    1023     37576708 :     int ActuatedNode = thisController.ActuatedNode;
    1024     37576708 :     int SensedNode = thisController.SensedNode;
    1025              : 
    1026              :     // Do the Begin Environment initializations
    1027     37576708 :     if (state.dataGlobal->BeginEnvrnFlag && MyEnvrnFlag(ControlNum)) {
    1028              : 
    1029              :         Real64 rho =
    1030         6112 :             state.dataPlnt->PlantLoop(thisController.ActuatedNodePlantLoc.loopNum).glycol->getDensity(state, Constant::CWInitConvTemp, RoutineName);
    1031              : 
    1032         6112 :         thisController.MinActuated = rho * thisController.MinVolFlowActuated;
    1033         6112 :         thisController.MaxActuated = rho * thisController.MaxVolFlowActuated;
    1034              : 
    1035              :         // Turn off scheme to reuse previous solution obtained at last SimAirLoop() call
    1036         6112 :         thisController.ReusePreviousSolutionFlag = false;
    1037              :         // Reset solution trackers
    1038        18336 :         for (auto &e : thisController.SolutionTrackers) {
    1039        12224 :             e.DefinedFlag = false;
    1040        12224 :             e.Mode = ControllerMode::None;
    1041        12224 :             e.ActuatedValue = 0.0;
    1042              :         }
    1043              : 
    1044         6112 :         MyEnvrnFlag(ControlNum) = false;
    1045              :     }
    1046              : 
    1047     37576708 :     if (!state.dataGlobal->BeginEnvrnFlag) {
    1048     37470298 :         MyEnvrnFlag(ControlNum) = true;
    1049              :     }
    1050              : 
    1051     37576708 :     PlantUtilities::SetActuatedBranchFlowRate(state, thisController.NextActuatedValue, ActuatedNode, thisController.ActuatedNodePlantLoc, false);
    1052              : 
    1053              :     // Do the following initializations (every time step): This should be the info from
    1054              :     // the previous components outlets or the node data in this section.
    1055              :     // Load the node data in this section for the component simulation
    1056     37576708 :     IsConvergedFlag = false;
    1057              : 
    1058     37576708 :     switch (thisController.ControlVar) {
    1059     35016410 :     case HVACControllers::CtrlVarType::Temperature: { // 'Temperature'
    1060     35016410 :         thisController.SensedValue = state.dataLoopNodes->Node(SensedNode).Temp;
    1061              :         // Done once per HVAC step
    1062     35016410 :         if (!thisController.IsSetPointDefinedFlag) {
    1063     14583703 :             thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).TempSetPoint;
    1064     14583703 :             thisController.IsSetPointDefinedFlag = true;
    1065              : 
    1066              :             // If there is a fault of water coil SAT sensor
    1067     14584586 :             if (thisController.FaultyCoilSATFlag && (!state.dataGlobal->WarmupFlag) && (!state.dataGlobal->DoingSizing) &&
    1068          883 :                 (!state.dataGlobal->KickOffSimulation)) {
    1069              :                 // calculate the sensor offset using fault information
    1070          883 :                 int FaultIndex = thisController.FaultyCoilSATIndex;
    1071          883 :                 thisController.FaultyCoilSATOffset = state.dataFaultsMgr->FaultsCoilSATSensor(FaultIndex).CalFaultOffsetAct(state);
    1072              :                 // update the SetPointValue
    1073          883 :                 thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).TempSetPoint - thisController.FaultyCoilSATOffset;
    1074              :             }
    1075              :         }
    1076     35016410 :     } break;
    1077      2560298 :     case HVACControllers::CtrlVarType::TemperatureAndHumidityRatio: { // 'TemperatureAndHumidityRatio'
    1078      2560298 :         if (thisController.HumRatCtrlOverride) {
    1079              :             // Humidity ratio control
    1080       278654 :             thisController.SensedValue = state.dataLoopNodes->Node(SensedNode).HumRat;
    1081              :         } else {
    1082              :             // Temperature control
    1083      2281644 :             thisController.SensedValue = state.dataLoopNodes->Node(SensedNode).Temp;
    1084              :         }
    1085      2560298 :         if (!thisController.IsSetPointDefinedFlag) {
    1086       694668 :             if (thisController.HumRatCtrlOverride) {
    1087              :                 // Humidity ratio control
    1088        70183 :                 thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).HumRatMax;
    1089              :             } else {
    1090              :                 // Pure temperature setpoint control strategy
    1091       624485 :                 thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).TempSetPoint;
    1092              :             }
    1093              :             // Finally indicate thate the setpoint has been computed
    1094       694668 :             thisController.IsSetPointDefinedFlag = true;
    1095              :         }
    1096      2560298 :     } break;
    1097            0 :     case HVACControllers::CtrlVarType::HumidityRatio: { // 'HumidityRatio'
    1098            0 :         thisController.SensedValue = state.dataLoopNodes->Node(SensedNode).HumRat;
    1099              :         // Done once per HVAC step
    1100            0 :         if (!thisController.IsSetPointDefinedFlag) {
    1101            0 :             switch (thisController.HumRatCntrlType) {
    1102            0 :             case HVAC::CtrlVarType::MaxHumRat: {
    1103            0 :                 thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).HumRatMax;
    1104            0 :             } break;
    1105            0 :             default: {
    1106            0 :                 thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).HumRatSetPoint;
    1107            0 :             } break;
    1108              :             }
    1109            0 :             thisController.IsSetPointDefinedFlag = true;
    1110              :         }
    1111            0 :     } break;
    1112            0 :     case HVACControllers::CtrlVarType::Flow: { // 'Flow'
    1113            0 :         thisController.SensedValue = state.dataLoopNodes->Node(SensedNode).MassFlowRate;
    1114              :         // Done once per HVAC step
    1115            0 :         if (!thisController.IsSetPointDefinedFlag) {
    1116            0 :             thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).MassFlowRateSetPoint;
    1117            0 :             thisController.IsSetPointDefinedFlag = true;
    1118              :         }
    1119            0 :     } break;
    1120            0 :     default: {
    1121            0 :         ShowFatalError(state, format("Invalid Controller Variable Type={}", ControlVariableTypes(thisController.ControlVar)));
    1122            0 :     } break;
    1123              :     }
    1124              : 
    1125     37576708 :     switch (thisController.ActuatorVar) {
    1126     37576708 :     case HVACControllers::CtrlVarType::Flow: { // 'Flow'
    1127              :         // At the beginning of every time step the value is reset to the User Input
    1128              :         // The interface managers can reset the Max or Min to available values during the time step
    1129              :         // and these will then be the new setpoint limits for the controller to work within.
    1130     37576708 :         thisController.ActuatedValue = state.dataLoopNodes->Node(ActuatedNode).MassFlowRate;
    1131              :         // Compute the currently available min and max bounds for controller.
    1132              :         // Done only once per HVAC step, as it would not make any sense to modify the min/max
    1133              :         // bounds during successive iterations of the root finder.
    1134     37576708 :         if (thisController.NumCalcCalls == 0) {
    1135     15278371 :             thisController.MinAvailActuated = max(state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMinAvail, thisController.MinActuated);
    1136     15278371 :             thisController.MaxAvailActuated = min(state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMaxAvail, thisController.MaxActuated);
    1137              :             // MinActuated is user input for minimum actuated flow, use that value if allowed
    1138              :             // (i.e., reset MinAvailActuated based on Node%MassFlowRateMaxAvail)
    1139     15278371 :             thisController.MinAvailActuated = min(thisController.MinAvailActuated, thisController.MaxAvailActuated);
    1140              :         }
    1141     37576708 :     } break;
    1142            0 :     default: {
    1143            0 :         ShowFatalError(state, format("Invalid Actuator Variable Type={}", ControlVariableTypes(thisController.ActuatorVar)));
    1144            0 :     } break;
    1145              :     }
    1146              : 
    1147              :     // Compute residual for control function using desired setpoint value and current sensed value
    1148              :     // NOTE: The delta sensed value might be wrong if the setpoint has not yet been computed.
    1149              :     //       Make sure not to use it until the setpoint has been computed.
    1150     37576708 :     if (thisController.IsSetPointDefinedFlag) {
    1151     37576708 :         thisController.DeltaSensed = thisController.SensedValue - thisController.SetPointValue;
    1152              :     } else {
    1153            0 :         thisController.DeltaSensed = 0.0;
    1154              :     }
    1155     37576708 : }
    1156              : 
    1157          902 : void SizeController(EnergyPlusData &state, int const ControlNum)
    1158              : {
    1159              : 
    1160              :     // SUBROUTINE INFORMATION:
    1161              :     //       AUTHOR         Fred Buhl
    1162              :     //       DATE WRITTEN   November 2001
    1163              : 
    1164              :     // PURPOSE OF THIS SUBROUTINE:
    1165              :     // This subroutine is for sizing Controller Components for which max flow rates have not been
    1166              :     // specified in the input.
    1167              : 
    1168              :     // METHODOLOGY EMPLOYED:
    1169              :     // Obtains flow rates from the actuated node. Should have been set by the water coils.
    1170              : 
    1171          902 :     auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    1172              : 
    1173          902 :     if (controllerProps.MaxVolFlowActuated == DataSizing::AutoSize) {
    1174        49923 :         for (int WaterCompNum = 1; WaterCompNum <= state.dataSize->SaveNumPlantComps; ++WaterCompNum) {
    1175        49135 :             if (state.dataSize->CompDesWaterFlow(WaterCompNum).SupNode == controllerProps.ActuatedNode) {
    1176          755 :                 controllerProps.MaxVolFlowActuated = state.dataSize->CompDesWaterFlow(WaterCompNum).DesVolFlowRate;
    1177              :             }
    1178              :         }
    1179              : 
    1180          788 :         if (controllerProps.MaxVolFlowActuated < HVAC::SmallWaterVolFlow) {
    1181           33 :             controllerProps.MaxVolFlowActuated = 0.0;
    1182              :         }
    1183          788 :         BaseSizer::reportSizerOutput(state,
    1184              :                                      controllerProps.ControllerType,
    1185              :                                      controllerProps.ControllerName,
    1186              :                                      "Maximum Actuated Flow [m3/s]",
    1187              :                                      controllerProps.MaxVolFlowActuated);
    1188              :     }
    1189              : 
    1190          902 :     if (controllerProps.Offset == DataSizing::AutoSize) {
    1191              :         // 2100 = 0.5 * 4.2 * 1000/1.2 * 1.2 where 0.5 is the ratio of chilled water delta T to supply air delta T,
    1192              :         //   4.2 is the ratio of water density to air density, 1000/1.2 is the ratio of water specific heat to
    1193              :         //   air specific heat, and 1.2 converts the result from air volumetric flow rate to air mass flow rate.
    1194              :         //   The assumption is that a temperature tolerance of 0.001 C is good for an air mass flow rate of 1 kg/s.
    1195              :         //   So we divide .001 by the air mass flow rate estimated from the water volumetric flow rate to come up
    1196              :         //   with a temperature tolerance that won't exceed the loop energy error tolerance (10 W).
    1197              :         // Finally we need to take into account the fact that somebody might change the energy tolerance.
    1198          285 :         controllerProps.Offset =
    1199          285 :             (0.001 / (2100.0 * max(controllerProps.MaxVolFlowActuated, HVAC::SmallWaterVolFlow))) * (DataConvergParams::HVACEnergyToler / 10.0);
    1200              :         // do not let the controller tolerance exceed 1/10 of the loop temperature tolerance.
    1201          285 :         controllerProps.Offset = min(0.1 * DataConvergParams::HVACTemperatureToler, controllerProps.Offset);
    1202          285 :         BaseSizer::reportSizerOutput(
    1203              :             state, controllerProps.ControllerType, controllerProps.ControllerName, "Controller Convergence Tolerance", controllerProps.Offset);
    1204              :     }
    1205          902 : }
    1206              : 
    1207     21963536 : void CalcSimpleController(EnergyPlusData &state,
    1208              :                           int const ControlNum,
    1209              :                           bool const FirstHVACIteration,
    1210              :                           bool &IsConvergedFlag,
    1211              :                           bool &IsUpToDateFlag,
    1212              :                           std::string const &ControllerName // used when errors occur
    1213              : )
    1214              : {
    1215              : 
    1216              :     // SUBROUTINE INFORMATION:
    1217              :     //       AUTHOR         Dimitri Curtil
    1218              :     //       DATE WRITTEN   May 2006
    1219              :     //       MODIFIED       Dimitri Curtil (LBNL), May 2006
    1220              :     //                      - Added IsPointFlagDefinedFlag to control when the setpoiont should be
    1221              :     //                        computed depending on the control strategy. This was needed to
    1222              :     //                        trigger the setpoint calculation for the dual temperature and
    1223              :     //                        humidity ratio control strategy only once the air loop has been
    1224              :     //                        evaluated with the max actuated flow.
    1225              :     //                        See the routine InitController() for more details on the setpoint
    1226              :     //                        calculation.
    1227              :     //       MODIFIED       Dimitri Curtil (LBNL), March 2006
    1228              :     //                      - Added IsUpToDateFlag to detect whether or not the air loop
    1229              :     //                        has been evaluated prior the first iteration, which allows
    1230              :     //                        to use the current node values as the first iterate for the root
    1231              :     //                        finder (for COLD RESTART ONLY).
    1232              :     //       MODIFIED       Dimitri Curtil (LBNL), Feb 2006
    1233              :     //                      - Added mode detection capability.
    1234              :     //                      - Now trying min actuated variable first to
    1235              :     //                        detect min-constrained cases in 1 iteration.
    1236              :     //                      - Trying max actuated variable second.
    1237              :     //                        Checks for max-constrained here instead of in
    1238              :     //                        NormActuatedCalc mode.
    1239              :     //                      - Checking for inactive mode as soon as min and max
    1240              :     //                        support points are known instead of in NormActuatedCalc
    1241              :     //                        mode.
    1242              : 
    1243              :     // SUBROUTINE ARGUMENT DEFINITIONS:
    1244              :     // Set to TRUE if current controller is converged; FALSE if more iteration are needed.
    1245              :     // Note that an error in the root finding process can be mapped onto IsConvergedFlag=TRUE
    1246              :     // to avoid continue iterating.
    1247              :     // TRUE if air loop is up-to-date meaning that the current node values are consistent (air loop evaluated)
    1248              :     // Only used within the Calc routines
    1249              : 
    1250     21963536 :     auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    1251     21963536 :     auto &rootFinders = state.dataHVACControllers->RootFinders(ControlNum);
    1252              : 
    1253              :     // Increment counter
    1254     21963536 :     ++controllerProps.NumCalcCalls;
    1255              : 
    1256              :     // Check to see if the component is running; if not converged and return.  This check will be done
    1257              :     // by looking at the component mass flow rate at the sensed node.
    1258     21963536 :     if (state.dataLoopNodes->Node(controllerProps.SensedNode).MassFlowRate == 0.0) {
    1259       884114 :         ExitCalcController(state, ControlNum, DataPrecisionGlobals::constant_zero, ControllerMode::Off, IsConvergedFlag, IsUpToDateFlag);
    1260       884114 :         return;
    1261              :     }
    1262              : 
    1263              :     // Initialize root finder
    1264     21079422 :     if (controllerProps.NumCalcCalls == 1) {
    1265              :         // Set min/max boundaries for root finder on first iteration
    1266      9581450 :         RootFinder::InitializeRootFinder(state, rootFinders, controllerProps.MinAvailActuated,
    1267              :                                          controllerProps.MaxAvailActuated); // XMin | XMax
    1268              : 
    1269              :         // Only allow to reuse initial evaluation if the air loop is up-to-date.
    1270              :         // Set in SolveAirLoopControllers()
    1271              :         // Only reuse initial evaluation if setpoint is already available for the current controller
    1272              :         // Note that in the case of dual temperature and humidity ratio control strategy since the
    1273              :         // setpoint at a later iteration, the initial solution cannot be reused.
    1274              :         // Make sure that the initial candidate value lies within range
    1275     19040231 :         controllerProps.ReuseIntermediateSolutionFlag = IsUpToDateFlag && controllerProps.IsSetPointDefinedFlag &&
    1276      9458781 :                                                         RootFinder::CheckRootFinderCandidate(rootFinders, controllerProps.ActuatedValue);
    1277              : 
    1278      9581450 :         if (controllerProps.ReuseIntermediateSolutionFlag) {
    1279              : 
    1280              :             // Reuse intermediate solution obtained with previous controller for the current HVAC step
    1281              :             // and fire root finder to get next root candidate
    1282      9458781 :             FindRootSimpleController(state, ControlNum, FirstHVACIteration, IsConvergedFlag, IsUpToDateFlag, ControllerName);
    1283              : 
    1284              :         } else {
    1285              :             // Always start with min point by default
    1286       122669 :             controllerProps.NextActuatedValue = rootFinders.MinPoint.X;
    1287              :         }
    1288              : 
    1289              :         // Process current iterate and compute next candidate if needed
    1290              :         // We assume that after the first controller iteration:
    1291              :         // - the setpoint is defined
    1292              :         // - the min and max available bounds are defined
    1293              :         // NOTE: Not explicitly checked but the air mass flow rate must remain constant across successive
    1294              :         //       controller iterations to ensure that the root finder converges.
    1295              :     } else {
    1296              :         // Check that the setpoint is defined
    1297     11497972 :         if (!controllerProps.IsSetPointDefinedFlag) {
    1298            0 :             ShowSevereError(state, format("CalcSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
    1299            0 :             ShowContinueError(state, format(" Controller name=\"{}\"", ControllerName));
    1300            0 :             ShowContinueError(state, " Setpoint is not available/defined.");
    1301            0 :             ShowFatalError(state, "Preceding error causes program termination.");
    1302              :         }
    1303              :         // Monitor invariants across successive controller iterations
    1304              :         // - min bound
    1305              :         // - max bound
    1306     11497972 :         if (rootFinders.MinPoint.X != controllerProps.MinAvailActuated) {
    1307            0 :             ShowSevereError(state, format("CalcSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
    1308            0 :             ShowContinueError(state, format(" Controller name=\"{}\"", ControllerName));
    1309            0 :             ShowContinueError(state, " Minimum bound must remain invariant during successive iterations.");
    1310            0 :             ShowContinueError(state, format(" Minimum root finder point={:.{}T}", rootFinders.MinPoint.X, NumSigDigits));
    1311            0 :             ShowContinueError(state, format(" Minimum avail actuated={:.{}T}", controllerProps.MinAvailActuated, NumSigDigits));
    1312            0 :             ShowFatalError(state, "Preceding error causes program termination.");
    1313              :         }
    1314     11497972 :         if (rootFinders.MaxPoint.X != controllerProps.MaxAvailActuated) {
    1315            0 :             ShowSevereError(state, format("CalcSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
    1316            0 :             ShowContinueError(state, format(" Controller name=\"{}\"", ControllerName));
    1317            0 :             ShowContinueError(state, " Maximum bound must remain invariant during successive iterations.");
    1318            0 :             ShowContinueError(state, format(" Maximum root finder point={:.{}T}", rootFinders.MaxPoint.X, NumSigDigits));
    1319            0 :             ShowContinueError(state, format(" Maximum avail actuated={:.{}T}", controllerProps.MaxAvailActuated, NumSigDigits));
    1320            0 :             ShowFatalError(state, "Preceding error causes program termination.");
    1321              :         }
    1322              : 
    1323              :         // Updates root finder with current iterate and computes next one if needed
    1324     11497972 :         FindRootSimpleController(state, ControlNum, FirstHVACIteration, IsConvergedFlag, IsUpToDateFlag, ControllerName);
    1325              :     }
    1326              : }
    1327              : 
    1328     20956753 : void FindRootSimpleController(EnergyPlusData &state,
    1329              :                               int const ControlNum,
    1330              :                               bool const FirstHVACIteration,
    1331              :                               bool &IsConvergedFlag,
    1332              :                               bool &IsUpToDateFlag,
    1333              :                               std::string const &ControllerName // used when errors occur
    1334              : )
    1335              : {
    1336              : 
    1337              :     // SUBROUTINE INFORMATION:
    1338              :     //       AUTHOR         Dimitri Curtil (LBNL)
    1339              :     //       DATE WRITTEN   March 2006
    1340              : 
    1341              :     // PURPOSE OF THIS SUBROUTINE:
    1342              :     // New routine to fire the root finder using the current actuated and sensed values.
    1343              :     // - Updates IsConvergedFlag depending ou iteration status.
    1344              :     // - Sets next actuated value to try in ControllerProps(ControlNum)%NextActuatedValue
    1345              : 
    1346              :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    1347              :     bool IsDoneFlag; // TRUE if root finder needs to continue iterating, FALSE otherwise.
    1348              :     ControllerMode PreviousSolutionMode;
    1349              :     Real64 PreviousSolutionValue;
    1350              : 
    1351     20956753 :     auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    1352     20956753 :     auto &rootFinders = state.dataHVACControllers->RootFinders(ControlNum);
    1353              : 
    1354              :     // Update root finder with latest solution point
    1355              :     // Check for unconstrained/constrained convergence
    1356              :     // Compute next candidate if not converged yet.
    1357     20956753 :     RootFinder::IterateRootFinder(state,
    1358              :                                   rootFinders,
    1359              :                                   controllerProps.ActuatedValue,
    1360              :                                   controllerProps.DeltaSensed,
    1361              :                                   IsDoneFlag); // root finder's data | X | Y | not used
    1362              : 
    1363              :     // Process root finder if converged or error
    1364              :     // Map root finder status onto controller mode
    1365     20956753 :     switch (rootFinders.StatusFlag) {
    1366     11375320 :     case DataRootFinder::RootFinderStatus::None:
    1367              :     case DataRootFinder::RootFinderStatus::WarningNonMonotonic:
    1368              :     case DataRootFinder::RootFinderStatus::WarningSingular: {
    1369              :         // We need to keep iterating...
    1370              :         int PreviousSolutionIndex;
    1371     11375320 :         IsConvergedFlag = false;
    1372              : 
    1373     11375320 :         if (FirstHVACIteration) {
    1374      4390245 :             PreviousSolutionIndex = 1;
    1375              :         } else {
    1376      6985075 :             PreviousSolutionIndex = 2;
    1377              :         }
    1378              : 
    1379     11375320 :         bool PreviousSolutionDefinedFlag = controllerProps.SolutionTrackers(PreviousSolutionIndex).DefinedFlag;
    1380     11375320 :         PreviousSolutionMode = controllerProps.SolutionTrackers(PreviousSolutionIndex).Mode;
    1381     11375320 :         PreviousSolutionValue = controllerProps.SolutionTrackers(PreviousSolutionIndex).ActuatedValue;
    1382              : 
    1383              :         // Attempt to use root at previous HVAC step in place of the candidate produced by the
    1384              :         // root finder.
    1385              :         // Set in InitController() depending on controller mode at previous HVAC step iteration
    1386              :         // Only attempted during bracketing phase of root finder.
    1387              :         // Check that a previous solution is available
    1388              :         // Make sure that mode of previous solution was active
    1389              :         // Make sure that proposed candidate does not conflict with current min/max range and lower/upper brackets
    1390     16380754 :         bool ReusePreviousSolutionFlag = controllerProps.ReusePreviousSolutionFlag &&
    1391      5005434 :                                          (rootFinders.CurrentMethodType == DataRootFinder::RootFinderMethod::Bracket) &&
    1392     19536194 :                                          PreviousSolutionDefinedFlag && (PreviousSolutionMode == ControllerMode::Active) &&
    1393      3155440 :                                          RootFinder::CheckRootFinderCandidate(rootFinders, PreviousSolutionValue);
    1394              : 
    1395     11375320 :         if (ReusePreviousSolutionFlag) {
    1396              :             // Try to reuse saved solution from previous call to SolveAirLoopControllers()
    1397              :             // instead of candidate proposed by the root finder
    1398      3155440 :             controllerProps.NextActuatedValue = PreviousSolutionValue;
    1399              : 
    1400              :             // Turn off flag since we can only use the previous solution once per HVAC iteration
    1401      3155440 :             controllerProps.ReusePreviousSolutionFlag = false;
    1402              :         } else {
    1403              :             // By default, use candidate value computed by root finder
    1404      8219880 :             controllerProps.NextActuatedValue = rootFinders.XCandidate;
    1405              :         }
    1406              : 
    1407     11375320 :     } break;
    1408      4177876 :     case DataRootFinder::RootFinderStatus::OK:
    1409              :     case DataRootFinder::RootFinderStatus::OKRoundOff: {
    1410              :         // Indicate convergence with base value (used to obtain DeltaSensed!)
    1411      4177876 :         ExitCalcController(state, ControlNum, rootFinders.XCandidate, ControllerMode::Active, IsConvergedFlag, IsUpToDateFlag);
    1412      4177876 :     } break;
    1413      4406342 :     case DataRootFinder::RootFinderStatus::OKMin: {
    1414              :         // Indicate convergence with min value
    1415              :         // Should be the same as ControllerProps(ControlNum)%MinAvailActuated
    1416      4406342 :         ExitCalcController(state, ControlNum, rootFinders.MinPoint.X, ControllerMode::MinActive, IsConvergedFlag, IsUpToDateFlag);
    1417      4406342 :     } break;
    1418       594566 :     case DataRootFinder::RootFinderStatus::OKMax: {
    1419              :         // Indicate convergence with max value
    1420              :         // Should be the same as ControllerProps(ControlNum)%MaxAvailActuated
    1421       594566 :         ExitCalcController(state, ControlNum, rootFinders.MaxPoint.X, ControllerMode::MaxActive, IsConvergedFlag, IsUpToDateFlag);
    1422              : 
    1423       594566 :     } break;
    1424       402461 :     case DataRootFinder::RootFinderStatus::ErrorSingular: {
    1425              :         // Indicate inactive mode with min actuated value
    1426              :         // NOTE: Original code returned Node(ActuatedNode)%MassFlowRateMinAvail
    1427              :         //       This was not portable in case the actuated variable was NOT a mass flow rate!
    1428              :         //       Replaced   Node(ActuatedNode)%MassFlowRateMinAvail
    1429              :         //       with       rootFinders%MinPoint%X
    1430              :         //       which is the same as (see SUBROUTINE InitController)
    1431              :         //                  ControllerProps(ControlNum)%MinAvailActuated
    1432       402461 :         ExitCalcController(state, ControlNum, rootFinders.MinPoint.X, ControllerMode::Inactive, IsConvergedFlag, IsUpToDateFlag);
    1433              : 
    1434              :         // Abnormal case: should never happen
    1435       402461 :     } break;
    1436            0 :     case DataRootFinder::RootFinderStatus::ErrorRange: {
    1437            0 :         ShowSevereError(state, format("FindRootSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
    1438            0 :         ShowContinueError(state, format(" Controller name=\"{}\"", ControllerName));
    1439            0 :         ShowContinueError(state,
    1440            0 :                           format(" Root candidate x={:.{}T} does not lie within the min/max bounds.", controllerProps.ActuatedValue, NumSigDigits));
    1441            0 :         ShowContinueError(state, format(" Min bound is x={:.{}T}", rootFinders.MinPoint.X, NumSigDigits));
    1442            0 :         ShowContinueError(state, format(" Max bound is x={:.{}T}", rootFinders.MaxPoint.X, NumSigDigits));
    1443            0 :         ShowFatalError(state, "Preceding error causes program termination.");
    1444              : 
    1445              :         // Abnormal case: should never happen
    1446            0 :     } break;
    1447            0 :     case DataRootFinder::RootFinderStatus::ErrorBracket: {
    1448            0 :         ShowSevereError(state, format("FindRootSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
    1449            0 :         ShowContinueError(state, format(" Controller name={}", controllerProps.ControllerName));
    1450            0 :         ShowContinueError(state, fmt::format(" Controller action={}", state.dataHVACCtrl->ActionTypes[static_cast<int>(controllerProps.Action)]));
    1451            0 :         ShowContinueError(
    1452            0 :             state, format(" Root candidate x={:.{}T} does not lie within the lower/upper brackets.", controllerProps.ActuatedValue, NumSigDigits));
    1453            0 :         if (rootFinders.LowerPoint.DefinedFlag) {
    1454            0 :             ShowContinueError(state, format(" Lower bracket is x={:.{}T}", rootFinders.LowerPoint.X, NumSigDigits));
    1455              :         }
    1456            0 :         if (rootFinders.UpperPoint.DefinedFlag) {
    1457            0 :             ShowContinueError(state, format(" Upper bracket is x={:.{}T}", rootFinders.UpperPoint.X, NumSigDigits));
    1458              :         }
    1459            0 :         ShowFatalError(state, "Preceding error causes program termination.");
    1460              : 
    1461              :         // Detected control function with wrong action between the min and max points.
    1462              :         // Should never happen: probably indicative of some serious problems in IDFs
    1463              :         // NOTE: This approach is more robust and consistent than what was done in version 1.3.
    1464              :         //       Indeed, such a function with the wrong action characteristic would have silently returned
    1465              :         //       either of the following values depending on the specified action:
    1466              :         //       - NORMAL ACTION:
    1467              :         //         - If y(xMin) > ySetPoint && y(xMax) < y(xMin), then  x = xMin
    1468              :         //         - If y(xMin) < ySetPoint && y(xMax) < y(xMin), then  x = xMax
    1469              :         //       - REVERSE ACTION:
    1470              :         //         - If y(xMin) < ySetPoint && y(xMax) > y(xMin), then  x = xMin
    1471              :         //         - If y(xMin) > ySetPoint && y(xMax) > y(xMin), then  x = xMax
    1472            0 :     } break;
    1473          188 :     case DataRootFinder::RootFinderStatus::ErrorSlope: {
    1474          188 :         if (!state.dataGlobal->WarmupFlag && controllerProps.BadActionErrCount == 0) {
    1475            3 :             ++controllerProps.BadActionErrCount;
    1476            3 :             ShowSevereError(state, format("FindRootSimpleController: Controller error for controller = \"{}\"", ControllerName));
    1477            6 :             ShowContinueErrorTimeStamp(state, "");
    1478            3 :             ShowContinueError(state,
    1479            3 :                               fmt::format("  Controller function is inconsistent with user specified controller action = {}",
    1480            3 :                                           state.dataHVACCtrl->ActionTypes[static_cast<int>(controllerProps.Action)]));
    1481            6 :             ShowContinueError(state, "  Actuator will be set to maximum action");
    1482            3 :             ShowContinueError(state, format("Controller control type={}", ControlVariableTypes(controllerProps.ControlVar)));
    1483            3 :             if (controllerProps.ControlVar == CtrlVarType::Temperature) {
    1484            3 :                 ShowContinueError(state, format("Controller temperature setpoint = {:.2T} [C]", controllerProps.SetPointValue));
    1485            3 :                 ShowContinueError(state, format("Controller sensed temperature = {:.2T} [C]", controllerProps.SensedValue));
    1486            0 :             } else if (controllerProps.ControlVar == CtrlVarType::HumidityRatio) {
    1487            0 :                 ShowContinueError(state, format("Controller humidity ratio setpoint = {:.2T} [kgWater/kgDryAir]", controllerProps.SetPointValue));
    1488            0 :                 ShowContinueError(state, format("Controller sensed humidity ratio = {:.2T} [kgWater/kgDryAir]", controllerProps.SensedValue));
    1489            0 :             } else if (controllerProps.ControlVar == CtrlVarType::TemperatureAndHumidityRatio) {
    1490            0 :                 if (controllerProps.HumRatCtrlOverride) {
    1491            0 :                     ShowContinueError(state, "Humidity control is active.");
    1492            0 :                     ShowContinueError(state, format("Controller humidity ratio setpoint = {:.2T} [kgWater/kgDryAir]", controllerProps.SetPointValue));
    1493            0 :                     ShowContinueError(state, format("Controller sensed humidity ratio = {:.2T} [kgWater/kgDryAir]", controllerProps.SensedValue));
    1494            0 :                     ShowContinueError(state,
    1495            0 :                                       format("Controller humidity ratio setpoint dew-point temperature = {:.2T} [C]",
    1496            0 :                                              Psychrometrics::PsyTdpFnWPb(state, controllerProps.SetPointValue, state.dataEnvrn->OutBaroPress)));
    1497            0 :                     ShowContinueError(
    1498              :                         state,
    1499            0 :                         format("Controller temperature setpoint = {:.2T} [C]", state.dataLoopNodes->Node(controllerProps.SensedNode).TempSetPoint));
    1500            0 :                     ShowContinueError(
    1501            0 :                         state, format("Controller sensed temperature = {:.2T} [C]", state.dataLoopNodes->Node(controllerProps.SensedNode).Temp));
    1502              :                 } else {
    1503            0 :                     ShowContinueError(state, format("Controller temperature setpoint = {:.2T} [C]", controllerProps.SetPointValue));
    1504            0 :                     ShowContinueError(state, format("Controller sensed temperature = {:.2T} [C]", controllerProps.SensedValue));
    1505              :                 }
    1506            0 :             } else if (controllerProps.ControlVar == CtrlVarType::Flow) {
    1507            0 :                 ShowContinueError(state, format("Controller mass flow rate setpoint = {:.2T} [kg/s]", controllerProps.SetPointValue));
    1508            0 :                 ShowContinueError(state, format("Controller sensed mass flow rate = {:.2T} [kg/s]", controllerProps.SensedValue));
    1509              :             } else {
    1510              :                 // bad control variable input checked in input routine
    1511              :             }
    1512            3 :             if (controllerProps.ActuatorVar == CtrlVarType::Flow) {
    1513            3 :                 ShowContinueError(state, format("Controller actuator mass flow rate set to {:.2T} [kg/s]", controllerProps.MaxAvailActuated));
    1514            3 :                 if (controllerProps.ControlVar == CtrlVarType::Temperature ||
    1515            0 :                     controllerProps.ControlVar == CtrlVarType::TemperatureAndHumidityRatio) {
    1516            6 :                     ShowContinueError(
    1517            6 :                         state, format("Controller actuator temperature = {:.2T} [C]", state.dataLoopNodes->Node(controllerProps.ActuatedNode).Temp));
    1518            3 :                     if (controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterCooling ||
    1519            3 :                         controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterDetailedFlatCooling) {
    1520            0 :                         if (controllerProps.HumRatCtrlOverride) {
    1521            0 :                             ShowContinueError(state,
    1522              :                                               "The entering chilled water temperature (controller actuator temperature) should be below the humidity "
    1523              :                                               "ratio setpoint dew-point temperature.");
    1524              :                         } else {
    1525            0 :                             ShowContinueError(
    1526              :                                 state,
    1527              :                                 "The entering chilled water temperature (controller actuator temperature) should be below the setpoint temperature.");
    1528              :                         }
    1529            3 :                     } else if (controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterSimpleHeating) {
    1530            9 :                         ShowContinueError(
    1531              :                             state, "The entering hot water temperature (controller actuator temperature) should be above the setpoint temperature");
    1532              :                     }
    1533              :                 }
    1534              :             } else {
    1535              :                 // bad actuator variable input checked in input routine
    1536              :             }
    1537          185 :         } else if (!state.dataGlobal->WarmupFlag) {
    1538            0 :             ++controllerProps.BadActionErrCount;
    1539            0 :             ShowRecurringSevereErrorAtEnd(state,
    1540            0 :                                           "FindRootSimpleController: Previous controller action error continues for controller = " + ControllerName,
    1541            0 :                                           controllerProps.BadActionErrIndex);
    1542              :         } else {
    1543              :             // do nothing
    1544              :         }
    1545              :         // Indicate convergence with min value
    1546              :         // Should be the same as ControllerProps(ControlNum)%MaxAvailActuated
    1547          188 :         ExitCalcController(state, ControlNum, rootFinders.MaxPoint.X, ControllerMode::MaxActive, IsConvergedFlag, IsUpToDateFlag);
    1548          188 :     } break;
    1549            0 :     default: {
    1550              :         // Should never happen
    1551            0 :         ShowSevereError(state, format("FindRootSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
    1552            0 :         ShowContinueError(state, format(" Controller name={}", ControllerName));
    1553            0 :         ShowContinueError(state, format(" Unrecognized root finder status flag={}", rootFinders.StatusFlag));
    1554            0 :         ShowFatalError(state, "Preceding error causes program termination.");
    1555            0 :     } break;
    1556              :     }
    1557     20956753 : }
    1558              : 
    1559     15612270 : void CheckSimpleController(EnergyPlusData &state, int const ControlNum, bool &IsConvergedFlag)
    1560              : {
    1561              : 
    1562              :     // SUBROUTINE INFORMATION:
    1563              :     //       AUTHOR         Dimitri Curtil (LBNL)
    1564              :     //       DATE WRITTEN   Feb 2006
    1565              : 
    1566              :     // PURPOSE OF THIS SUBROUTINE:
    1567              :     // New routine used to detect whether controller can be considered converged
    1568              :     // depending on its mode of operation.
    1569              :     // Used after all controllers on an air loop have been solved in order
    1570              :     // to make sure that final air loop state still represents a converged
    1571              :     // state.
    1572              :     // PRECONDITION: Setpoint must be known. See ControllerProps%IsSetPointDefinedFlag
    1573              : 
    1574     15612270 :     auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    1575     15612270 :     auto &rootFinders = state.dataHVACControllers->RootFinders(ControlNum);
    1576              : 
    1577              :     // Default initialization: assuming no convergence unless detected in the following code!
    1578     15612270 :     IsConvergedFlag = false;
    1579              : 
    1580     15612270 :     switch (controllerProps.Mode) {
    1581      1665982 :     case ControllerMode::Off: {
    1582              :         // Check whether the component is running
    1583              :         // This check is performed by looking at the component mass flow rate at the sensed node.
    1584              :         // Since the components have been simulated before getting here, if they are zero they should be OFF.
    1585      1665982 :         if (state.dataLoopNodes->Node(controllerProps.SensedNode).MassFlowRate == 0.0) {
    1586      1665966 :             if (controllerProps.ActuatedValue == 0.0) {
    1587      1665966 :                 IsConvergedFlag = true;
    1588      1665966 :                 return;
    1589              :             }
    1590              :         }
    1591           16 :     } break;
    1592       617346 :     case ControllerMode::Inactive: {
    1593              :         // Controller component NOT available (ie, inactive)
    1594              :         // Make sure that the actuated variable is still equal to the node min avail
    1595              :         // NOTE: Replaced Node(ActuatedNode)%MassFlowRateMinAvail         in release 1.3
    1596              :         //       with     ControllerProps(ControlNum)%MinAvailActuated    in release 1.4
    1597       617346 :         if (controllerProps.ActuatedValue == controllerProps.MinAvailActuated) {
    1598       617346 :             IsConvergedFlag = true;
    1599       617346 :             return;
    1600              :         }
    1601            0 :     } break;
    1602      6287783 :     case ControllerMode::MinActive: {
    1603              :         // Check for min constrained convergence
    1604      6287783 :         if (CheckMinActiveController(state, ControlNum)) {
    1605      5447053 :             IsConvergedFlag = true;
    1606      5447053 :             return;
    1607              :         }
    1608              :         // Check for unconstrained convergence assuming that there is more than one controller controlling
    1609              :         // the same sensed node and that the other controller was able to meet the setpoint although this one
    1610              :         // was min-constrained.
    1611       840730 :         if (RootFinder::CheckRootFinderConvergence(rootFinders, controllerProps.DeltaSensed)) {
    1612              :             // Indicate convergence with base value (used to compute DeltaSensed!)
    1613        81683 :             IsConvergedFlag = true;
    1614        81683 :             return;
    1615              :         }
    1616       759047 :     } break;
    1617       942610 :     case ControllerMode::MaxActive: {
    1618              :         // Check for max constrained convergence
    1619       942610 :         if (CheckMaxActiveController(state, ControlNum)) {
    1620       821754 :             IsConvergedFlag = true;
    1621       821754 :             return;
    1622              :         }
    1623              :         // Check for unconstrained convergence assuming that there is more than one controller controlling
    1624              :         // the same sensed node and that the other controller was able to meet the setpoint although this one
    1625              :         // was max-constrained.
    1626       120856 :         if (RootFinder::CheckRootFinderConvergence(rootFinders, controllerProps.DeltaSensed)) {
    1627              :             // Indicate convergence with base value (used to compute DeltaSensed!)
    1628           92 :             IsConvergedFlag = true;
    1629           92 :             return;
    1630              :         }
    1631       120764 :     } break;
    1632      6098532 :     case ControllerMode::Active: {
    1633              :         // Check min constraint on actuated variable
    1634      6098532 :         if (controllerProps.ActuatedValue < controllerProps.MinAvailActuated) {
    1635         5123 :             IsConvergedFlag = false;
    1636         5123 :             return;
    1637              :         }
    1638              :         // Check max constraint on actuated variable
    1639      6093409 :         if (controllerProps.ActuatedValue > controllerProps.MaxAvailActuated) {
    1640            0 :             IsConvergedFlag = false;
    1641            0 :             return;
    1642              :         }
    1643              : 
    1644              :         // Check for unconstrained convergence
    1645              :         // Equivalent to:
    1646              :         // IF ((ABS(ControllerProps(ControlNum)%DeltaSensed) .LE. ControllerProps(ControlNum)%Offset)) THEN
    1647              :         // NOTE: If setpoint has changed since last call, then the following test will most likely fail.
    1648      6093409 :         if (RootFinder::CheckRootFinderConvergence(rootFinders, controllerProps.DeltaSensed)) {
    1649              :             // Indicate convergence with base value (used to compute DeltaSensed!)
    1650      4724190 :             IsConvergedFlag = true;
    1651      4724190 :             return;
    1652              :         }
    1653              :         // Check for min constrained convergence
    1654      1369219 :         if (CheckMinActiveController(state, ControlNum)) {
    1655        57994 :             IsConvergedFlag = true;
    1656        57994 :             return;
    1657              :         }
    1658              :         // Check for max constrained convergence
    1659      1311225 :         if (CheckMaxActiveController(state, ControlNum)) {
    1660        19770 :             IsConvergedFlag = true;
    1661        19770 :             return;
    1662              :         }
    1663      1291455 :     } break;
    1664           17 :     default: {
    1665              :         // Can only happen if controller is not converged after MaxIter in SolveAirLoopControllers()
    1666              :         // which will produce ControllerProps(ControlNum)%Mode = iModeNone
    1667           17 :         IsConvergedFlag = false;
    1668           17 :     } break;
    1669              :     }
    1670              : }
    1671              : 
    1672      7657002 : bool CheckMinActiveController(EnergyPlusData &state, int const ControlNum)
    1673              : {
    1674              :     // FUNCTION INFORMATION:
    1675              :     //       AUTHOR         Dimitri Curtil
    1676              :     //       DATE WRITTEN   May 2006
    1677              : 
    1678              :     // PURPOSE OF THIS FUNCTION:
    1679              :     // Returns true if controller is min-constrained. false otherwise.
    1680              : 
    1681      7657002 :     auto const &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    1682              : 
    1683              :     // Check that actuated value is the min avail actuated value
    1684      7657002 :     if (controllerProps.ActuatedValue != controllerProps.MinAvailActuated) {
    1685      1092627 :         return false;
    1686              :     }
    1687              : 
    1688      6564375 :     switch (controllerProps.Action) {
    1689      5201329 :     case ControllerAction::NormalAction: { // "NORMAL"
    1690              :         // Check for min constrained convergence
    1691      5201329 :         if (controllerProps.SetPointValue <= controllerProps.SensedValue) {
    1692      4203623 :             return true;
    1693              :         }
    1694       997706 :     } break;
    1695      1363046 :     case ControllerAction::Reverse: { // "REVERSE"
    1696              :         // Check for min constrained convergence
    1697      1363046 :         if (controllerProps.SetPointValue >= controllerProps.SensedValue) {
    1698      1301424 :             return true;
    1699              :         }
    1700        61622 :     } break;
    1701            0 :     default: {
    1702              :         // Should never happen
    1703            0 :         ShowSevereError(state, format("CheckMinActiveController: Invalid controller action during {}.", CreateHVACStepFullString(state)));
    1704            0 :         ShowContinueError(state, format("CheckMinActiveController: Controller name={}", controllerProps.ControllerName));
    1705            0 :         ShowContinueError(state, R"(CheckMinActiveController: Valid choices are "NORMAL" or "REVERSE")");
    1706            0 :         ShowFatalError(state, "CheckMinActiveController: Preceding error causes program termination.");
    1707            0 :     } break;
    1708              :     }
    1709              : 
    1710      1059328 :     return false;
    1711              : }
    1712              : 
    1713      2253835 : bool CheckMaxActiveController(EnergyPlusData &state, int const ControlNum)
    1714              : {
    1715              :     // FUNCTION INFORMATION:
    1716              :     //       AUTHOR         Dimitri Curtil
    1717              :     //       DATE WRITTEN   May 2006
    1718              : 
    1719              :     // PURPOSE OF THIS FUNCTION:
    1720              :     // Returns true if controller is max-constrained. false otherwise.
    1721              : 
    1722      2253835 :     auto &ControllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    1723              : 
    1724              :     // Check that actuated value is the max avail actuated value
    1725      2253835 :     if (ControllerProps.ActuatedValue != ControllerProps.MaxAvailActuated) {
    1726      1287259 :         return false;
    1727              :     }
    1728              : 
    1729       966576 :     switch (ControllerProps.Action) {
    1730       332531 :     case ControllerAction::NormalAction: { // "NORMAL"
    1731              :         // Check for max constrained convergence
    1732       332531 :         if (ControllerProps.SetPointValue >= ControllerProps.SensedValue) {
    1733       327655 :             return true;
    1734              :         }
    1735         4876 :     } break;
    1736       634045 :     case ControllerAction::Reverse: { // "REVERSE"
    1737              :         // Check for max constrained convergence
    1738       634045 :         if (ControllerProps.SetPointValue <= ControllerProps.SensedValue) {
    1739       513869 :             return true;
    1740              :         }
    1741       120176 :     } break;
    1742            0 :     default: {
    1743              :         // Should never happen
    1744            0 :         ShowSevereError(state, format("CheckMaxActiveController: Invalid controller action during {}.", CreateHVACStepFullString(state)));
    1745            0 :         ShowContinueError(state, format("CheckMaxActiveController: Controller name={}", ControllerProps.ControllerName));
    1746            0 :         ShowContinueError(state, R"(CheckMaxActiveController: Valid choices are "NORMAL" or "REVERSE")");
    1747            0 :         ShowFatalError(state, "CheckMaxActiveController: Preceding error causes program termination.");
    1748            0 :     } break;
    1749              :     }
    1750              : 
    1751       125052 :     return false;
    1752              : }
    1753              : 
    1754     15612270 : void SaveSimpleController(EnergyPlusData &state, int const ControlNum, bool const FirstHVACIteration, bool const IsConvergedFlag)
    1755              : {
    1756              : 
    1757              :     // SUBROUTINE INFORMATION:
    1758              :     //       AUTHOR         Dimitri Curtil
    1759              :     //       DATE WRITTEN   April 2006
    1760              : 
    1761              :     // PURPOSE OF THIS SUBROUTINE:
    1762              :     // Updates solution trackers if simple controller is converged.
    1763              : 
    1764     15612270 :     auto &ControllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    1765              : 
    1766              :     // Save solution and mode for next call only if converged
    1767     15612270 :     if (IsConvergedFlag) {
    1768     13435848 :         int PreviousSolutionIndex = FirstHVACIteration ? 1 : 2;
    1769              : 
    1770     13435848 :         if (ControllerProps.Mode == ControllerMode::Active) {
    1771      4801954 :             ControllerProps.SolutionTrackers(PreviousSolutionIndex).DefinedFlag = true;
    1772      4801954 :             ControllerProps.SolutionTrackers(PreviousSolutionIndex).Mode = ControllerProps.Mode;
    1773      4801954 :             ControllerProps.SolutionTrackers(PreviousSolutionIndex).ActuatedValue = ControllerProps.NextActuatedValue;
    1774              :         } else {
    1775      8633894 :             ControllerProps.SolutionTrackers(PreviousSolutionIndex).DefinedFlag = false;
    1776      8633894 :             ControllerProps.SolutionTrackers(PreviousSolutionIndex).Mode = ControllerProps.Mode;
    1777      8633894 :             ControllerProps.SolutionTrackers(PreviousSolutionIndex).ActuatedValue = ControllerProps.NextActuatedValue;
    1778              :         }
    1779              :     }
    1780     15612270 : }
    1781              : 
    1782     37894646 : void UpdateController(EnergyPlusData &state, int const ControlNum)
    1783              : {
    1784              :     // PURPOSE OF THIS SUBROUTINE:
    1785              :     // This subroutine updates the actuated node with the next candidate value.
    1786              : 
    1787     37894646 :     auto &ControllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    1788              : 
    1789              :     // Set the actuated node of the Controller
    1790     37894646 :     switch (ControllerProps.ActuatorVar) {
    1791     37894646 :     case CtrlVarType::Flow: { // 'Flow'
    1792     37894646 :         PlantUtilities::SetActuatedBranchFlowRate(
    1793     37894646 :             state, ControllerProps.NextActuatedValue, ControllerProps.ActuatedNode, ControllerProps.ActuatedNodePlantLoc, false);
    1794              :         //     Node(ActuatedNode)%MassFlowRate = ControllerProps(ControlNum)%NextActuatedValue
    1795     37894646 :     } break;
    1796            0 :     default: {
    1797            0 :         ShowFatalError(state, format("UpdateController: Invalid Actuator Variable Type={}", ControlVariableTypes(ControllerProps.ActuatorVar)));
    1798            0 :     } break;
    1799              :     }
    1800     37894646 : }
    1801              : 
    1802     21963536 : void CheckTempAndHumRatCtrl(EnergyPlusData &state, int const ControlNum, bool &IsConvergedFlag)
    1803              : {
    1804              :     {
    1805     21963536 :         auto &thisController = state.dataHVACControllers->ControllerProps(ControlNum);
    1806     21963536 :         if (IsConvergedFlag) {
    1807     10465547 :             if (thisController.ControlVar == CtrlVarType::TemperatureAndHumidityRatio) {
    1808              :                 // For temperature and humidity control, after temperature control is converged, check if humidity setpoint is met
    1809       694596 :                 if (!thisController.HumRatCtrlOverride) {
    1810              :                     // For humidity control tolerance, always use 0.0001 which is roughly equivalent to a 0.015C change in dewpoint
    1811       624430 :                     if (state.dataLoopNodes->Node(thisController.SensedNode).HumRat >
    1812       624430 :                         (state.dataLoopNodes->Node(thisController.SensedNode).HumRatMax + 1.0e-5)) {
    1813              :                         // Turn on humidity control and restart controller
    1814        70183 :                         IsConvergedFlag = false;
    1815        70183 :                         thisController.HumRatCtrlOverride = true;
    1816        70183 :                         if (thisController.Action == ControllerAction::Reverse) {
    1817              :                             // Cooling coil controller should always be Reverse, but skip this if not
    1818        70183 :                             RootFinder::SetupRootFinder(state,
    1819        70183 :                                                         state.dataHVACControllers->RootFinders(ControlNum),
    1820              :                                                         DataRootFinder::Slope::Decreasing,
    1821              :                                                         DataRootFinder::RootFinderMethod::FalsePosition,
    1822              :                                                         DataPrecisionGlobals::constant_zero,
    1823              :                                                         1.0e-6,
    1824              :                                                         1.0e-5);
    1825              :                         }
    1826              :                         // Do a cold start reset, same as iControllerOpColdStart
    1827        70183 :                         ResetController(state, ControlNum, false, IsConvergedFlag);
    1828              :                     }
    1829              :                 }
    1830              :             }
    1831              :         }
    1832              :     }
    1833     21963536 : }
    1834              : 
    1835     10465547 : void ExitCalcController(EnergyPlusData &state,
    1836              :                         int const ControlNum,
    1837              :                         Real64 const NextActuatedValue,
    1838              :                         ControllerMode const Mode,
    1839              :                         bool &IsConvergedFlag,
    1840              :                         bool &IsUpToDateFlag)
    1841              : {
    1842              : 
    1843              :     // SUBROUTINE INFORMATION:
    1844              :     //       AUTHOR         Dimitri Curtil
    1845              :     //       DATE WRITTEN   February 06
    1846              : 
    1847              :     // PURPOSE OF THIS SUBROUTINE:
    1848              :     // Only called when controller is considered as "converged", meaning that we do no longer
    1849              :     // need to continue iterating.
    1850              : 
    1851              :     // METHODOLOGY EMPLOYED:
    1852              :     // Updates:
    1853              :     // - next actuated value
    1854              :     // - controller mode
    1855              :     // - IsConvergedFlag
    1856              :     // - IsUpToDateFlag
    1857              : 
    1858     10465547 :     auto &ControllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    1859              : 
    1860     10465547 :     ControllerProps.NextActuatedValue = NextActuatedValue;
    1861     10465547 :     ControllerProps.Mode = Mode;
    1862     10465547 :     IsConvergedFlag = true;
    1863              : 
    1864              :     // Set IsUpToDateFlag upon exiting to indicate caller whether or not the air loop needs to be
    1865              :     // re-simulated with the current candidate value, ie ControllerProps(ControlNum)%NextActuatedValue
    1866     10465547 :     if (ControllerProps.ActuatedValue != ControllerProps.NextActuatedValue) {
    1867       402461 :         IsUpToDateFlag = false;
    1868              :     } else {
    1869     10063086 :         IsUpToDateFlag = true;
    1870              :     }
    1871     10465547 : }
    1872              : 
    1873            0 : void TrackAirLoopControllers(EnergyPlusData &state,
    1874              :                              int const AirLoopNum,
    1875              :                              DataHVACControllers::ControllerWarmRestart const WarmRestartStatus,
    1876              :                              int const AirLoopIterMax,
    1877              :                              int const AirLoopIterTot,
    1878              :                              int const AirLoopNumCalls)
    1879              : {
    1880              : 
    1881              :     // SUBROUTINE INFORMATION:
    1882              :     //       AUTHOR         Dimitri Curtil
    1883              :     //       DATE WRITTEN   April 2006
    1884              : 
    1885              :     // PURPOSE OF THIS SUBROUTINE:
    1886              :     // Updates runtime statistics for controllers on the specified air loop.
    1887              :     // Used to produce objective metrics when analyzing runtime performance
    1888              :     // of HVAC controllers for different implementations.
    1889              : 
    1890              :     // SUBROUTINE PARAMETER DEFINITIONS:
    1891              :     // See CONTROLLER_WARM_RESTART_<> parameters in DataHVACControllers.cc
    1892              :     // If Status<0, no speculative warm restart.
    1893              :     // If Status==0, speculative warm restart failed.
    1894              :     // If Status>0, speculative warm restart succeeded.
    1895              :     // Max number of iterations performed by controllers on this air loop (per call to SimAirLoop)
    1896              :     // Aggregated number of iterations performed by controllers on this air loop (per call to SimAirLoop)
    1897              :     // Number of times SimAirLoopComponents() has been invoked
    1898              : 
    1899            0 :     auto &airLoopStats = state.dataHVACControllers->AirLoopStats(AirLoopNum);
    1900              : 
    1901              :     // If no controllers on this air loop then we have nothing to do
    1902            0 :     if (state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers == 0) {
    1903            0 :         return;
    1904              :     }
    1905              :     // To avoid tracking statistics in case of no air loop or no HVAC controllers are defined
    1906            0 :     if (state.dataHVACControllers->NumAirLoopStats == 0) {
    1907            0 :         return;
    1908              :     }
    1909              : 
    1910              :     // Update performance statistics for air loop
    1911            0 :     ++airLoopStats.NumCalls;
    1912              : 
    1913            0 :     switch (WarmRestartStatus) {
    1914            0 :     case DataHVACControllers::ControllerWarmRestart::Success: {
    1915            0 :         ++airLoopStats.NumSuccessfulWarmRestarts;
    1916            0 :     } break;
    1917            0 :     case DataHVACControllers::ControllerWarmRestart::Fail: {
    1918            0 :         ++airLoopStats.NumFailedWarmRestarts;
    1919            0 :     } break;
    1920            0 :     default: {
    1921              :         // Nothing to do if no speculative warm restart used
    1922            0 :     } break;
    1923              :     }
    1924              : 
    1925            0 :     airLoopStats.TotSimAirLoopComponents += AirLoopNumCalls;
    1926              : 
    1927            0 :     airLoopStats.MaxSimAirLoopComponents = max(airLoopStats.MaxSimAirLoopComponents, AirLoopNumCalls);
    1928              : 
    1929            0 :     airLoopStats.TotIterations += AirLoopIterTot;
    1930              : 
    1931            0 :     airLoopStats.MaxIterations = max(airLoopStats.MaxIterations, AirLoopIterMax);
    1932              : 
    1933              :     // Update performance statistics for each controller on air loop
    1934            0 :     for (int ControllerNum = 1; ControllerNum <= state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers; ++ControllerNum) {
    1935            0 :         TrackAirLoopController(state, AirLoopNum, ControllerNum);
    1936              :     }
    1937              : }
    1938              : 
    1939            0 : void TrackAirLoopController(EnergyPlusData &state,
    1940              :                             int const AirLoopNum,       // Air loop index
    1941              :                             int const AirLoopControlNum // Controller index on this air loop
    1942              : )
    1943              : {
    1944              : 
    1945              :     // SUBROUTINE INFORMATION:
    1946              :     //       AUTHOR         Dimitri Curtil
    1947              :     //       DATE WRITTEN   April 2006
    1948              : 
    1949              :     // PURPOSE OF THIS SUBROUTINE:
    1950              :     // Updates runtime statistics for the specified controller.
    1951              :     // Used to produce objective metrics when analyzing runtime performance
    1952              :     // of HVAC controllers for different implementations.
    1953              : 
    1954            0 :     auto &airLoopStats = state.dataHVACControllers->AirLoopStats(AirLoopNum);
    1955            0 :     int ControlIndex = state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).ControllerIndex(AirLoopControlNum);
    1956            0 :     auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlIndex);
    1957              : 
    1958              :     // We use NumCalcCalls instead of the iteration counter used in SolveAirLoopControllers()
    1959              :     // to avoid having to call TrackAirLoopController() directly from SolveAirLoopControllers().
    1960              :     // The 2 counters should be the same anyway as NumCalcCalls is first reset to zero and
    1961              :     // incremented each time ManageControllers() is invoked with iControllerOpIterate
    1962            0 :     int IterationCount = controllerProps.NumCalcCalls;
    1963            0 :     ControllerMode Mode = controllerProps.Mode;
    1964              : 
    1965            0 :     if (Mode != ControllerMode::None) {
    1966              : 
    1967            0 :         ++airLoopStats.ControllerStats(AirLoopControlNum).NumCalls(static_cast<int>(Mode));
    1968              : 
    1969            0 :         airLoopStats.ControllerStats(AirLoopControlNum).TotIterations(static_cast<int>(Mode)) += IterationCount;
    1970              : 
    1971            0 :         airLoopStats.ControllerStats(AirLoopControlNum).MaxIterations(static_cast<int>(Mode)) =
    1972            0 :             max(airLoopStats.ControllerStats(AirLoopControlNum).MaxIterations(static_cast<int>(Mode)), IterationCount);
    1973              :     }
    1974            0 : }
    1975              : 
    1976          799 : void DumpAirLoopStatistics(EnergyPlusData &state)
    1977              : {
    1978              : 
    1979              :     // SUBROUTINE INFORMATION:
    1980              :     //       AUTHOR         Dimitri Curtil
    1981              :     //       DATE WRITTEN   April 2006
    1982              : 
    1983              :     // PURPOSE OF THIS SUBROUTINE:
    1984              :     // Writes runtime statistics for controllers on all air loops
    1985              :     // to a CSV file named "statistics.HVACControllers.csv".
    1986              : 
    1987              :     // Detect if statistics have been generated or not for this run
    1988          799 :     if (!state.dataSysVars->TrackAirLoopEnvFlag) {
    1989          799 :         return;
    1990              :     }
    1991              : 
    1992            0 :     InputOutputFilePath StatisticsFilePath{"statistics.HVACControllers.csv"};
    1993            0 :     auto statisticsFile = StatisticsFilePath.open(state, "DumpAirLoopStatistics"); // (THIS_AUTO_OK)
    1994              : 
    1995              :     // note that the AirLoopStats object does not seem to be initialized when this code
    1996              :     // is executed and it causes a crash here
    1997            0 :     for (int AirLoopNum = 1; AirLoopNum <= state.dataHVACGlobal->NumPrimaryAirSys; ++AirLoopNum) {
    1998            0 :         WriteAirLoopStatistics(
    1999            0 :             state, statisticsFile, state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum), state.dataHVACControllers->AirLoopStats(AirLoopNum));
    2000              :     }
    2001            0 : }
    2002              : 
    2003            0 : void WriteAirLoopStatistics(EnergyPlusData &state,
    2004              :                             InputOutputFile &statisticsFile,
    2005              :                             DefinePrimaryAirSystem const &ThisPrimaryAirSystem,
    2006              :                             AirLoopStatsType const &ThisAirLoopStats)
    2007              : {
    2008              : 
    2009              :     // SUBROUTINE INFORMATION:
    2010              :     //       AUTHOR         Dimitri Curtil
    2011              :     //       DATE WRITTEN   April 2006
    2012              : 
    2013              :     // PURPOSE OF THIS SUBROUTINE:
    2014              :     // Writes runtime statistics for controllers on the specified air loop
    2015              :     // to the specified file.
    2016              : 
    2017              :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    2018              :     Real64 WarmRestartSuccessRatio;
    2019              :     Real64 AvgIterations;
    2020              : 
    2021            0 :     print(statisticsFile, "{},\n", ThisPrimaryAirSystem.Name);
    2022              : 
    2023              :     // Number of calls to SimAirLoop() has been invoked over the course of the simulation
    2024              :     // to simulate the specified air loop
    2025            0 :     print(statisticsFile, "NumCalls,{}\n", ThisAirLoopStats.NumCalls);
    2026              : 
    2027              :     // Warm restart success ratio
    2028            0 :     int NumWarmRestarts = ThisAirLoopStats.NumSuccessfulWarmRestarts + ThisAirLoopStats.NumFailedWarmRestarts;
    2029            0 :     if (NumWarmRestarts == 0) {
    2030            0 :         WarmRestartSuccessRatio = 0.0;
    2031              :     } else {
    2032            0 :         WarmRestartSuccessRatio = double(ThisAirLoopStats.NumSuccessfulWarmRestarts) / double(NumWarmRestarts);
    2033              :     }
    2034              : 
    2035            0 :     print(statisticsFile, "NumWarmRestarts,{}\n", NumWarmRestarts);
    2036            0 :     print(statisticsFile, "NumSuccessfulWarmRestarts,{}\n", ThisAirLoopStats.NumSuccessfulWarmRestarts);
    2037            0 :     print(statisticsFile, "NumFailedWarmRestarts,{}\n", ThisAirLoopStats.NumFailedWarmRestarts);
    2038            0 :     print(statisticsFile, "WarmRestartSuccessRatio,{:.10T}\n", WarmRestartSuccessRatio);
    2039              : 
    2040              :     // Total number of times SimAirLoopComponents() has been invoked over the course of the simulation
    2041              :     // to simulate the specified air loop
    2042            0 :     print(statisticsFile, "TotSimAirLoopComponents,{}\n", ThisAirLoopStats.TotSimAirLoopComponents);
    2043              :     // Maximum number of times SimAirLoopComponents() has been invoked over the course of the simulation
    2044              :     // to simulate the specified air loop
    2045            0 :     print(statisticsFile, "MaxSimAirLoopComponents,{}\n", ThisAirLoopStats.MaxSimAirLoopComponents);
    2046              : 
    2047              :     // Aggregated number of iterations needed by all controllers to simulate the specified air loop
    2048            0 :     print(statisticsFile, "TotIterations,{}\n", ThisAirLoopStats.TotIterations);
    2049              :     // Maximum number of iterations needed by controllers to simulate the specified air loop
    2050            0 :     print(statisticsFile, "MaxIterations,{}\n", ThisAirLoopStats.MaxIterations);
    2051              : 
    2052              :     // Average number of iterations needed by controllers to simulate the specified air loop
    2053            0 :     if (ThisAirLoopStats.NumCalls == 0) {
    2054            0 :         AvgIterations = 0.0;
    2055              :     } else {
    2056            0 :         AvgIterations = double(ThisAirLoopStats.TotIterations) / double(ThisAirLoopStats.NumCalls);
    2057              :     }
    2058              : 
    2059            0 :     print(statisticsFile, "AvgIterations,{:.10T}\n", AvgIterations);
    2060              : 
    2061              :     // Dump statistics for each controller on this air loop
    2062            0 :     for (int AirLoopControlNum = 1; AirLoopControlNum <= ThisPrimaryAirSystem.NumControllers; ++AirLoopControlNum) {
    2063              : 
    2064            0 :         print(statisticsFile, "{},\n", ThisPrimaryAirSystem.ControllerName(AirLoopControlNum));
    2065              : 
    2066              :         // Aggregate iteration trackers across all operating modes
    2067            0 :         int NumCalls = 0;
    2068            0 :         int TotIterations = 0;
    2069            0 :         int MaxIterations = 0;
    2070              : 
    2071            0 :         for (int iModeNum = iFirstMode; iModeNum <= iLastMode; ++iModeNum) {
    2072            0 :             NumCalls += ThisAirLoopStats.ControllerStats(AirLoopControlNum).NumCalls(iModeNum);
    2073              : 
    2074            0 :             TotIterations += ThisAirLoopStats.ControllerStats(AirLoopControlNum).TotIterations(iModeNum);
    2075              : 
    2076            0 :             MaxIterations = max(MaxIterations, ThisAirLoopStats.ControllerStats(AirLoopControlNum).MaxIterations(iModeNum));
    2077              :         }
    2078              : 
    2079              :         // Number of times this controller was simulated (should match air loop num calls)
    2080            0 :         print(statisticsFile, "NumCalls,{}\n", NumCalls);
    2081              :         // Aggregated number of iterations needed by this controller
    2082            0 :         print(statisticsFile, "TotIterations,{}\n", TotIterations);
    2083              :         // Aggregated number of iterations needed by this controller
    2084            0 :         print(statisticsFile, "MaxIterations,{}\n", MaxIterations);
    2085              : 
    2086              :         // Average number of iterations needed by controllers to simulate the specified air loop
    2087            0 :         if (NumCalls == 0) {
    2088            0 :             AvgIterations = 0.0;
    2089              :         } else {
    2090            0 :             AvgIterations = double(TotIterations) / double(NumCalls);
    2091              :         }
    2092            0 :         print(statisticsFile, "AvgIterations,{:.10T}\n", AvgIterations);
    2093              : 
    2094              :         // Dump iteration trackers for each operating mode
    2095            0 :         for (int iModeNum = iFirstMode; iModeNum <= iLastMode; ++iModeNum) {
    2096              : 
    2097            0 :             print(statisticsFile, "{},\n", state.dataHVACCtrl->ControllerModeTypes(iModeNum));
    2098              : 
    2099              :             // Number of times this controller operated in this mode
    2100            0 :             print(statisticsFile, "NumCalls,{}\n", ThisAirLoopStats.ControllerStats(AirLoopControlNum).NumCalls(iModeNum));
    2101              : 
    2102              :             // Aggregated number of iterations needed by this controller
    2103            0 :             print(statisticsFile, "TotIterations,{}\n", ThisAirLoopStats.ControllerStats(AirLoopControlNum).TotIterations(iModeNum));
    2104              :             // Aggregated number of iterations needed by this controller
    2105            0 :             print(statisticsFile, "MaxIterations,{}\n", ThisAirLoopStats.ControllerStats(AirLoopControlNum).MaxIterations(iModeNum));
    2106              : 
    2107              :             // Average number of iterations needed by controllers to simulate the specified air loop
    2108            0 :             if (ThisAirLoopStats.ControllerStats(AirLoopControlNum).NumCalls(iModeNum) == 0) {
    2109            0 :                 AvgIterations = 0.0;
    2110              :             } else {
    2111            0 :                 AvgIterations = double(ThisAirLoopStats.ControllerStats(AirLoopControlNum).TotIterations(iModeNum)) /
    2112            0 :                                 double(ThisAirLoopStats.ControllerStats(AirLoopControlNum).NumCalls(iModeNum));
    2113              :             }
    2114            0 :             print(statisticsFile, "AvgIterations,{:.10T}\n", AvgIterations);
    2115              :         }
    2116              :     }
    2117            0 : }
    2118              : 
    2119            0 : void SetupAirLoopControllersTracer(EnergyPlusData &state, int const AirLoopNum)
    2120              : {
    2121              : 
    2122              :     // SUBROUTINE INFORMATION:
    2123              :     //       AUTHOR         Dimitri Curtil
    2124              :     //       DATE WRITTEN   February 2006
    2125              : 
    2126              :     // PURPOSE OF THIS SUBROUTINE:
    2127              :     // Opens main trace file for controllers on specific air loop
    2128              :     // and writes header row with titles.
    2129              : 
    2130              :     // Open main controller trace file for each air loop
    2131            0 :     const std::string TraceFilePath = fmt::format("controller.{}.csv", state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).Name);
    2132              : 
    2133            0 :     auto &airLoopStats = state.dataHVACControllers->AirLoopStats(AirLoopNum);
    2134              : 
    2135              :     // Store file unit in air loop stats
    2136            0 :     airLoopStats.TraceFile->filePath = TraceFilePath;
    2137            0 :     airLoopStats.TraceFile->open();
    2138              : 
    2139            0 :     if (!airLoopStats.TraceFile->good()) {
    2140            0 :         ShowFatalError(state, format("SetupAirLoopControllersTracer: Failed to open air loop trace file \"{}\" for output (write).", TraceFilePath));
    2141            0 :         return;
    2142              :     }
    2143              : 
    2144            0 :     auto &TraceFile = *airLoopStats.TraceFile;
    2145              : 
    2146              :     // List all controllers and their corresponding handles into main trace file
    2147            0 :     print(TraceFile, "Num,Name,\n");
    2148              : 
    2149            0 :     for (int ControllerNum = 1; ControllerNum <= state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers; ++ControllerNum) {
    2150            0 :         print(TraceFile, "{},{},\n", ControllerNum, state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).ControllerName(ControllerNum));
    2151              :         // SAME AS ControllerProps(ControllerIndex)%ControllerName BUT NOT YET AVAILABLE
    2152              :     }
    2153              : 
    2154              :     // Skip a bunch of lines
    2155            0 :     print(TraceFile, "\n\n\n");
    2156              : 
    2157              :     // Write column header in main controller trace file
    2158            0 :     print(TraceFile,
    2159              :           "ZoneSizingCalc,SysSizingCalc,EnvironmentNum,WarmupFlag,SysTimeStamp,SysTimeInterval,BeginTimeStepFlag,FirstTimeStepSysFlag,"
    2160              :           "FirstHVACIteration,AirLoopPass,AirLoopNumCallsTot,AirLoopConverged,");
    2161              : 
    2162              :     // Write headers for final state
    2163            0 :     for (int ControllerNum = 1; ControllerNum <= state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers; ++ControllerNum) {
    2164            0 :         print(
    2165              :             TraceFile, "Mode{},IterMax{},XRoot{},YRoot{},YSetPoint{},\n", ControllerNum, ControllerNum, ControllerNum, ControllerNum, ControllerNum);
    2166              :     }
    2167              : 
    2168            0 :     print(TraceFile, "\n");
    2169            0 : }
    2170              : 
    2171            0 : void TraceAirLoopControllers(EnergyPlusData &state,
    2172              :                              bool const FirstHVACIteration,
    2173              :                              int const AirLoopNum,
    2174              :                              int const AirLoopPass,
    2175              :                              bool const AirLoopConverged,
    2176              :                              int const AirLoopNumCalls)
    2177              : {
    2178              : 
    2179              :     // SUBROUTINE INFORMATION:
    2180              :     //       AUTHOR         Dimitri Curtil
    2181              :     //       DATE WRITTEN   January 2006
    2182              : 
    2183              :     // PURPOSE OF THIS SUBROUTINE:
    2184              :     // This subroutine writes diagnostic to the trace file attached to each air loop.
    2185              : 
    2186              :     // SUBROUTINE ARGUMENT DEFINITIONS:
    2187              :     // TRUE when primary air system & controllers simulation has converged;
    2188              :     // Number of times SimAirLoopComponents() has been invoked
    2189              : 
    2190            0 :     auto &airLoopStats = state.dataHVACControllers->AirLoopStats(AirLoopNum);
    2191              : 
    2192              :     // IF no controllers on this air loop then we have nothing to do
    2193            0 :     if (state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers == 0) {
    2194            0 :         return;
    2195              :     }
    2196              :     // To avoid tracking statistics in case of no air loop or no HVAC controllers are defined
    2197            0 :     if (state.dataHVACControllers->NumAirLoopStats == 0) {
    2198            0 :         return;
    2199              :     }
    2200              : 
    2201              :     // Setup trace file on first call only
    2202            0 :     if (airLoopStats.FirstTraceFlag) {
    2203            0 :         SetupAirLoopControllersTracer(state, AirLoopNum);
    2204              : 
    2205            0 :         airLoopStats.FirstTraceFlag = false;
    2206              :     }
    2207              : 
    2208            0 :     auto &TraceFile = *airLoopStats.TraceFile;
    2209              : 
    2210            0 :     if (!TraceFile.good()) {
    2211            0 :         return;
    2212              :     }
    2213              : 
    2214              :     // Write iteration stamp first
    2215            0 :     TraceIterationStamp(state, TraceFile, FirstHVACIteration, AirLoopPass, AirLoopConverged, AirLoopNumCalls);
    2216              : 
    2217              :     // Loop over the air sys controllers and write diagnostic to trace file
    2218            0 :     for (int ControllerNum = 1; ControllerNum <= state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers; ++ControllerNum) {
    2219            0 :         TraceAirLoopController(state, TraceFile, state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).ControllerIndex(ControllerNum));
    2220              :     }
    2221              : 
    2222              :     // Go to next line
    2223            0 :     print(TraceFile, "\n");
    2224              : }
    2225              : 
    2226            0 : void TraceIterationStamp(EnergyPlusData &state,
    2227              :                          InputOutputFile &TraceFile,
    2228              :                          bool const FirstHVACIteration,
    2229              :                          int const AirLoopPass,
    2230              :                          bool const AirLoopConverged,
    2231              :                          int const AirLoopNumCalls)
    2232              : {
    2233              : 
    2234              :     // SUBROUTINE INFORMATION:
    2235              :     //       AUTHOR         Dimitri Curtil
    2236              :     //       DATE WRITTEN   February 2006
    2237              : 
    2238              :     // PURPOSE OF THIS SUBROUTINE:
    2239              :     // Writes current iteration time stamp to specified trace file.
    2240              : 
    2241              :     // SUBROUTINE PARAMETER DEFINITIONS:
    2242              :     // TRUE when primary air system and controllers simulation has converged;
    2243              :     // Number of times SimAirLoopComponents() has been invoked
    2244              : 
    2245              :     // Write step stamp to air loop trace file after reset
    2246              :     // Note that we do not go to the next line
    2247            0 :     print(TraceFile,
    2248              :           "{},{},{},{},{},{},{},{},{},{},{},{},",
    2249            0 :           static_cast<int>(state.dataGlobal->ZoneSizingCalc),
    2250            0 :           static_cast<int>(state.dataGlobal->SysSizingCalc),
    2251            0 :           state.dataEnvrn->CurEnvirNum,
    2252            0 :           static_cast<int>(state.dataGlobal->WarmupFlag),
    2253            0 :           CreateHVACTimeString(state),
    2254            0 :           MakeHVACTimeIntervalString(state),
    2255            0 :           static_cast<int>(state.dataGlobal->BeginTimeStepFlag),
    2256            0 :           static_cast<int>(state.dataHVACGlobal->FirstTimeStepSysFlag),
    2257            0 :           static_cast<int>(FirstHVACIteration),
    2258              :           AirLoopPass,
    2259              :           AirLoopNumCalls,
    2260            0 :           static_cast<int>(AirLoopConverged));
    2261            0 : }
    2262              : 
    2263            0 : void TraceAirLoopController(EnergyPlusData &state, InputOutputFile &TraceFile, int const ControlNum)
    2264              : {
    2265              : 
    2266              :     // SUBROUTINE INFORMATION:
    2267              :     //       AUTHOR         Dimitri Curtil
    2268              :     //       DATE WRITTEN   January 2006
    2269              : 
    2270              :     // PURPOSE OF THIS SUBROUTINE:
    2271              :     // This subroutine writes convergence diagnostic to the air loop trace file
    2272              :     // for the specified controller index.
    2273              : 
    2274            0 :     auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    2275              : 
    2276            0 :     print(TraceFile,
    2277              :           "{},{},{:.10T},{:.10T},{:.10T},",
    2278            0 :           controllerProps.Mode,
    2279            0 :           controllerProps.NumCalcCalls,
    2280            0 :           state.dataLoopNodes->Node(controllerProps.ActuatedNode).MassFlowRate,
    2281            0 :           state.dataLoopNodes->Node(controllerProps.SensedNode).Temp,
    2282            0 :           state.dataLoopNodes->Node(controllerProps.SensedNode).TempSetPoint);
    2283            0 : }
    2284              : 
    2285            0 : void SetupIndividualControllerTracer(EnergyPlusData &state, int const ControlNum)
    2286              : {
    2287              : 
    2288              :     // SUBROUTINE INFORMATION:
    2289              :     //       AUTHOR         Dimitri Curtil
    2290              :     //       DATE WRITTEN   February 2006
    2291              : 
    2292              :     // PURPOSE OF THIS SUBROUTINE:
    2293              :     // Opens individual controller trace file for the specified controller
    2294              :     // and writes header row.
    2295              : 
    2296            0 :     auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    2297              : 
    2298            0 :     const std::string TraceFilePath = fmt::format("controller.{}.csv", controllerProps.ControllerName);
    2299            0 :     auto &TraceFile = *controllerProps.TraceFile;
    2300            0 :     TraceFile.filePath = TraceFilePath;
    2301            0 :     TraceFile.open();
    2302              : 
    2303            0 :     if (!TraceFile.good()) {
    2304            0 :         ShowFatalError(state,
    2305            0 :                        format("SetupIndividualControllerTracer: Failed to open controller trace file \"{}\" for output (write).", TraceFilePath));
    2306            0 :         return;
    2307              :     }
    2308              : 
    2309              :     // Write header row
    2310              :     // Masss flow rate
    2311              :     // Convergence analysis
    2312            0 :     print(TraceFile,
    2313              :           "EnvironmentNum,WarmupFlag,SysTimeStamp,SysTimeInterval,AirLoopPass,FirstHVACIteration,Operation,NumCalcCalls,SensedNode%MassFlowRate,"
    2314              :           "ActuatedNode%MassFlowRateMinAvail,ActuatedNode%MassFlowRateMaxAvail,X,Y,Setpoint,DeltaSensed,Offset,Mode,IsConvergedFlag,"
    2315              :           "NextActuatedValue");
    2316              : 
    2317            0 :     RootFinder::WriteRootFinderTraceHeader(TraceFile);
    2318              : 
    2319              :     // Finally skip line
    2320            0 :     print(TraceFile, "\n");
    2321            0 : }
    2322              : 
    2323            0 : void TraceIndividualController(EnergyPlusData &state,
    2324              :                                int const ControlNum,
    2325              :                                bool const FirstHVACIteration,
    2326              :                                int const AirLoopPass,
    2327              :                                DataHVACControllers::ControllerOperation const Operation, // Operation to execute
    2328              :                                bool const IsConvergedFlag)
    2329              : {
    2330              : 
    2331              :     // SUBROUTINE INFORMATION:
    2332              :     //       AUTHOR         Dimitri Curtil
    2333              :     //       DATE WRITTEN   January 2006
    2334              : 
    2335              :     // PURPOSE OF THIS SUBROUTINE:
    2336              :     // This subroutine writes convergence diagnostic to the trace file for the specified controller.
    2337              : 
    2338              :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    2339              :     bool SkipLineFlag;
    2340              : 
    2341            0 :     auto &ControllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    2342              : 
    2343              :     // Setup individual trace file on first trace only
    2344            0 :     if (ControllerProps.FirstTraceFlag) {
    2345            0 :         SetupIndividualControllerTracer(state, ControlNum);
    2346              : 
    2347            0 :         ControllerProps.FirstTraceFlag = false;
    2348            0 :         SkipLineFlag = false;
    2349              :     } else {
    2350            0 :         SkipLineFlag = FirstHVACIteration && (ControllerProps.NumCalcCalls == 0);
    2351              :     }
    2352              : 
    2353            0 :     auto &TraceFile = *ControllerProps.TraceFile;
    2354              : 
    2355              :     // Nothing to do if trace file not registered
    2356            0 :     if (!TraceFile.good()) {
    2357            0 :         return;
    2358              :     }
    2359              : 
    2360              :     // Skip a line before each new HVAC step
    2361            0 :     if (SkipLineFlag) {
    2362            0 :         print(TraceFile, "\n");
    2363              :     }
    2364              : 
    2365              :     // Set the sensed and actuated node numbers
    2366            0 :     int ActuatedNode = ControllerProps.ActuatedNode;
    2367            0 :     int SensedNode = ControllerProps.SensedNode;
    2368              : 
    2369              :     // Write iteration stamp
    2370            0 :     print(TraceFile,
    2371              :           "{},{},{},{},{},{},{},{},",
    2372            0 :           state.dataEnvrn->CurEnvirNum,
    2373            0 :           static_cast<int>(state.dataGlobal->WarmupFlag),
    2374            0 :           CreateHVACTimeString(state),
    2375            0 :           MakeHVACTimeIntervalString(state),
    2376              :           AirLoopPass,
    2377            0 :           static_cast<int>(FirstHVACIteration),
    2378              :           Operation,
    2379            0 :           ControllerProps.NumCalcCalls);
    2380              : 
    2381              :     // Write detailed diagnostic
    2382            0 :     switch (Operation) {
    2383            0 :     case DataHVACControllers::ControllerOperation::ColdStart:
    2384              :     case DataHVACControllers::ControllerOperation::WarmRestart: {
    2385            0 :         print(TraceFile,
    2386              :               "{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{},{},{},{},{:.10T},",
    2387            0 :               state.dataLoopNodes->Node(SensedNode).MassFlowRate,
    2388            0 :               state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMinAvail,
    2389            0 :               state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMaxAvail,
    2390            0 :               ControllerProps.ActuatedValue,
    2391            0 :               state.dataLoopNodes->Node(SensedNode).Temp,
    2392            0 :               ControllerProps.SetPointValue,
    2393            0 :               ' ',
    2394            0 :               ' ',
    2395            0 :               ControllerProps.Mode,
    2396            0 :               static_cast<int>(IsConvergedFlag),
    2397            0 :               ControllerProps.NextActuatedValue);
    2398              :         // X | Y | setpoint | DeltaSensed = Y - YRoot | Offset | Mode | IsConvergedFlag
    2399              : 
    2400              :         // No trace available for root finder yet
    2401              :         // Skip call to WriteRootFinderTrace()
    2402              : 
    2403              :         // Finally skip line
    2404            0 :         print(TraceFile, "\n");
    2405            0 :     } break;
    2406            0 :     case DataHVACControllers::ControllerOperation::Iterate: {
    2407              :         // Masss flow rate
    2408              :         // Convergence analysis
    2409              : 
    2410            0 :         print(TraceFile,
    2411              :               "{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{},{},{:.10T},",
    2412            0 :               state.dataLoopNodes->Node(SensedNode).MassFlowRate,
    2413            0 :               state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMinAvail,
    2414            0 :               state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMaxAvail,
    2415            0 :               ControllerProps.ActuatedValue,
    2416            0 :               state.dataLoopNodes->Node(SensedNode).Temp,
    2417            0 :               ControllerProps.SetPointValue,
    2418            0 :               ControllerProps.DeltaSensed,
    2419            0 :               ControllerProps.Offset,
    2420            0 :               ControllerProps.Mode,
    2421            0 :               static_cast<int>(IsConvergedFlag),
    2422            0 :               ControllerProps.NextActuatedValue);
    2423              : 
    2424              :         // X | Y | setpoint | DeltaSensed = Y - YRoot | Offset | Mode | IsConvergedFlag
    2425              : 
    2426              :         // Append trace for root finder
    2427            0 :         RootFinder::WriteRootFinderTrace(TraceFile, state.dataHVACControllers->RootFinders(ControlNum));
    2428              : 
    2429              :         // Finally skip line
    2430            0 :         print(TraceFile, "\n");
    2431              : 
    2432            0 :     } break;
    2433            0 :     case DataHVACControllers::ControllerOperation::End: {
    2434              :         // Masss flow rate
    2435              :         // Convergence analysis
    2436            0 :         print(TraceFile,
    2437              :               "{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{},{},{:.10T},",
    2438            0 :               state.dataLoopNodes->Node(SensedNode).MassFlowRate,
    2439            0 :               state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMinAvail,
    2440            0 :               state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMaxAvail,
    2441            0 :               ControllerProps.ActuatedValue,
    2442            0 :               state.dataLoopNodes->Node(SensedNode).Temp,
    2443            0 :               ControllerProps.SetPointValue,
    2444            0 :               ControllerProps.DeltaSensed,
    2445            0 :               ControllerProps.Offset,
    2446            0 :               ControllerProps.Mode,
    2447            0 :               static_cast<int>(IsConvergedFlag),
    2448            0 :               ControllerProps.NextActuatedValue);
    2449              : 
    2450              :         // X | Y | setpoint | DeltaSensed = Y - YRoot | Offset | Mode | IsConvergedFlag
    2451              : 
    2452              :         // No trace available for root finder yet
    2453              :         // Skip call to WriteRootFinderTrace()
    2454              : 
    2455              :         // Finally skip line
    2456            0 :         print(TraceFile, "\n");
    2457              : 
    2458              :         // Skip an additional line to indicate end of current HVAC step
    2459            0 :         print(TraceFile, "\n");
    2460              : 
    2461            0 :     } break;
    2462            0 :     default: {
    2463              :         // Should never happen
    2464            0 :         ShowFatalError(
    2465            0 :             state, format("TraceIndividualController: Invalid Operation passed={}, Controller name={}", Operation, ControllerProps.ControllerName));
    2466            0 :     } break;
    2467              :     }
    2468              : 
    2469            0 :     TraceFile.flush();
    2470              : }
    2471              : 
    2472            0 : Real64 GetCurrentHVACTime(const EnergyPlusData &state)
    2473              : {
    2474              :     // SUBROUTINE INFORMATION:
    2475              :     //       AUTHOR         Dimitri Curtil
    2476              :     //       DATE WRITTEN   November 2004
    2477              :     //       MODIFIED       na
    2478              :     //       RE-ENGINEERED  na
    2479              : 
    2480              :     // PURPOSE OF THIS FUNCTION:
    2481              :     // This routine returns the time in seconds at the end of the current HVAC step.
    2482              : 
    2483              :     // This is the correct formula that does not use MinutesPerSystemTimeStep, which would
    2484              :     // erronously truncate all sub-minute system time steps down to the closest full minute.
    2485              :     // Maybe later TimeStepZone, TimeStepSys and SysTimeElapsed could also be specified
    2486              :     // as real.
    2487              :     Real64 const CurrentHVACTime =
    2488            0 :         (state.dataGlobal->CurrentTime - state.dataGlobal->TimeStepZone) + state.dataHVACGlobal->SysTimeElapsed + state.dataHVACGlobal->TimeStepSys;
    2489            0 :     return CurrentHVACTime * Constant::rSecsInHour;
    2490              : }
    2491              : 
    2492      2854666 : Real64 GetPreviousHVACTime(const EnergyPlusData &state)
    2493              : {
    2494              :     // SUBROUTINE INFORMATION:
    2495              :     //       AUTHOR         Dimitri Curtil
    2496              :     //       DATE WRITTEN   November 2004
    2497              : 
    2498              :     // PURPOSE OF THIS FUNCTION:
    2499              :     // This routine returns the time in seconds at the beginning of the current HVAC step.
    2500              : 
    2501              :     // This is the correct formula that does not use MinutesPerSystemTimeStep, which would
    2502              :     // erronously truncate all sub-minute system time steps down to the closest full minute.
    2503      2854666 :     Real64 const PreviousHVACTime = (state.dataGlobal->CurrentTime - state.dataGlobal->TimeStepZone) + state.dataHVACGlobal->SysTimeElapsed;
    2504      2854666 :     return PreviousHVACTime * Constant::rSecsInHour;
    2505              : }
    2506              : 
    2507            0 : std::string CreateHVACTimeString(const EnergyPlusData &state)
    2508              : {
    2509              : 
    2510              :     // FUNCTION INFORMATION:
    2511              :     //       AUTHOR         Dimitri Curtil
    2512              :     //       DATE WRITTEN   January 2006
    2513              : 
    2514              :     // PURPOSE OF THIS FUNCTION:
    2515              :     // This function creates a string describing the current time stamp of the system
    2516              :     // time step.
    2517              : 
    2518            0 :     std::string Buffer = General::CreateTimeString(GetCurrentHVACTime(state));
    2519            0 :     return state.dataEnvrn->CurMnDy + ' ' + stripped(Buffer);
    2520            0 : }
    2521              : 
    2522            0 : std::string CreateHVACStepFullString(const EnergyPlusData &state)
    2523              : {
    2524              : 
    2525              :     // FUNCTION INFORMATION:
    2526              :     //       AUTHOR         Dimitri Curtil
    2527              :     //       DATE WRITTEN   April 2006
    2528              : 
    2529              :     // PURPOSE OF THIS FUNCTION:
    2530              :     // This function creates a string describing the current HVAC step.
    2531              :     // It includes the environment name, the current day/month and the current
    2532              :     // time stamp for the system time step.
    2533              :     // It is used in error messages only.
    2534              : 
    2535            0 :     return state.dataEnvrn->EnvironmentName + ", " + MakeHVACTimeIntervalString(state);
    2536              : }
    2537              : 
    2538            0 : std::string MakeHVACTimeIntervalString(const EnergyPlusData &state)
    2539              : {
    2540              : 
    2541              :     // FUNCTION INFORMATION:
    2542              :     //       AUTHOR         Dimitri Curtil
    2543              :     //       DATE WRITTEN   January 2006
    2544              : 
    2545              :     // PURPOSE OF THIS FUNCTION:
    2546              :     // This function creates a string describing the current time interval of the system
    2547              :     // time step.
    2548              : 
    2549            0 :     return format("{} - {}", General::CreateTimeString(GetPreviousHVACTime(state)), General::CreateTimeString(GetCurrentHVACTime(state)));
    2550              : }
    2551              : 
    2552          297 : void CheckControllerListOrder(EnergyPlusData &state)
    2553              : {
    2554              : 
    2555              :     // SUBROUTINE INFORMATION:
    2556              :     //       AUTHOR         B. Griffith
    2557              :     //       DATE WRITTEN   Oct 10.
    2558              : 
    2559              :     // PURPOSE OF THIS SUBROUTINE:
    2560              :     // check that if multiple controllers on an air loop, that they aren't listed in a bad order
    2561              :     // CR 8253
    2562              : 
    2563              :     // METHODOLOGY EMPLOYED:
    2564              :     // setup data for sensed nodes and compare positions if on the same branch
    2565              : 
    2566              :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    2567          297 :     Array2D_int ContrlSensedNodeNums; // array for storing sense node info
    2568              : 
    2569          326 :     for (int AirSysNum = 1; AirSysNum <= state.dataHVACGlobal->NumPrimaryAirSys; ++AirSysNum) {
    2570              : 
    2571           29 :         if (state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).NumControllers > 1) {
    2572              :             // first see how many are water coil controllers
    2573            7 :             int WaterCoilContrlCount = 0; // init
    2574           21 :             for (int ContrlNum = 1; ContrlNum <= state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).NumControllers; ++ContrlNum) {
    2575           14 :                 if (Util::SameString(state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).ControllerType(ContrlNum), "CONTROLLER:WATERCOIL")) {
    2576           14 :                     ++WaterCoilContrlCount;
    2577              :                 }
    2578              :             }
    2579              : 
    2580            7 :             if (WaterCoilContrlCount > 1) {
    2581            7 :                 ContrlSensedNodeNums.allocate(3, WaterCoilContrlCount);
    2582            7 :                 ContrlSensedNodeNums = 0;
    2583            7 :                 int SensedNodeIndex = 0;
    2584           21 :                 for (int ContrlNum = 1; ContrlNum <= state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).NumControllers; ++ContrlNum) {
    2585           14 :                     if (Util::SameString(state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).ControllerType(ContrlNum), "CONTROLLER:WATERCOIL")) {
    2586           14 :                         ++SensedNodeIndex;
    2587           14 :                         int foundControl = Util::FindItemInList(state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).ControllerName(ContrlNum),
    2588           14 :                                                                 state.dataHVACControllers->ControllerProps,
    2589              :                                                                 &ControllerPropsType::ControllerName);
    2590           14 :                         if (foundControl > 0) {
    2591           14 :                             ContrlSensedNodeNums(1, SensedNodeIndex) = state.dataHVACControllers->ControllerProps(foundControl).SensedNode;
    2592              :                         }
    2593              :                     }
    2594              :                 }
    2595              :             }
    2596              : 
    2597              :             // fill branch index for sensed nodes
    2598            7 :             if (allocated(ContrlSensedNodeNums)) {
    2599           26 :                 for (int BranchNum = 1; BranchNum <= state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).NumBranches; ++BranchNum) {
    2600           57 :                     for (int SensedNodeIndex = 1; SensedNodeIndex <= WaterCoilContrlCount; ++SensedNodeIndex) {
    2601          128 :                         for (int BranchNodeIndex = 1;
    2602          128 :                              BranchNodeIndex <= state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).Branch(BranchNum).TotalNodes;
    2603              :                              ++BranchNodeIndex) {
    2604           90 :                             if (ContrlSensedNodeNums(1, SensedNodeIndex) ==
    2605           90 :                                 state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).Branch(BranchNum).NodeNum(BranchNodeIndex)) {
    2606           14 :                                 ContrlSensedNodeNums(2, SensedNodeIndex) = BranchNodeIndex;
    2607           14 :                                 ContrlSensedNodeNums(3, SensedNodeIndex) = BranchNum;
    2608              :                             }
    2609              :                         }
    2610              :                     }
    2611              :                 }
    2612              :             }
    2613              :             // check if flow order doesn't agree with controller order
    2614            7 :             if (allocated(ContrlSensedNodeNums)) {
    2615           21 :                 for (int SensedNodeIndex = 1; SensedNodeIndex <= WaterCoilContrlCount; ++SensedNodeIndex) {
    2616           14 :                     if (SensedNodeIndex == 1) {
    2617            7 :                         continue;
    2618              :                     }
    2619            7 :                     if (ContrlSensedNodeNums(2, SensedNodeIndex) < ContrlSensedNodeNums(2, SensedNodeIndex - 1)) {
    2620              :                         // now see if on the same branch
    2621            1 :                         if (ContrlSensedNodeNums(3, SensedNodeIndex) == ContrlSensedNodeNums(3, SensedNodeIndex - 1)) {
    2622              :                             // we have a flow order problem with water coil controllers
    2623            0 :                             ShowSevereError(state, "CheckControllerListOrder: A water coil controller list has the wrong order");
    2624            0 :                             ShowContinueError(state,
    2625            0 :                                               format("Check the AirLoopHVAC:ControllerList for the air loop called \"{}\"",
    2626            0 :                                                      state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).Name));
    2627            0 :                             ShowContinueError(state,
    2628              :                                               "When there are multiple Controller:WaterCoil objects for the same air loop, they need to be "
    2629              :                                               "listed in the proper order.");
    2630            0 :                             ShowContinueError(state,
    2631              :                                               "The controllers should be listed in natural flow order with those for upstream coils listed "
    2632              :                                               "before those for downstream coils.");
    2633            0 :                             ShowContinueError(state, "The sensed nodes specified for the respective controllers should also reflect this order.");
    2634              :                         }
    2635              :                     }
    2636              :                 }
    2637              :             }
    2638              : 
    2639            7 :             if (allocated(ContrlSensedNodeNums)) {
    2640            7 :                 ContrlSensedNodeNums.deallocate();
    2641              :             }
    2642              : 
    2643              :         } // controllers > 1
    2644              :     }
    2645          297 : }
    2646              : 
    2647          899 : void CheckCoilWaterInletNode(EnergyPlusData &state,
    2648              :                              int const WaterInletNodeNum, // input actuator node number
    2649              :                              bool &NodeNotFound           // true if matching actuator node not found
    2650              : )
    2651              : {
    2652              : 
    2653              :     // SUBROUTINE INFORMATION:
    2654              :     //       AUTHOR         Heejin Cho
    2655              :     //       DATE WRITTEN   November 2010
    2656              : 
    2657              :     // PURPOSE OF THIS FUNCTION:
    2658              :     // This subroutine checks that the water inlet node number is matched by
    2659              :     // the actuator node number of some water coil
    2660              : 
    2661          899 :     if (state.dataHVACControllers->GetControllerInputFlag) {
    2662           22 :         GetControllerInput(state);
    2663           22 :         state.dataHVACControllers->GetControllerInputFlag = false;
    2664              :     }
    2665              : 
    2666          899 :     NodeNotFound = true;
    2667         8737 :     for (auto const &ControllerProps : state.dataHVACControllers->ControllerProps) {
    2668         7838 :         if (ControllerProps.ActuatedNode == WaterInletNodeNum) {
    2669          897 :             NodeNotFound = false;
    2670              :         }
    2671              :     }
    2672          899 : }
    2673              : 
    2674         3356 : void GetControllerNameAndIndex(EnergyPlusData &state,
    2675              :                                int const WaterInletNodeNum, // input actuator node number
    2676              :                                std::string &ControllerName, // controller name used by water coil
    2677              :                                int &ControllerIndex,        // controller index used by water coil
    2678              :                                bool &ErrorsFound            // true if matching actuator node not found
    2679              : )
    2680              : {
    2681              : 
    2682              :     // SUBROUTINE INFORMATION:
    2683              :     //       AUTHOR         Richard Raustad
    2684              :     //       DATE WRITTEN   June 2017
    2685              : 
    2686              :     // PURPOSE OF THIS FUNCTION:
    2687              :     // This subroutine checks that the water inlet node number is matched by
    2688              :     // the actuator node number of some water coil and passed back controller name and index
    2689              : 
    2690         3356 :     if (state.dataHVACControllers->GetControllerInputFlag) {
    2691          302 :         GetControllerInput(state);
    2692          302 :         state.dataHVACControllers->GetControllerInputFlag = false;
    2693              :     }
    2694              : 
    2695         3356 :     ControllerName = " ";
    2696         3356 :     ControllerIndex = 0;
    2697        25988 :     for (int ControlNum = 1; ControlNum <= state.dataHVACControllers->NumControllers; ++ControlNum) {
    2698        23539 :         if (state.dataHVACControllers->ControllerProps(ControlNum).ActuatedNode == WaterInletNodeNum) {
    2699          907 :             ControllerIndex = ControlNum;
    2700          907 :             ControllerName = state.dataHVACControllers->ControllerProps(ControlNum).ControllerName;
    2701          907 :             break;
    2702              :         }
    2703              :     }
    2704              : 
    2705         3356 :     if (ControllerIndex == 0) {
    2706         2449 :         ErrorsFound = true;
    2707              :     }
    2708         3356 : }
    2709              : 
    2710           58 : void GetControllerActuatorNodeNum(EnergyPlusData &state,
    2711              :                                   std::string const &ControllerName, // name of coil controller
    2712              :                                   int &WaterInletNodeNum,            // input actuator node number
    2713              :                                   bool &NodeNotFound                 // true if matching actuator node not found
    2714              : )
    2715              : {
    2716              : 
    2717              :     // SUBROUTINE INFORMATION:
    2718              :     //       AUTHOR         Richard Raustad, FSEC
    2719              :     //       DATE WRITTEN   September 2013
    2720              : 
    2721              :     // PURPOSE OF THIS FUNCTION:
    2722              :     // This subroutine finds the controllers actuator node number
    2723              : 
    2724           58 :     if (state.dataHVACControllers->GetControllerInputFlag) {
    2725            2 :         GetControllerInput(state);
    2726            2 :         state.dataHVACControllers->GetControllerInputFlag = false;
    2727              :     }
    2728              : 
    2729           58 :     NodeNotFound = true;
    2730           58 :     int ControlNum = Util::FindItemInList(ControllerName, state.dataHVACControllers->ControllerProps, &ControllerPropsType::ControllerName);
    2731           58 :     if (ControlNum > 0 && ControlNum <= state.dataHVACControllers->NumControllers) {
    2732           58 :         WaterInletNodeNum = state.dataHVACControllers->ControllerProps(ControlNum).ActuatedNode;
    2733           58 :         NodeNotFound = false;
    2734              :     }
    2735           58 : }
    2736              : 
    2737          900 : int GetControllerIndex(EnergyPlusData &state, std::string const &ControllerName // name of coil controller
    2738              : )
    2739              : {
    2740              : 
    2741              :     // SUBROUTINE INFORMATION:
    2742              :     //       AUTHOR         Richard Raustad, FSEC
    2743              :     //       DATE WRITTEN   January 2018
    2744              : 
    2745              :     // This subroutine finds the controllers actuator node number
    2746              : 
    2747          900 :     if (state.dataHVACControllers->GetControllerInputFlag) {
    2748            0 :         GetControllerInput(state);
    2749            0 :         state.dataHVACControllers->GetControllerInputFlag = false;
    2750              :     }
    2751              : 
    2752          900 :     int ControllerIndex = Util::FindItemInList(ControllerName, state.dataHVACControllers->ControllerProps, &ControllerPropsType::ControllerName);
    2753          900 :     if (ControllerIndex == 0) {
    2754            0 :         ShowFatalError(state,
    2755            0 :                        format("ManageControllers: Invalid controller={}. The only valid controller type for an AirLoopHVAC is Controller:WaterCoil.",
    2756              :                               ControllerName));
    2757              :     }
    2758              : 
    2759          900 :     return ControllerIndex;
    2760              : }
    2761              : 
    2762              : } // namespace EnergyPlus::HVACControllers
        

Generated by: LCOV version 2.0-1