LCOV - code coverage report
Current view: top level - EnergyPlus - HVACControllers.cc (source / functions) Hit Total Coverage
Test: lcov.output.filtered Lines: 645 1205 53.5 %
Date: 2023-01-17 19:17:23 Functions: 23 35 65.7 %

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

Generated by: LCOV version 1.13