LCOV - code coverage report
Current view: top level - EnergyPlus - HVACControllers.cc (source / functions) Coverage Total Hit
Test: lcov.output.filtered Lines: 47.8 % 1144 547
Test Date: 2025-05-22 16:09:37 Functions: 60.0 % 35 21

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

Generated by: LCOV version 2.0-1