LCOV - code coverage report
Current view: top level - EnergyPlus - HVACControllers.cc (source / functions) Hit Total Coverage
Test: lcov.output.filtered Lines: 582 1146 50.8 %
Date: 2024-08-24 18:31:18 Functions: 22 35 62.9 %

          Line data    Source code
       1             : // EnergyPlus, Copyright (c) 1996-2024, 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           4 : std::string ControlVariableTypes(CtrlVarType const &c)
     178             : {
     179           4 :     switch (c) {
     180           0 :     case CtrlVarType::NoControlVariable:
     181           0 :         return "No control variable";
     182           4 :     case CtrlVarType::Temperature:
     183           4 :         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    52803819 : 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    52803819 :     if (state.dataHVACControllers->GetControllerInputFlag) { // First time subroutine has been entered
     226           0 :         GetControllerInput(state);
     227           0 :         state.dataHVACControllers->GetControllerInputFlag = false;
     228             :     }
     229             : 
     230    52803819 :     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    52803819 :         ControlNum = ControllerIndex;
     241    52803819 :         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    52803819 :         if (state.dataHVACControllers->CheckEquipName(ControlNum)) {
     249         866 :             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         866 :             state.dataHVACControllers->CheckEquipName(ControlNum) = false;
     258             :         }
     259             :     }
     260             : 
     261    52803819 :     auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
     262             : 
     263    52803819 :     if (controllerProps.BypassControllerCalc && BypassOAController) {
     264     1905678 :         IsUpToDateFlag = true;
     265     1905678 :         IsConvergedFlag = true;
     266     1905678 :         if (present(AllowWarmRestartFlag)) AllowWarmRestartFlag = true;
     267     2218272 :         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    50898141 :     if (controllerProps.ActuatedNodePlantLoc.loopNum > 0) {
     275             : 
     276    50897275 :         if (state.dataPlnt->PlantLoop(controllerProps.ActuatedNodePlantLoc.loopNum)
     277    50897275 :                 .LoopSide(controllerProps.ActuatedNodePlantLoc.loopSideNum)
     278    50897275 :                 .FlowLock == DataPlant::FlowLock::Locked) {
     279             :             // plant is rigid so controller cannot change anything.
     280             :             // Update the current Controller to the outlet nodes
     281      312594 :             UpdateController(state, ControlNum);
     282             : 
     283      312594 :             IsConvergedFlag = true;
     284      312594 :             return;
     285             :         }
     286             :     }
     287             : 
     288             :     // Detect if speculative warm restart is supported by this computer
     289    50585547 :     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     9746213 :         if (controllerProps.ControlVar == CtrlVarType::TemperatureAndHumidityRatio) {
     294      424998 :             AllowWarmRestartFlag = false;
     295             :         } else {
     296     9321215 :             AllowWarmRestartFlag = true;
     297             :         }
     298             :     }
     299             : 
     300    50585547 :     if (controllerProps.InitFirstPass) {
     301             :         // Coil must first be sized to:
     302             :         // Initialize controllerProps%MinActuated and controllerProps%MaxActuated
     303         866 :         InitController(state, ControlNum, IsConvergedFlag);
     304         866 :         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    50585547 :     switch (Operation) {
     313     9746213 :     case DataHVACControllers::ControllerOperation::ColdStart: {
     314             :         // For temperature and humidity control reset humidity control override if it was set
     315     9746213 :         if (controllerProps.HumRatCtrlOverride) {
     316       62062 :             controllerProps.HumRatCtrlOverride = false;
     317             :             // Put the controller tolerance (offset) back to it's original value
     318       62062 :             RootFinder::SetupRootFinder(state,
     319       62062 :                                         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     9746213 :         ResetController(state, ControlNum, false, IsConvergedFlag);
     329             :         // Update the current Controller to the outlet nodes
     330     9746213 :         UpdateController(state, ControlNum);
     331     9746213 :     } break;
     332     5172357 :     case DataHVACControllers::ControllerOperation::WarmRestart: {
     333             :         // If a iControllerOpWarmRestart call, set the actuator inlet flows to previous solution
     334     5172357 :         ResetController(state, ControlNum, true, IsConvergedFlag);
     335             :         // Update the current Controller to the outlet nodes
     336     5172357 :         UpdateController(state, ControlNum);
     337     5172357 :     } break;
     338    20748407 :     case DataHVACControllers::ControllerOperation::Iterate: {
     339             :         // With the correct ControlNum Initialize all Controller related parameters
     340    20748407 :         InitController(state, ControlNum, IsConvergedFlag);
     341             : 
     342             :         // No initialization needed: should have been done before
     343             :         // Simulate the correct Controller with the current ControlNum
     344    20748407 :         int ControllerType = controllerProps.ControllerType_Num;
     345             : 
     346    20748407 :         if (ControllerType == ControllerSimple_Type) { // 'Controller:WaterCoil'
     347    20748407 :             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    20748407 :         UpdateController(state, ControlNum);
     354             : 
     355    20748407 :         CheckTempAndHumRatCtrl(state, ControlNum, IsConvergedFlag);
     356             : 
     357    20748407 :     } break;
     358    14918570 :     case DataHVACControllers::ControllerOperation::End: {
     359             :         // With the correct ControlNum Initialize all Controller related parameters
     360    14918570 :         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    14918570 :         int ControllerType = controllerProps.ControllerType_Num;
     365             : 
     366    14918570 :         if (ControllerType == ControllerSimple_Type) { // 'Controller:WaterCoil'
     367    14918570 :             CheckSimpleController(state, ControlNum, IsConvergedFlag);
     368    14918570 :             SaveSimpleController(state, ControlNum, FirstHVACIteration, IsConvergedFlag);
     369             :         } else {
     370           0 :             ShowFatalError(state, format("Invalid controller type in ManageControllers={}", controllerProps.ControllerType));
     371             :         }
     372             : 
     373    14918570 :     } 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    50585547 :     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         325 : 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         325 :     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         325 :     Array1D<Real64> NumArray;
     416         325 :     Array1D_string AlphArray;
     417         325 :     Array1D_string cAlphaFields;   // Alpha field names
     418         325 :     Array1D_string cNumericFields; // Numeric field names
     419         325 :     Array1D_bool lAlphaBlanks;     // Logical array, alpha field input BLANK = .TRUE.
     420         325 :     Array1D_bool lNumericBlanks;   // Logical array, numeric field input BLANK = .TRUE.
     421         325 :     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         325 :     std::string const CurrentModuleObject = "Controller:WaterCoil";
     428         325 :     int NumSimpleControllers = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, CurrentModuleObject);
     429         325 :     state.dataHVACControllers->NumControllers = NumSimpleControllers;
     430             : 
     431             :     // Allocate stats data structure for each air loop and controller if needed
     432         325 :     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         325 :     if (state.dataHVACControllers->NumControllers == 0) return;
     446             :     // Condition of no controllers will be taken care of elsewhere, if necessary
     447             : 
     448         296 :     state.dataHVACControllers->ControllerProps.allocate(state.dataHVACControllers->NumControllers);
     449         296 :     state.dataHVACControllers->RootFinders.allocate(state.dataHVACControllers->NumControllers);
     450         296 :     state.dataHVACControllers->CheckEquipName.dimension(state.dataHVACControllers->NumControllers, true);
     451             : 
     452         296 :     state.dataInputProcessing->inputProcessor->getObjectDefMaxArgs(state, CurrentModuleObject, NumArgs, NumAlphas, NumNums);
     453         296 :     AlphArray.allocate(NumAlphas);
     454         296 :     cAlphaFields.allocate(NumAlphas);
     455         296 :     cNumericFields.allocate(NumNums);
     456         296 :     NumArray.dimension(NumNums, 0.0);
     457         296 :     lAlphaBlanks.dimension(NumAlphas, true);
     458         296 :     lNumericBlanks.dimension(NumNums, true);
     459             : 
     460             :     // Now find and load all of the simple controllers.
     461         296 :     if (NumSimpleControllers > 0) {
     462             :         int IOStat;
     463        1162 :         for (int Num = 1; Num <= NumSimpleControllers; ++Num) {
     464         866 :             auto &controllerProps = state.dataHVACControllers->ControllerProps(Num);
     465         866 :             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         866 :             controllerProps.ControllerName = AlphArray(1);
     479         866 :             controllerProps.ControllerType = CurrentModuleObject;
     480             : 
     481         866 :             controllerProps.ControlVar = static_cast<EnergyPlus::HVACControllers::CtrlVarType>(getEnumValue(ctrlVarNamesUC, AlphArray(2)));
     482         866 :             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         866 :             controllerProps.Action = static_cast<ControllerAction>(getEnumValue(actionNamesUC, AlphArray(3)));
     492         866 :             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         866 :             if (AlphArray(4) == "FLOW") {
     500         866 :                 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         866 :             controllerProps.SensedNode = NodeInputManager::GetOnlySingleNode(state,
     507         866 :                                                                              AlphArray(5),
     508             :                                                                              ErrorsFound,
     509             :                                                                              DataLoopNode::ConnectionObjectType::ControllerWaterCoil,
     510         866 :                                                                              AlphArray(1),
     511             :                                                                              DataLoopNode::NodeFluidType::Blank,
     512             :                                                                              DataLoopNode::ConnectionType::Sensor,
     513             :                                                                              NodeInputManager::CompFluidStream::Primary,
     514             :                                                                              DataLoopNode::ObjectIsNotParent);
     515         866 :             controllerProps.ActuatedNode = NodeInputManager::GetOnlySingleNode(state,
     516         866 :                                                                                AlphArray(6),
     517             :                                                                                ErrorsFound,
     518             :                                                                                DataLoopNode::ConnectionObjectType::ControllerWaterCoil,
     519         866 :                                                                                AlphArray(1),
     520             :                                                                                DataLoopNode::NodeFluidType::Blank,
     521             :                                                                                DataLoopNode::ConnectionType::Actuator,
     522             :                                                                                NodeInputManager::CompFluidStream::Primary,
     523             :                                                                                DataLoopNode::ObjectIsNotParent);
     524         866 :             controllerProps.Offset = NumArray(1);
     525         866 :             controllerProps.MaxVolFlowActuated = NumArray(2);
     526         866 :             controllerProps.MinVolFlowActuated = NumArray(3);
     527             : 
     528         866 :             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         866 :             if (controllerProps.SensedNode > 0) {
     535             : 
     536             :                 bool NodeNotFound; // flag true if the sensor node is on the coil air outlet node
     537         866 :                 if (controllerProps.ControlVar == HVACControllers::CtrlVarType::HumidityRatio ||
     538         866 :                     controllerProps.ControlVar == HVACControllers::CtrlVarType::TemperatureAndHumidityRatio) {
     539          45 :                     SetPointManager::ResetHumidityRatioCtrlVarType(state, controllerProps.SensedNode);
     540             :                 }
     541         866 :                 WaterCoils::CheckForSensorAndSetPointNode(state, controllerProps.SensedNode, controllerProps.ControlVar, NodeNotFound);
     542             : 
     543         866 :                 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         866 :                     bool EMSSetPointErrorFlag = false;
     552         866 :                     switch (controllerProps.ControlVar) {
     553         821 :                     case HVACControllers::CtrlVarType::Temperature: {
     554         821 :                         EMSManager::CheckIfNodeSetPointManagedByEMS(state, controllerProps.SensedNode, HVAC::CtrlVarType::Temp, EMSSetPointErrorFlag);
     555         821 :                         state.dataLoopNodes->NodeSetpointCheck(controllerProps.SensedNode).needsSetpointChecking = false;
     556         821 :                         if (EMSSetPointErrorFlag) {
     557         638 :                             if (!SetPointManager::NodeHasSPMCtrlVarType(state, controllerProps.SensedNode, HVAC::CtrlVarType::Temp)) {
     558           0 :                                 ShowContinueError(state, " ..Temperature setpoint not found on coil air outlet node.");
     559           0 :                                 ShowContinueError(
     560             :                                     state, " ..The setpoint may have been placed on a node downstream of the coil or on an airloop outlet node.");
     561           0 :                                 ShowContinueError(state, " ..Specify the setpoint and the sensor on the coil air outlet node when possible.");
     562             :                             }
     563             :                         }
     564         821 :                     } break;
     565           0 :                     case HVACControllers::CtrlVarType::HumidityRatio: {
     566           0 :                         EMSManager::CheckIfNodeSetPointManagedByEMS(
     567             :                             state, controllerProps.SensedNode, HVAC::CtrlVarType::MaxHumRat, EMSSetPointErrorFlag);
     568           0 :                         state.dataLoopNodes->NodeSetpointCheck(controllerProps.SensedNode).needsSetpointChecking = false;
     569           0 :                         if (EMSSetPointErrorFlag) {
     570           0 :                             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           0 :                     } break;
     578          45 :                     case HVACControllers::CtrlVarType::TemperatureAndHumidityRatio: {
     579          45 :                         EMSManager::CheckIfNodeSetPointManagedByEMS(state, controllerProps.SensedNode, HVAC::CtrlVarType::Temp, EMSSetPointErrorFlag);
     580          45 :                         state.dataLoopNodes->NodeSetpointCheck(controllerProps.SensedNode).needsSetpointChecking = false;
     581          45 :                         if (EMSSetPointErrorFlag) {
     582          30 :                             if (!SetPointManager::NodeHasSPMCtrlVarType(state, controllerProps.SensedNode, HVAC::CtrlVarType::Temp)) {
     583           0 :                                 ShowContinueError(state, " ..Temperature setpoint not found on coil air outlet node.");
     584           0 :                                 ShowContinueError(
     585             :                                     state, " ..The setpoint may have been placed on a node downstream of the coil or on an airloop outlet node.");
     586           0 :                                 ShowContinueError(state, " ..Specify the setpoint and the sensor on the coil air outlet node when possible.");
     587             :                             }
     588             :                         }
     589          45 :                         EMSSetPointErrorFlag = false;
     590          45 :                         EMSManager::CheckIfNodeSetPointManagedByEMS(
     591             :                             state, controllerProps.SensedNode, HVAC::CtrlVarType::MaxHumRat, EMSSetPointErrorFlag);
     592          45 :                         state.dataLoopNodes->NodeSetpointCheck(controllerProps.SensedNode).needsSetpointChecking = false;
     593          45 :                         if (EMSSetPointErrorFlag) {
     594          30 :                             if (!SetPointManager::NodeHasSPMCtrlVarType(state, controllerProps.SensedNode, HVAC::CtrlVarType::MaxHumRat)) {
     595           0 :                                 ShowContinueError(state, " ..Humidity ratio setpoint not found on coil air outlet node.");
     596           0 :                                 ShowContinueError(
     597             :                                     state, " ..The setpoint may have been placed on a node downstream of the coil or on an airloop outlet node.");
     598           0 :                                 ShowContinueError(state, " ..Specify the setpoint and the sensor on the coil air outlet node when possible.");
     599             :                             }
     600             :                         }
     601          45 :                     } 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        1162 :     for (int Num = 1; Num <= NumSimpleControllers; ++Num) {
     612         866 :         auto &controllerProps = state.dataHVACControllers->ControllerProps(Num);
     613         866 :         WaterCoils::CheckActuatorNode(state, controllerProps.ActuatedNode, controllerProps.WaterCoilType, ActuatorNodeNotFound);
     614         866 :         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         866 :             if (controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterCooling ||
     620         557 :                 controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterDetailedFlatCooling) {
     621         459 :                 if (controllerProps.Action == ControllerAction::NoAction) {
     622           0 :                     controllerProps.Action = ControllerAction::Reverse;
     623         459 :                 } 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         407 :             } else if (controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterSimpleHeating) {
     630         407 :                 if (controllerProps.Action == ControllerAction::NoAction) {
     631           0 :                     controllerProps.Action = ControllerAction::NormalAction;
     632         407 :                 } 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         296 :     AlphArray.deallocate();
     643         296 :     cAlphaFields.deallocate();
     644         296 :     cNumericFields.deallocate();
     645         296 :     NumArray.deallocate();
     646         296 :     lAlphaBlanks.deallocate();
     647         296 :     lNumericBlanks.deallocate();
     648             : 
     649             :     // CR 8253 check that the sensed nodes in the controllers are in flow order in controller List
     650         296 :     CheckControllerListOrder(state);
     651             : 
     652         296 :     if (ErrorsFound) {
     653           0 :         ShowFatalError(state, format("{}Errors found in getting {} input.", RoutineName, CurrentModuleObject));
     654             :     }
     655         499 : }
     656             : 
     657             : // End of Get Input subroutines for the Module
     658             : //******************************************************************************
     659             : 
     660             : // Beginning Initialization Section of the Module
     661             : //******************************************************************************
     662             : 
     663    14980648 : 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    14980648 :     auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
     679    14980648 :     auto &rootFinders = state.dataHVACControllers->RootFinders(ControlNum);
     680             : 
     681    14980648 :     Real64 NoFlowResetValue = 0.0;
     682    14980648 :     PlantUtilities::SetActuatedBranchFlowRate(state, NoFlowResetValue, controllerProps.ActuatedNode, controllerProps.ActuatedNodePlantLoc, true);
     683             : 
     684             :     //  ENDIF
     685             : 
     686             :     // Reset iteration counter and internal variables
     687    14980648 :     controllerProps.NumCalcCalls = 0;
     688    14980648 :     controllerProps.DeltaSensed = 0.0;
     689    14980648 :     controllerProps.SensedValue = 0.0;
     690    14980648 :     controllerProps.ActuatedValue = 0.0;
     691             : 
     692             :     // Reset setpoint-related quantities
     693    14980648 :     controllerProps.SetPointValue = 0.0;
     694    14980648 :     controllerProps.IsSetPointDefinedFlag = false;
     695             : 
     696             :     // MinAvailActuated and MaxAvailActuated set in InitController()
     697    14980648 :     controllerProps.MinAvailActuated = 0.0;
     698    14980648 :     controllerProps.MinAvailSensed = 0.0;
     699    14980648 :     controllerProps.MaxAvailActuated = 0.0;
     700    14980648 :     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    14980648 :     if (DoWarmRestartFlag) {
     705     5172357 :         controllerProps.DoWarmRestartFlag = true;
     706             :     } else {
     707     9808291 :         controllerProps.DoWarmRestartFlag = false;
     708             :         // If no speculative warm restart then reset stored mode and actucated value
     709     9808291 :         controllerProps.Mode = ControllerMode::None;
     710     9808291 :         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    14980648 :     controllerProps.ReusePreviousSolutionFlag = true;
     718             :     // Always reset to false by default. Set in CalcSimpleController() on the first controller iteration.
     719    14980648 :     controllerProps.ReuseIntermediateSolutionFlag = false;
     720             :     // By default not converged
     721    14980648 :     IsConvergedFlag = false;
     722             : 
     723             :     // Reset root finder
     724             :     // This is independent of the processing in InitializeRootFinder() performed in Calc() routine.
     725    14980648 :     rootFinders.StatusFlag = DataRootFinder::RootFinderStatus::None;
     726    14980648 :     rootFinders.CurrentMethodType = DataRootFinder::RootFinderMethod::None;
     727             : 
     728    14980648 :     rootFinders.CurrentPoint.DefinedFlag = false;
     729    14980648 :     rootFinders.CurrentPoint.X = 0.0;
     730    14980648 :     rootFinders.CurrentPoint.Y = 0.0;
     731             : 
     732    14980648 :     rootFinders.MinPoint.DefinedFlag = false;
     733    14980648 :     rootFinders.MaxPoint.DefinedFlag = false;
     734    14980648 :     rootFinders.LowerPoint.DefinedFlag = false;
     735    14980648 :     rootFinders.UpperPoint.DefinedFlag = false;
     736    14980648 : }
     737             : 
     738    35667843 : 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    35667843 :     auto &thisController = state.dataHVACControllers->ControllerProps(ControlNum);
     757    35667843 :     Array1D_bool &MyEnvrnFlag(state.dataHVACControllers->MyEnvrnFlag);
     758    35667843 :     Array1D_bool &MySizeFlag(state.dataHVACControllers->MySizeFlag);
     759    35667843 :     Array1D_bool &MyPlantIndexsFlag(state.dataHVACControllers->MyPlantIndexsFlag);
     760             : 
     761    35667843 :     if (state.dataHVACControllers->InitControllerOneTimeFlag) {
     762             : 
     763         296 :         MyEnvrnFlag.allocate(state.dataHVACControllers->NumControllers);
     764         296 :         MySizeFlag.allocate(state.dataHVACControllers->NumControllers);
     765         296 :         MyPlantIndexsFlag.allocate(state.dataHVACControllers->NumControllers);
     766         296 :         MyEnvrnFlag = true;
     767         296 :         MySizeFlag = true;
     768         296 :         MyPlantIndexsFlag = true;
     769         296 :         state.dataHVACControllers->InitControllerOneTimeFlag = false;
     770             :     }
     771             : 
     772    35667843 :     if (!state.dataGlobal->SysSizingCalc && state.dataHVACControllers->InitControllerSetPointCheckFlag && state.dataHVACGlobal->DoSetPointTest) {
     773             :         // check for missing setpoints
     774        1162 :         for (int ControllerIndex = 1; ControllerIndex <= state.dataHVACControllers->NumControllers; ++ControllerIndex) {
     775         866 :             auto &controllerProps = state.dataHVACControllers->ControllerProps(ControllerIndex);
     776         866 :             int SensedNode = controllerProps.SensedNode;
     777         866 :             switch (controllerProps.ControlVar) {
     778         821 :             case HVACControllers::CtrlVarType::Temperature: { // 'Temperature'
     779         821 :                 if (state.dataLoopNodes->Node(SensedNode).TempSetPoint == DataLoopNode::SensedNodeFlagValue) {
     780          32 :                     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          32 :                         EMSManager::CheckIfNodeSetPointManagedByEMS(
     793          32 :                             state, SensedNode, HVAC::CtrlVarType::Temp, state.dataHVACGlobal->SetPointErrorFlag);
     794          32 :                         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         789 :                     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         821 :             } 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          45 :             case HVACControllers::CtrlVarType::TemperatureAndHumidityRatio: { // 'TemperatureAndHumidityRatio'
     868          45 :                 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          45 :                 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          45 :             } 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         296 :         state.dataHVACControllers->InitControllerSetPointCheckFlag = false;
     961             :     }
     962             : 
     963    35667843 :     if (allocated(state.dataPlnt->PlantLoop) && MyPlantIndexsFlag(ControlNum)) {
     964        1732 :         PlantUtilities::ScanPlantLoopsForNodeNum(
     965         866 :             state, thisController.ControllerName, thisController.ActuatedNode, thisController.ActuatedNodePlantLoc);
     966         866 :         MyPlantIndexsFlag(ControlNum) = false;
     967             :     }
     968             : 
     969    35667843 :     if (!state.dataGlobal->SysSizingCalc && MySizeFlag(ControlNum)) {
     970             : 
     971         866 :         SizeController(state, ControlNum);
     972             : 
     973             :         // Check to make sure that the Minimum Flow rate is less than the max.
     974         866 :         if (thisController.MaxVolFlowActuated == 0.0) {
     975          66 :             ShowWarningError(state,
     976          66 :                              format("{}: Controller:WaterCoil=\"{}\", Maximum Actuated Flow is zero.", RoutineName, thisController.ControllerName));
     977          33 :             thisController.MinVolFlowActuated = 0.0;
     978         833 :         } 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         866 :         switch (thisController.Action) {
     987         407 :         case ControllerAction::NormalAction: {
     988         407 :             RootFinder::SetupRootFinder(state,
     989         407 :                                         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         407 :         } break;
     999         459 :         case ControllerAction::Reverse: {
    1000         459 :             RootFinder::SetupRootFinder(state,
    1001         459 :                                         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         459 :         } 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         866 :         MySizeFlag(ControlNum) = false;
    1016             :     }
    1017             : 
    1018             :     // Set the sensed and actuated node numbers
    1019    35667843 :     int ActuatedNode = thisController.ActuatedNode;
    1020    35667843 :     int SensedNode = thisController.SensedNode;
    1021             : 
    1022             :     // Do the Begin Environment initializations
    1023    35667843 :     if (state.dataGlobal->BeginEnvrnFlag && MyEnvrnFlag(ControlNum)) {
    1024             : 
    1025        5860 :         Real64 rho = FluidProperties::GetDensityGlycol(state,
    1026        5860 :                                                        state.dataPlnt->PlantLoop(thisController.ActuatedNodePlantLoc.loopNum).FluidName,
    1027             :                                                        Constant::CWInitConvTemp,
    1028        5860 :                                                        state.dataPlnt->PlantLoop(thisController.ActuatedNodePlantLoc.loopNum).FluidIndex,
    1029             :                                                        RoutineName);
    1030             : 
    1031        5860 :         thisController.MinActuated = rho * thisController.MinVolFlowActuated;
    1032        5860 :         thisController.MaxActuated = rho * thisController.MaxVolFlowActuated;
    1033             : 
    1034             :         // Turn off scheme to reuse previous solution obtained at last SimAirLoop() call
    1035        5860 :         thisController.ReusePreviousSolutionFlag = false;
    1036             :         // Reset solution trackers
    1037       17580 :         for (auto &e : thisController.SolutionTrackers) {
    1038       11720 :             e.DefinedFlag = false;
    1039       11720 :             e.Mode = ControllerMode::None;
    1040       11720 :             e.ActuatedValue = 0.0;
    1041             :         }
    1042             : 
    1043        5860 :         MyEnvrnFlag(ControlNum) = false;
    1044             :     }
    1045             : 
    1046    35667843 :     if (!state.dataGlobal->BeginEnvrnFlag) {
    1047    35566595 :         MyEnvrnFlag(ControlNum) = true;
    1048             :     }
    1049             : 
    1050    35667843 :     PlantUtilities::SetActuatedBranchFlowRate(state, thisController.NextActuatedValue, ActuatedNode, thisController.ActuatedNodePlantLoc, false);
    1051             : 
    1052             :     // Do the following initializations (every time step): This should be the info from
    1053             :     // the previous components outlets or the node data in this section.
    1054             :     // Load the node data in this section for the component simulation
    1055    35667843 :     IsConvergedFlag = false;
    1056             : 
    1057    35667843 :     switch (thisController.ControlVar) {
    1058    33909667 :     case HVACControllers::CtrlVarType::Temperature: { // 'Temperature'
    1059    33909667 :         thisController.SensedValue = state.dataLoopNodes->Node(SensedNode).Temp;
    1060             :         // Done once per HVAC step
    1061    33909667 :         if (!thisController.IsSetPointDefinedFlag) {
    1062    14089409 :             thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).TempSetPoint;
    1063    14089409 :             thisController.IsSetPointDefinedFlag = true;
    1064             : 
    1065             :             // If there is a fault of water coil SAT sensor
    1066    14090292 :             if (thisController.FaultyCoilSATFlag && (!state.dataGlobal->WarmupFlag) && (!state.dataGlobal->DoingSizing) &&
    1067         883 :                 (!state.dataGlobal->KickOffSimulation)) {
    1068             :                 // calculate the sensor offset using fault information
    1069         883 :                 int FaultIndex = thisController.FaultyCoilSATIndex;
    1070         883 :                 thisController.FaultyCoilSATOffset = state.dataFaultsMgr->FaultsCoilSATSensor(FaultIndex).CalFaultOffsetAct(state);
    1071             :                 // update the SetPointValue
    1072         883 :                 thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).TempSetPoint - thisController.FaultyCoilSATOffset;
    1073             :             }
    1074             :         }
    1075    33909667 :     } break;
    1076     1758176 :     case HVACControllers::CtrlVarType::TemperatureAndHumidityRatio: { // 'TemperatureAndHumidityRatio'
    1077     1758176 :         if (thisController.HumRatCtrlOverride) {
    1078             :             // Humidity ratio control
    1079      255492 :             thisController.SensedValue = state.dataLoopNodes->Node(SensedNode).HumRat;
    1080             :         } else {
    1081             :             // Temperature control
    1082     1502684 :             thisController.SensedValue = state.dataLoopNodes->Node(SensedNode).Temp;
    1083             :         }
    1084     1758176 :         if (!thisController.IsSetPointDefinedFlag) {
    1085      487121 :             if (thisController.HumRatCtrlOverride) {
    1086             :                 // Humidity ratio control
    1087       62078 :                 thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).HumRatMax;
    1088             :             } else {
    1089             :                 // Pure temperature setpoint control strategy
    1090      425043 :                 thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).TempSetPoint;
    1091             :             }
    1092             :             // Finally indicate thate the setpoint has been computed
    1093      487121 :             thisController.IsSetPointDefinedFlag = true;
    1094             :         }
    1095     1758176 :     } break;
    1096           0 :     case HVACControllers::CtrlVarType::HumidityRatio: { // 'HumidityRatio'
    1097           0 :         thisController.SensedValue = state.dataLoopNodes->Node(SensedNode).HumRat;
    1098             :         // Done once per HVAC step
    1099           0 :         if (!thisController.IsSetPointDefinedFlag) {
    1100           0 :             switch (thisController.HumRatCntrlType) {
    1101           0 :             case HVAC::CtrlVarType::MaxHumRat: {
    1102           0 :                 thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).HumRatMax;
    1103           0 :             } break;
    1104           0 :             default: {
    1105           0 :                 thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).HumRatSetPoint;
    1106           0 :             } break;
    1107             :             }
    1108           0 :             thisController.IsSetPointDefinedFlag = true;
    1109             :         }
    1110           0 :     } break;
    1111           0 :     case HVACControllers::CtrlVarType::Flow: { // 'Flow'
    1112           0 :         thisController.SensedValue = state.dataLoopNodes->Node(SensedNode).MassFlowRate;
    1113             :         // Done once per HVAC step
    1114           0 :         if (!thisController.IsSetPointDefinedFlag) {
    1115           0 :             thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).MassFlowRateSetPoint;
    1116           0 :             thisController.IsSetPointDefinedFlag = true;
    1117             :         }
    1118           0 :     } break;
    1119           0 :     default: {
    1120           0 :         ShowFatalError(state, format("Invalid Controller Variable Type={}", ControlVariableTypes(thisController.ControlVar)));
    1121           0 :     } break;
    1122             :     }
    1123             : 
    1124    35667843 :     switch (thisController.ActuatorVar) {
    1125    35667843 :     case HVACControllers::CtrlVarType::Flow: { // 'Flow'
    1126             :         // At the beginning of every time step the value is reset to the User Input
    1127             :         // The interface managers can reset the Max or Min to available values during the time step
    1128             :         // and these will then be the new setpoint limits for the controller to work within.
    1129    35667843 :         thisController.ActuatedValue = state.dataLoopNodes->Node(ActuatedNode).MassFlowRate;
    1130             :         // Compute the currently available min and max bounds for controller.
    1131             :         // Done only once per HVAC step, as it would not make any sense to modify the min/max
    1132             :         // bounds during successive iterations of the root finder.
    1133    35667843 :         if (thisController.NumCalcCalls == 0) {
    1134    14576530 :             thisController.MinAvailActuated = max(state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMinAvail, thisController.MinActuated);
    1135    14576530 :             thisController.MaxAvailActuated = min(state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMaxAvail, thisController.MaxActuated);
    1136             :             // MinActuated is user input for minimum actuated flow, use that value if allowed
    1137             :             // (i.e., reset MinAvailActuated based on Node%MassFlowRateMaxAvail)
    1138    14576530 :             thisController.MinAvailActuated = min(thisController.MinAvailActuated, thisController.MaxAvailActuated);
    1139             :         }
    1140    35667843 :     } break;
    1141           0 :     default: {
    1142           0 :         ShowFatalError(state, format("Invalid Actuator Variable Type={}", ControlVariableTypes(thisController.ActuatorVar)));
    1143           0 :     } break;
    1144             :     }
    1145             : 
    1146             :     // Compute residual for control function using desired setpoint value and current sensed value
    1147             :     // NOTE: The delta sensed value might be wrong if the setpoint has not yet been computed.
    1148             :     //       Make sure not to use it until the setpoint has been computed.
    1149    35667843 :     if (thisController.IsSetPointDefinedFlag) {
    1150    35667843 :         thisController.DeltaSensed = thisController.SensedValue - thisController.SetPointValue;
    1151             :     } else {
    1152           0 :         thisController.DeltaSensed = 0.0;
    1153             :     }
    1154    35667843 : }
    1155             : 
    1156         866 : void SizeController(EnergyPlusData &state, int const ControlNum)
    1157             : {
    1158             : 
    1159             :     // SUBROUTINE INFORMATION:
    1160             :     //       AUTHOR         Fred Buhl
    1161             :     //       DATE WRITTEN   November 2001
    1162             : 
    1163             :     // PURPOSE OF THIS SUBROUTINE:
    1164             :     // This subroutine is for sizing Controller Components for which max flow rates have not been
    1165             :     // specified in the input.
    1166             : 
    1167             :     // METHODOLOGY EMPLOYED:
    1168             :     // Obtains flow rates from the actuated node. Should have been set by the water coils.
    1169             : 
    1170         866 :     auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    1171             : 
    1172         866 :     if (controllerProps.MaxVolFlowActuated == DataSizing::AutoSize) {
    1173       35163 :         for (int WaterCompNum = 1; WaterCompNum <= state.dataSize->SaveNumPlantComps; ++WaterCompNum) {
    1174       34411 :             if (state.dataSize->CompDesWaterFlow(WaterCompNum).SupNode == controllerProps.ActuatedNode) {
    1175         719 :                 controllerProps.MaxVolFlowActuated = state.dataSize->CompDesWaterFlow(WaterCompNum).DesVolFlowRate;
    1176             :             }
    1177             :         }
    1178             : 
    1179         752 :         if (controllerProps.MaxVolFlowActuated < HVAC::SmallWaterVolFlow) {
    1180          33 :             controllerProps.MaxVolFlowActuated = 0.0;
    1181             :         }
    1182         752 :         BaseSizer::reportSizerOutput(state,
    1183             :                                      controllerProps.ControllerType,
    1184             :                                      controllerProps.ControllerName,
    1185             :                                      "Maximum Actuated Flow [m3/s]",
    1186             :                                      controllerProps.MaxVolFlowActuated);
    1187             :     }
    1188             : 
    1189         866 :     if (controllerProps.Offset == DataSizing::AutoSize) {
    1190             :         // 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,
    1191             :         //   4.2 is the ratio of water density to air density, 1000/1.2 is the ratio of water specific heat to
    1192             :         //   air specific heat, and 1.2 converts the result from air volumetric flow rate to air mass flow rate.
    1193             :         //   The assumption is that a temperature tolerance of 0.001 C is good for an air mass flow rate of 1 kg/s.
    1194             :         //   So we divide .001 by the air mass flow rate estimated from the water volumetric flow rate to come up
    1195             :         //   with a temperature tolerance that won't exceed the loop energy error tolerance (10 W).
    1196             :         // Finally we need to take into account the fact that somebody might change the energy tolerance.
    1197         273 :         controllerProps.Offset =
    1198         273 :             (0.001 / (2100.0 * max(controllerProps.MaxVolFlowActuated, HVAC::SmallWaterVolFlow))) * (DataConvergParams::HVACEnergyToler / 10.0);
    1199             :         // do not let the controller tolerance exceed 1/10 of the loop temperature tolerance.
    1200         273 :         controllerProps.Offset = min(0.1 * DataConvergParams::HVACTemperatureToler, controllerProps.Offset);
    1201         273 :         BaseSizer::reportSizerOutput(
    1202             :             state, controllerProps.ControllerType, controllerProps.ControllerName, "Controller Convergence Tolerance", controllerProps.Offset);
    1203             :     }
    1204         866 : }
    1205             : 
    1206    20748407 : void CalcSimpleController(EnergyPlusData &state,
    1207             :                           int const ControlNum,
    1208             :                           bool const FirstHVACIteration,
    1209             :                           bool &IsConvergedFlag,
    1210             :                           bool &IsUpToDateFlag,
    1211             :                           std::string const &ControllerName // used when errors occur
    1212             : )
    1213             : {
    1214             : 
    1215             :     // SUBROUTINE INFORMATION:
    1216             :     //       AUTHOR         Dimitri Curtil
    1217             :     //       DATE WRITTEN   May 2006
    1218             :     //       MODIFIED       Dimitri Curtil (LBNL), May 2006
    1219             :     //                      - Added IsPointFlagDefinedFlag to control when the setpoiont should be
    1220             :     //                        computed depending on the control strategy. This was needed to
    1221             :     //                        trigger the setpoint calculation for the dual temperature and
    1222             :     //                        humidity ratio control strategy only once the air loop has been
    1223             :     //                        evaluated with the max actuated flow.
    1224             :     //                        See the routine InitController() for more details on the setpoint
    1225             :     //                        calculation.
    1226             :     //       MODIFIED       Dimitri Curtil (LBNL), March 2006
    1227             :     //                      - Added IsUpToDateFlag to detect whether or not the air loop
    1228             :     //                        has been evaluated prior the first iteration, which allows
    1229             :     //                        to use the current node values as the first iterate for the root
    1230             :     //                        finder (for COLD RESTART ONLY).
    1231             :     //       MODIFIED       Dimitri Curtil (LBNL), Feb 2006
    1232             :     //                      - Added mode detection capability.
    1233             :     //                      - Now trying min actuated variable first to
    1234             :     //                        detect min-constrained cases in 1 iteration.
    1235             :     //                      - Trying max actuated variable second.
    1236             :     //                        Checks for max-constrained here instead of in
    1237             :     //                        NormActuatedCalc mode.
    1238             :     //                      - Checking for inactive mode as soon as min and max
    1239             :     //                        support points are known instead of in NormActuatedCalc
    1240             :     //                        mode.
    1241             : 
    1242             :     // SUBROUTINE ARGUMENT DEFINITIONS:
    1243             :     // Set to TRUE if current controller is converged; FALSE if more iteration are needed.
    1244             :     // Note that an error in the root finding process can be mapped onto IsConvergedFlag=TRUE
    1245             :     // to avoid continue iterating.
    1246             :     // TRUE if air loop is up-to-date meaning that the current node values are consistent (air loop evaluated)
    1247             :     // Only used within the Calc routines
    1248             : 
    1249    20748407 :     auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    1250    20748407 :     auto &rootFinders = state.dataHVACControllers->RootFinders(ControlNum);
    1251             : 
    1252             :     // Increment counter
    1253    20748407 :     ++controllerProps.NumCalcCalls;
    1254             : 
    1255             :     // Check to see if the component is running; if not converged and return.  This check will be done
    1256             :     // by looking at the component mass flow rate at the sensed node.
    1257    20748407 :     if (state.dataLoopNodes->Node(controllerProps.SensedNode).MassFlowRate == 0.0) {
    1258      885993 :         ExitCalcController(state, ControlNum, DataPrecisionGlobals::constant_zero, ControllerMode::Off, IsConvergedFlag, IsUpToDateFlag);
    1259      885993 :         return;
    1260             :     }
    1261             : 
    1262             :     // Initialize root finder
    1263    19862414 :     if (controllerProps.NumCalcCalls == 1) {
    1264             :         // Set min/max boundaries for root finder on first iteration
    1265     8922298 :         RootFinder::InitializeRootFinder(state, rootFinders, controllerProps.MinAvailActuated,
    1266             :                                          controllerProps.MaxAvailActuated); // XMin | XMax
    1267             : 
    1268             :         // Only allow to reuse initial evaluation if the air loop is up-to-date.
    1269             :         // Set in SolveAirLoopControllers()
    1270             :         // Only reuse initial evaluation if setpoint is already available for the current controller
    1271             :         // Note that in the case of dual temperature and humidity ratio control strategy since the
    1272             :         // setpoint at a later iteration, the initial solution cannot be reused.
    1273             :         // Make sure that the initial candidate value lies within range
    1274    17727555 :         controllerProps.ReuseIntermediateSolutionFlag = IsUpToDateFlag && controllerProps.IsSetPointDefinedFlag &&
    1275     8805257 :                                                         RootFinder::CheckRootFinderCandidate(rootFinders, controllerProps.ActuatedValue);
    1276             : 
    1277     8922298 :         if (controllerProps.ReuseIntermediateSolutionFlag) {
    1278             : 
    1279             :             // Reuse intermediate solution obtained with previous controller for the current HVAC step
    1280             :             // and fire root finder to get next root candidate
    1281     8805257 :             FindRootSimpleController(state, ControlNum, FirstHVACIteration, IsConvergedFlag, IsUpToDateFlag, ControllerName);
    1282             : 
    1283             :         } else {
    1284             :             // Always start with min point by default
    1285      117041 :             controllerProps.NextActuatedValue = rootFinders.MinPoint.X;
    1286             :         }
    1287             : 
    1288             :         // Process current iterate and compute next candidate if needed
    1289             :         // We assume that after the first controller iteration:
    1290             :         // - the setpoint is defined
    1291             :         // - the min and max available bounds are defined
    1292             :         // NOTE: Not explicitly checked but the air mass flow rate must remain constant across successive
    1293             :         //       controller iterations to ensure that the root finder converges.
    1294             :     } else {
    1295             :         // Check that the setpoint is defined
    1296    10940116 :         if (!controllerProps.IsSetPointDefinedFlag) {
    1297           0 :             ShowSevereError(state, format("CalcSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
    1298           0 :             ShowContinueError(state, format(" Controller name=\"{}\"", ControllerName));
    1299           0 :             ShowContinueError(state, " Setpoint is not available/defined.");
    1300           0 :             ShowFatalError(state, "Preceding error causes program termination.");
    1301             :         }
    1302             :         // Monitor invariants across successive controller iterations
    1303             :         // - min bound
    1304             :         // - max bound
    1305    10940116 :         if (rootFinders.MinPoint.X != controllerProps.MinAvailActuated) {
    1306           0 :             ShowSevereError(state, format("CalcSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
    1307           0 :             ShowContinueError(state, format(" Controller name=\"{}\"", ControllerName));
    1308           0 :             ShowContinueError(state, " Minimum bound must remain invariant during successive iterations.");
    1309           0 :             ShowContinueError(state, format(" Minimum root finder point={:.{}T}", rootFinders.MinPoint.X, NumSigDigits));
    1310           0 :             ShowContinueError(state, format(" Minimum avail actuated={:.{}T}", controllerProps.MinAvailActuated, NumSigDigits));
    1311           0 :             ShowFatalError(state, "Preceding error causes program termination.");
    1312             :         }
    1313    10940116 :         if (rootFinders.MaxPoint.X != controllerProps.MaxAvailActuated) {
    1314           0 :             ShowSevereError(state, format("CalcSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
    1315           0 :             ShowContinueError(state, format(" Controller name=\"{}\"", ControllerName));
    1316           0 :             ShowContinueError(state, " Maximum bound must remain invariant during successive iterations.");
    1317           0 :             ShowContinueError(state, format(" Maximum root finder point={:.{}T}", rootFinders.MaxPoint.X, NumSigDigits));
    1318           0 :             ShowContinueError(state, format(" Maximum avail actuated={:.{}T}", controllerProps.MaxAvailActuated, NumSigDigits));
    1319           0 :             ShowFatalError(state, "Preceding error causes program termination.");
    1320             :         }
    1321             : 
    1322             :         // Updates root finder with current iterate and computes next one if needed
    1323    10940116 :         FindRootSimpleController(state, ControlNum, FirstHVACIteration, IsConvergedFlag, IsUpToDateFlag, ControllerName);
    1324             :     }
    1325             : }
    1326             : 
    1327    19745373 : void FindRootSimpleController(EnergyPlusData &state,
    1328             :                               int const ControlNum,
    1329             :                               bool const FirstHVACIteration,
    1330             :                               bool &IsConvergedFlag,
    1331             :                               bool &IsUpToDateFlag,
    1332             :                               std::string const &ControllerName // used when errors occur
    1333             : )
    1334             : {
    1335             : 
    1336             :     // SUBROUTINE INFORMATION:
    1337             :     //       AUTHOR         Dimitri Curtil (LBNL)
    1338             :     //       DATE WRITTEN   March 2006
    1339             : 
    1340             :     // PURPOSE OF THIS SUBROUTINE:
    1341             :     // New routine to fire the root finder using the current actuated and sensed values.
    1342             :     // - Updates IsConvergedFlag depending ou iteration status.
    1343             :     // - Sets next actuated value to try in ControllerProps(ControlNum)%NextActuatedValue
    1344             : 
    1345             :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    1346             :     bool IsDoneFlag; // TRUE if root finder needs to continue iterating, FALSE otherwise.
    1347             :     ControllerMode PreviousSolutionMode;
    1348             :     Real64 PreviousSolutionValue;
    1349             : 
    1350    19745373 :     auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    1351    19745373 :     auto &rootFinders = state.dataHVACControllers->RootFinders(ControlNum);
    1352             : 
    1353             :     // Update root finder with latest solution point
    1354             :     // Check for unconstrained/constrained convergence
    1355             :     // Compute next candidate if not converged yet.
    1356    19745373 :     RootFinder::IterateRootFinder(state,
    1357             :                                   rootFinders,
    1358             :                                   controllerProps.ActuatedValue,
    1359             :                                   controllerProps.DeltaSensed,
    1360             :                                   IsDoneFlag); // root finder's data | X | Y | not used
    1361             : 
    1362             :     // Process root finder if converged or error
    1363             :     // Map root finder status onto controller mode
    1364    19745373 :     switch (rootFinders.StatusFlag) {
    1365    10823090 :     case DataRootFinder::RootFinderStatus::None:
    1366             :     case DataRootFinder::RootFinderStatus::WarningNonMonotonic:
    1367             :     case DataRootFinder::RootFinderStatus::WarningSingular: {
    1368             :         // We need to keep iterating...
    1369             :         int PreviousSolutionIndex;
    1370    10823090 :         IsConvergedFlag = false;
    1371             : 
    1372    10823090 :         if (FirstHVACIteration) {
    1373     4047016 :             PreviousSolutionIndex = 1;
    1374             :         } else {
    1375     6776074 :             PreviousSolutionIndex = 2;
    1376             :         }
    1377             : 
    1378    10823090 :         bool PreviousSolutionDefinedFlag = controllerProps.SolutionTrackers(PreviousSolutionIndex).DefinedFlag;
    1379    10823090 :         PreviousSolutionMode = controllerProps.SolutionTrackers(PreviousSolutionIndex).Mode;
    1380    10823090 :         PreviousSolutionValue = controllerProps.SolutionTrackers(PreviousSolutionIndex).ActuatedValue;
    1381             : 
    1382             :         // Attempt to use root at previous HVAC step in place of the candidate produced by the
    1383             :         // root finder.
    1384             :         // Set in InitController() depending on controller mode at previous HVAC step iteration
    1385             :         // Only attempted during bracketing phase of root finder.
    1386             :         // Check that a previous solution is available
    1387             :         // Make sure that mode of previous solution was active
    1388             :         // Make sure that proposed candidate does not conflict with current min/max range and lower/upper brackets
    1389    15577534 :         bool ReusePreviousSolutionFlag = controllerProps.ReusePreviousSolutionFlag &&
    1390     4754444 :                                          (rootFinders.CurrentMethodType == DataRootFinder::RootFinderMethod::Bracket) &&
    1391    18626566 :                                          PreviousSolutionDefinedFlag && (PreviousSolutionMode == ControllerMode::Active) &&
    1392     3049032 :                                          RootFinder::CheckRootFinderCandidate(rootFinders, PreviousSolutionValue);
    1393             : 
    1394    10823090 :         if (ReusePreviousSolutionFlag) {
    1395             :             // Try to reuse saved solution from previous call to SolveAirLoopControllers()
    1396             :             // instead of candidate proposed by the root finder
    1397     3049032 :             controllerProps.NextActuatedValue = PreviousSolutionValue;
    1398             : 
    1399             :             // Turn off flag since we can only use the previous solution once per HVAC iteration
    1400     3049032 :             controllerProps.ReusePreviousSolutionFlag = false;
    1401             :         } else {
    1402             :             // By default, use candidate value computed by root finder
    1403     7774058 :             controllerProps.NextActuatedValue = rootFinders.XCandidate;
    1404             :         }
    1405             : 
    1406    10823090 :     } break;
    1407     3985360 :     case DataRootFinder::RootFinderStatus::OK:
    1408             :     case DataRootFinder::RootFinderStatus::OKRoundOff: {
    1409             :         // Indicate convergence with base value (used to obtain DeltaSensed!)
    1410     3985360 :         ExitCalcController(state, ControlNum, rootFinders.XCandidate, ControllerMode::Active, IsConvergedFlag, IsUpToDateFlag);
    1411     3985360 :     } break;
    1412     4062331 :     case DataRootFinder::RootFinderStatus::OKMin: {
    1413             :         // Indicate convergence with min value
    1414             :         // Should be the same as ControllerProps(ControlNum)%MinAvailActuated
    1415     4062331 :         ExitCalcController(state, ControlNum, rootFinders.MinPoint.X, ControllerMode::MinActive, IsConvergedFlag, IsUpToDateFlag);
    1416     4062331 :     } break;
    1417      481208 :     case DataRootFinder::RootFinderStatus::OKMax: {
    1418             :         // Indicate convergence with max value
    1419             :         // Should be the same as ControllerProps(ControlNum)%MaxAvailActuated
    1420      481208 :         ExitCalcController(state, ControlNum, rootFinders.MaxPoint.X, ControllerMode::MaxActive, IsConvergedFlag, IsUpToDateFlag);
    1421             : 
    1422      481208 :     } break;
    1423      392731 :     case DataRootFinder::RootFinderStatus::ErrorSingular: {
    1424             :         // Indicate inactive mode with min actuated value
    1425             :         // NOTE: Original code returned Node(ActuatedNode)%MassFlowRateMinAvail
    1426             :         //       This was not portable in case the actuated variable was NOT a mass flow rate!
    1427             :         //       Replaced   Node(ActuatedNode)%MassFlowRateMinAvail
    1428             :         //       with       rootFinders%MinPoint%X
    1429             :         //       which is the same as (see SUBROUTINE InitController)
    1430             :         //                  ControllerProps(ControlNum)%MinAvailActuated
    1431      392731 :         ExitCalcController(state, ControlNum, rootFinders.MinPoint.X, ControllerMode::Inactive, IsConvergedFlag, IsUpToDateFlag);
    1432             : 
    1433             :         // Abnormal case: should never happen
    1434      392731 :     } break;
    1435           0 :     case DataRootFinder::RootFinderStatus::ErrorRange: {
    1436           0 :         ShowSevereError(state, format("FindRootSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
    1437           0 :         ShowContinueError(state, format(" Controller name=\"{}\"", ControllerName));
    1438           0 :         ShowContinueError(state,
    1439           0 :                           format(" Root candidate x={:.{}T} does not lie within the min/max bounds.", controllerProps.ActuatedValue, NumSigDigits));
    1440           0 :         ShowContinueError(state, format(" Min bound is x={:.{}T}", rootFinders.MinPoint.X, NumSigDigits));
    1441           0 :         ShowContinueError(state, format(" Max bound is x={:.{}T}", rootFinders.MaxPoint.X, NumSigDigits));
    1442           0 :         ShowFatalError(state, "Preceding error causes program termination.");
    1443             : 
    1444             :         // Abnormal case: should never happen
    1445           0 :     } break;
    1446           0 :     case DataRootFinder::RootFinderStatus::ErrorBracket: {
    1447           0 :         ShowSevereError(state, format("FindRootSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
    1448           0 :         ShowContinueError(state, format(" Controller name={}", controllerProps.ControllerName));
    1449           0 :         ShowContinueError(state, fmt::format(" Controller action={}", state.dataHVACCtrl->ActionTypes[static_cast<int>(controllerProps.Action)]));
    1450           0 :         ShowContinueError(
    1451           0 :             state, format(" Root candidate x={:.{}T} does not lie within the lower/upper brackets.", controllerProps.ActuatedValue, NumSigDigits));
    1452           0 :         if (rootFinders.LowerPoint.DefinedFlag) {
    1453           0 :             ShowContinueError(state, format(" Lower bracket is x={:.{}T}", rootFinders.LowerPoint.X, NumSigDigits));
    1454             :         }
    1455           0 :         if (rootFinders.UpperPoint.DefinedFlag) {
    1456           0 :             ShowContinueError(state, format(" Upper bracket is x={:.{}T}", rootFinders.UpperPoint.X, NumSigDigits));
    1457             :         }
    1458           0 :         ShowFatalError(state, "Preceding error causes program termination.");
    1459             : 
    1460             :         // Detected control function with wrong action between the min and max points.
    1461             :         // Should never happen: probably indicative of some serious problems in IDFs
    1462             :         // NOTE: This approach is more robust and consistent than what was done in version 1.3.
    1463             :         //       Indeed, such a function with the wrong action characteristic would have silently returned
    1464             :         //       either of the following values depending on the specified action:
    1465             :         //       - NORMAL 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             :         //       - REVERSE ACTION:
    1469             :         //         - If y(xMin) < ySetPoint && y(xMax) > y(xMin), then  x = xMin
    1470             :         //         - If y(xMin) > ySetPoint && y(xMax) > y(xMin), then  x = xMax
    1471           0 :     } break;
    1472         653 :     case DataRootFinder::RootFinderStatus::ErrorSlope: {
    1473         653 :         if (!state.dataGlobal->WarmupFlag && controllerProps.BadActionErrCount == 0) {
    1474           4 :             ++controllerProps.BadActionErrCount;
    1475           4 :             ShowSevereError(state, format("FindRootSimpleController: Controller error for controller = \"{}\"", ControllerName));
    1476           4 :             ShowContinueErrorTimeStamp(state, "");
    1477           4 :             ShowContinueError(state,
    1478           4 :                               fmt::format("  Controller function is inconsistent with user specified controller action = {}",
    1479           4 :                                           state.dataHVACCtrl->ActionTypes[static_cast<int>(controllerProps.Action)]));
    1480           4 :             ShowContinueError(state, "  Actuator will be set to maximum action");
    1481           4 :             ShowContinueError(state, format("Controller control type={}", ControlVariableTypes(controllerProps.ControlVar)));
    1482           4 :             if (controllerProps.ControlVar == CtrlVarType::Temperature) {
    1483           4 :                 ShowContinueError(state, format("Controller temperature setpoint = {:.2T} [C]", controllerProps.SetPointValue));
    1484           4 :                 ShowContinueError(state, format("Controller sensed temperature = {:.2T} [C]", controllerProps.SensedValue));
    1485           0 :             } else if (controllerProps.ControlVar == CtrlVarType::HumidityRatio) {
    1486           0 :                 ShowContinueError(state, format("Controller humidity ratio setpoint = {:.2T} [kgWater/kgDryAir]", controllerProps.SetPointValue));
    1487           0 :                 ShowContinueError(state, format("Controller sensed humidity ratio = {:.2T} [kgWater/kgDryAir]", controllerProps.SensedValue));
    1488           0 :             } else if (controllerProps.ControlVar == CtrlVarType::TemperatureAndHumidityRatio) {
    1489           0 :                 if (controllerProps.HumRatCtrlOverride) {
    1490           0 :                     ShowContinueError(state, "Humidity control is active.");
    1491           0 :                     ShowContinueError(state, format("Controller humidity ratio setpoint = {:.2T} [kgWater/kgDryAir]", controllerProps.SetPointValue));
    1492           0 :                     ShowContinueError(state, format("Controller sensed humidity ratio = {:.2T} [kgWater/kgDryAir]", controllerProps.SensedValue));
    1493           0 :                     ShowContinueError(state,
    1494           0 :                                       format("Controller humidity ratio setpoint dew-point temperature = {:.2T} [C]",
    1495           0 :                                              Psychrometrics::PsyTdpFnWPb(state, controllerProps.SetPointValue, state.dataEnvrn->OutBaroPress)));
    1496           0 :                     ShowContinueError(
    1497             :                         state,
    1498           0 :                         format("Controller temperature setpoint = {:.2T} [C]", state.dataLoopNodes->Node(controllerProps.SensedNode).TempSetPoint));
    1499           0 :                     ShowContinueError(
    1500           0 :                         state, format("Controller sensed temperature = {:.2T} [C]", state.dataLoopNodes->Node(controllerProps.SensedNode).Temp));
    1501             :                 } else {
    1502           0 :                     ShowContinueError(state, format("Controller temperature setpoint = {:.2T} [C]", controllerProps.SetPointValue));
    1503           0 :                     ShowContinueError(state, format("Controller sensed temperature = {:.2T} [C]", controllerProps.SensedValue));
    1504             :                 }
    1505           0 :             } else if (controllerProps.ControlVar == CtrlVarType::Flow) {
    1506           0 :                 ShowContinueError(state, format("Controller mass flow rate setpoint = {:.2T} [kg/s]", controllerProps.SetPointValue));
    1507           0 :                 ShowContinueError(state, format("Controller sensed mass flow rate = {:.2T} [kg/s]", controllerProps.SensedValue));
    1508             :             } else {
    1509             :                 // bad control variable input checked in input routine
    1510             :             }
    1511           4 :             if (controllerProps.ActuatorVar == CtrlVarType::Flow) {
    1512           4 :                 ShowContinueError(state, format("Controller actuator mass flow rate set to {:.2T} [kg/s]", controllerProps.MaxAvailActuated));
    1513           4 :                 if (controllerProps.ControlVar == CtrlVarType::Temperature ||
    1514           0 :                     controllerProps.ControlVar == CtrlVarType::TemperatureAndHumidityRatio) {
    1515           8 :                     ShowContinueError(
    1516           8 :                         state, format("Controller actuator temperature = {:.2T} [C]", state.dataLoopNodes->Node(controllerProps.ActuatedNode).Temp));
    1517           4 :                     if (controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterCooling ||
    1518           3 :                         controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterDetailedFlatCooling) {
    1519           1 :                         if (controllerProps.HumRatCtrlOverride) {
    1520           0 :                             ShowContinueError(state,
    1521             :                                               "The entering chilled water temperature (controller actuator temperature) should be below the humidity "
    1522             :                                               "ratio setpoint dew-point temperature.");
    1523             :                         } else {
    1524           1 :                             ShowContinueError(
    1525             :                                 state,
    1526             :                                 "The entering chilled water temperature (controller actuator temperature) should be below the setpoint temperature.");
    1527             :                         }
    1528           3 :                     } else if (controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterSimpleHeating) {
    1529           3 :                         ShowContinueError(
    1530             :                             state, "The entering hot water temperature (controller actuator temperature) should be above the setpoint temperature");
    1531             :                     }
    1532             :                 }
    1533             :             } else {
    1534             :                 // bad actuator variable input checked in input routine
    1535             :             }
    1536         649 :         } else if (!state.dataGlobal->WarmupFlag) {
    1537           0 :             ++controllerProps.BadActionErrCount;
    1538           0 :             ShowRecurringSevereErrorAtEnd(state,
    1539           0 :                                           "FindRootSimpleController: Previous controller action error continues for controller = " + ControllerName,
    1540           0 :                                           controllerProps.BadActionErrIndex);
    1541             :         } else {
    1542             :             // do nothing
    1543             :         }
    1544             :         // Indicate convergence with min value
    1545             :         // Should be the same as ControllerProps(ControlNum)%MaxAvailActuated
    1546         653 :         ExitCalcController(state, ControlNum, rootFinders.MaxPoint.X, ControllerMode::MaxActive, IsConvergedFlag, IsUpToDateFlag);
    1547         653 :     } break;
    1548           0 :     default: {
    1549             :         // Should never happen
    1550           0 :         ShowSevereError(state, format("FindRootSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
    1551           0 :         ShowContinueError(state, format(" Controller name={}", ControllerName));
    1552           0 :         ShowContinueError(state, format(" Unrecognized root finder status flag={}", rootFinders.StatusFlag));
    1553           0 :         ShowFatalError(state, "Preceding error causes program termination.");
    1554           0 :     } break;
    1555             :     }
    1556    19745373 : }
    1557             : 
    1558    14918570 : void CheckSimpleController(EnergyPlusData &state, int const ControlNum, bool &IsConvergedFlag)
    1559             : {
    1560             : 
    1561             :     // SUBROUTINE INFORMATION:
    1562             :     //       AUTHOR         Dimitri Curtil (LBNL)
    1563             :     //       DATE WRITTEN   Feb 2006
    1564             : 
    1565             :     // PURPOSE OF THIS SUBROUTINE:
    1566             :     // New routine used to detect whether controller can be considered converged
    1567             :     // depending on its mode of operation.
    1568             :     // Used after all controllers on an air loop have been solved in order
    1569             :     // to make sure that final air loop state still represents a converged
    1570             :     // state.
    1571             :     // PRECONDITION: Setpoint must be known. See ControllerProps%IsSetPointDefinedFlag
    1572             : 
    1573    14918570 :     auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    1574    14918570 :     auto &rootFinders = state.dataHVACControllers->RootFinders(ControlNum);
    1575             : 
    1576             :     // Default initialization: assuming no convergence unless detected in the following code!
    1577    14918570 :     IsConvergedFlag = false;
    1578             : 
    1579    14918570 :     switch (controllerProps.Mode) {
    1580     1672272 :     case ControllerMode::Off: {
    1581             :         // Check whether the component is running
    1582             :         // This check is performed by looking at the component mass flow rate at the sensed node.
    1583             :         // Since the components have been simulated before getting here, if they are zero they should be OFF.
    1584     1672272 :         if (state.dataLoopNodes->Node(controllerProps.SensedNode).MassFlowRate == 0.0) {
    1585     1672256 :             if (controllerProps.ActuatedValue == 0.0) {
    1586     1672256 :                 IsConvergedFlag = true;
    1587     1672256 :                 return;
    1588             :             }
    1589             :         }
    1590          16 :     } break;
    1591      611300 :     case ControllerMode::Inactive: {
    1592             :         // Controller component NOT available (ie, inactive)
    1593             :         // Make sure that the actuated variable is still equal to the node min avail
    1594             :         // NOTE: Replaced Node(ActuatedNode)%MassFlowRateMinAvail         in release 1.3
    1595             :         //       with     ControllerProps(ControlNum)%MinAvailActuated    in release 1.4
    1596      611300 :         if (controllerProps.ActuatedValue == controllerProps.MinAvailActuated) {
    1597      611300 :             IsConvergedFlag = true;
    1598      611300 :             return;
    1599             :         }
    1600           0 :     } break;
    1601     5916793 :     case ControllerMode::MinActive: {
    1602             :         // Check for min constrained convergence
    1603     5916793 :         if (CheckMinActiveController(state, ControlNum)) {
    1604     5070034 :             IsConvergedFlag = true;
    1605     5070034 :             return;
    1606             :         }
    1607             :         // Check for unconstrained convergence assuming that there is more than one controller controlling
    1608             :         // the same sensed node and that the other controller was able to meet the setpoint although this one
    1609             :         // was min-constrained.
    1610      846759 :         if (RootFinder::CheckRootFinderConvergence(rootFinders, controllerProps.DeltaSensed)) {
    1611             :             // Indicate convergence with base value (used to compute DeltaSensed!)
    1612       82260 :             IsConvergedFlag = true;
    1613       82260 :             return;
    1614             :         }
    1615      764499 :     } break;
    1616      826080 :     case ControllerMode::MaxActive: {
    1617             :         // Check for max constrained convergence
    1618      826080 :         if (CheckMaxActiveController(state, ControlNum)) {
    1619      705579 :             IsConvergedFlag = true;
    1620      705579 :             return;
    1621             :         }
    1622             :         // Check for unconstrained convergence assuming that there is more than one controller controlling
    1623             :         // the same sensed node and that the other controller was able to meet the setpoint although this one
    1624             :         // was max-constrained.
    1625      120501 :         if (RootFinder::CheckRootFinderConvergence(rootFinders, controllerProps.DeltaSensed)) {
    1626             :             // Indicate convergence with base value (used to compute DeltaSensed!)
    1627          93 :             IsConvergedFlag = true;
    1628          93 :             return;
    1629             :         }
    1630      120408 :     } break;
    1631     5892110 :     case ControllerMode::Active: {
    1632             :         // Check min constraint on actuated variable
    1633     5892110 :         if (controllerProps.ActuatedValue < controllerProps.MinAvailActuated) {
    1634        5123 :             IsConvergedFlag = false;
    1635        5123 :             return;
    1636             :         }
    1637             :         // Check max constraint on actuated variable
    1638     5886987 :         if (controllerProps.ActuatedValue > controllerProps.MaxAvailActuated) {
    1639           0 :             IsConvergedFlag = false;
    1640           0 :             return;
    1641             :         }
    1642             : 
    1643             :         // Check for unconstrained convergence
    1644             :         // Equivalent to:
    1645             :         // IF ((ABS(ControllerProps(ControlNum)%DeltaSensed) .LE. ControllerProps(ControlNum)%Offset)) THEN
    1646             :         // NOTE: If setpoint has changed since last call, then the following test will most likely fail.
    1647     5886987 :         if (RootFinder::CheckRootFinderConvergence(rootFinders, controllerProps.DeltaSensed)) {
    1648             :             // Indicate convergence with base value (used to compute DeltaSensed!)
    1649     4535898 :             IsConvergedFlag = true;
    1650     4535898 :             return;
    1651             :         }
    1652             :         // Check for min constrained convergence
    1653     1351089 :         if (CheckMinActiveController(state, ControlNum)) {
    1654       57755 :             IsConvergedFlag = true;
    1655       57755 :             return;
    1656             :         }
    1657             :         // Check for max constrained convergence
    1658     1293334 :         if (CheckMaxActiveController(state, ControlNum)) {
    1659       19784 :             IsConvergedFlag = true;
    1660       19784 :             return;
    1661             :         }
    1662     1273550 :     } break;
    1663          15 :     default: {
    1664             :         // Can only happen if controller is not converged after MaxIter in SolveAirLoopControllers()
    1665             :         // which will produce ControllerProps(ControlNum)%Mode = iModeNone
    1666          15 :         IsConvergedFlag = false;
    1667          15 :     } break;
    1668             :     }
    1669             : }
    1670             : 
    1671     7267882 : bool CheckMinActiveController(EnergyPlusData &state, int const ControlNum)
    1672             : {
    1673             :     // FUNCTION INFORMATION:
    1674             :     //       AUTHOR         Dimitri Curtil
    1675             :     //       DATE WRITTEN   May 2006
    1676             : 
    1677             :     // PURPOSE OF THIS FUNCTION:
    1678             :     // Returns true if controller is min-constrained. false otherwise.
    1679             : 
    1680     7267882 :     auto const &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    1681             : 
    1682             :     // Check that actuated value is the min avail actuated value
    1683     7267882 :     if (controllerProps.ActuatedValue != controllerProps.MinAvailActuated) {
    1684     1074757 :         return false;
    1685             :     }
    1686             : 
    1687     6193125 :     switch (controllerProps.Action) {
    1688     4862438 :     case ControllerAction::NormalAction: { // "NORMAL"
    1689             :         // Check for min constrained convergence
    1690     4862438 :         if (controllerProps.SetPointValue <= controllerProps.SensedValue) {
    1691     3858775 :             return true;
    1692             :         }
    1693     1003663 :     } break;
    1694     1330687 :     case ControllerAction::Reverse: { // "REVERSE"
    1695             :         // Check for min constrained convergence
    1696     1330687 :         if (controllerProps.SetPointValue >= controllerProps.SensedValue) {
    1697     1269014 :             return true;
    1698             :         }
    1699       61673 :     } break;
    1700           0 :     default: {
    1701             :         // Should never happen
    1702           0 :         ShowSevereError(state, format("CheckMinActiveController: Invalid controller action during {}.", CreateHVACStepFullString(state)));
    1703           0 :         ShowContinueError(state, format("CheckMinActiveController: Controller name={}", controllerProps.ControllerName));
    1704           0 :         ShowContinueError(state, R"(CheckMinActiveController: Valid choices are "NORMAL" or "REVERSE")");
    1705           0 :         ShowFatalError(state, "CheckMinActiveController: Preceding error causes program termination.");
    1706           0 :     } break;
    1707             :     }
    1708             : 
    1709     1065336 :     return false;
    1710             : }
    1711             : 
    1712     2119414 : bool CheckMaxActiveController(EnergyPlusData &state, int const ControlNum)
    1713             : {
    1714             :     // FUNCTION INFORMATION:
    1715             :     //       AUTHOR         Dimitri Curtil
    1716             :     //       DATE WRITTEN   May 2006
    1717             : 
    1718             :     // PURPOSE OF THIS FUNCTION:
    1719             :     // Returns true if controller is max-constrained. false otherwise.
    1720             : 
    1721     2119414 :     auto &ControllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    1722             : 
    1723             :     // Check that actuated value is the max avail actuated value
    1724     2119414 :     if (ControllerProps.ActuatedValue != ControllerProps.MaxAvailActuated) {
    1725     1269356 :         return false;
    1726             :     }
    1727             : 
    1728      850058 :     switch (ControllerProps.Action) {
    1729      242939 :     case ControllerAction::NormalAction: { // "NORMAL"
    1730             :         // Check for max constrained convergence
    1731      242939 :         if (ControllerProps.SetPointValue >= ControllerProps.SensedValue) {
    1732      238243 :             return true;
    1733             :         }
    1734        4696 :     } break;
    1735      607119 :     case ControllerAction::Reverse: { // "REVERSE"
    1736             :         // Check for max constrained convergence
    1737      607119 :         if (ControllerProps.SetPointValue <= ControllerProps.SensedValue) {
    1738      487120 :             return true;
    1739             :         }
    1740      119999 :     } break;
    1741           0 :     default: {
    1742             :         // Should never happen
    1743           0 :         ShowSevereError(state, format("CheckMaxActiveController: Invalid controller action during {}.", CreateHVACStepFullString(state)));
    1744           0 :         ShowContinueError(state, format("CheckMaxActiveController: Controller name={}", ControllerProps.ControllerName));
    1745           0 :         ShowContinueError(state, R"(CheckMaxActiveController: Valid choices are "NORMAL" or "REVERSE")");
    1746           0 :         ShowFatalError(state, "CheckMaxActiveController: Preceding error causes program termination.");
    1747           0 :     } break;
    1748             :     }
    1749             : 
    1750      124695 :     return false;
    1751             : }
    1752             : 
    1753    14918570 : void SaveSimpleController(EnergyPlusData &state, int const ControlNum, bool const FirstHVACIteration, bool const IsConvergedFlag)
    1754             : {
    1755             : 
    1756             :     // SUBROUTINE INFORMATION:
    1757             :     //       AUTHOR         Dimitri Curtil
    1758             :     //       DATE WRITTEN   April 2006
    1759             : 
    1760             :     // PURPOSE OF THIS SUBROUTINE:
    1761             :     // Updates solution trackers if simple controller is converged.
    1762             : 
    1763    14918570 :     auto &ControllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    1764             : 
    1765             :     // Save solution and mode for next call only if converged
    1766    14918570 :     if (IsConvergedFlag) {
    1767    12754959 :         int PreviousSolutionIndex = FirstHVACIteration ? 1 : 2;
    1768             : 
    1769    12754959 :         if (ControllerProps.Mode == ControllerMode::Active) {
    1770     4613437 :             ControllerProps.SolutionTrackers(PreviousSolutionIndex).DefinedFlag = true;
    1771     4613437 :             ControllerProps.SolutionTrackers(PreviousSolutionIndex).Mode = ControllerProps.Mode;
    1772     4613437 :             ControllerProps.SolutionTrackers(PreviousSolutionIndex).ActuatedValue = ControllerProps.NextActuatedValue;
    1773             :         } else {
    1774     8141522 :             ControllerProps.SolutionTrackers(PreviousSolutionIndex).DefinedFlag = false;
    1775     8141522 :             ControllerProps.SolutionTrackers(PreviousSolutionIndex).Mode = ControllerProps.Mode;
    1776     8141522 :             ControllerProps.SolutionTrackers(PreviousSolutionIndex).ActuatedValue = ControllerProps.NextActuatedValue;
    1777             :         }
    1778             :     }
    1779    14918570 : }
    1780             : 
    1781    35979571 : void UpdateController(EnergyPlusData &state, int const ControlNum)
    1782             : {
    1783             :     // PURPOSE OF THIS SUBROUTINE:
    1784             :     // This subroutine updates the actuated node with the next candidate value.
    1785             : 
    1786    35979571 :     auto &ControllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    1787             : 
    1788             :     // Set the actuated node of the Controller
    1789    35979571 :     switch (ControllerProps.ActuatorVar) {
    1790    35979571 :     case CtrlVarType::Flow: { // 'Flow'
    1791    35979571 :         PlantUtilities::SetActuatedBranchFlowRate(
    1792    35979571 :             state, ControllerProps.NextActuatedValue, ControllerProps.ActuatedNode, ControllerProps.ActuatedNodePlantLoc, false);
    1793             :         //     Node(ActuatedNode)%MassFlowRate = ControllerProps(ControlNum)%NextActuatedValue
    1794    35979571 :     } break;
    1795           0 :     default: {
    1796           0 :         ShowFatalError(state, format("UpdateController: Invalid Actuator Variable Type={}", ControlVariableTypes(ControllerProps.ActuatorVar)));
    1797           0 :     } break;
    1798             :     }
    1799    35979571 : }
    1800             : 
    1801    20748407 : void CheckTempAndHumRatCtrl(EnergyPlusData &state, int const ControlNum, bool &IsConvergedFlag)
    1802             : {
    1803             :     {
    1804    20748407 :         auto &thisController = state.dataHVACControllers->ControllerProps(ControlNum);
    1805    20748407 :         if (IsConvergedFlag) {
    1806     9808276 :             if (thisController.ControlVar == CtrlVarType::TemperatureAndHumidityRatio) {
    1807             :                 // For temperature and humidity control, after temperature control is converged, check if humidity setpoint is met
    1808      487061 :                 if (!thisController.HumRatCtrlOverride) {
    1809             :                     // For humidity control tolerance, always use 0.0001 which is roughly equivalent to a 0.015C change in dewpoint
    1810      424998 :                     if (state.dataLoopNodes->Node(thisController.SensedNode).HumRat >
    1811      424998 :                         (state.dataLoopNodes->Node(thisController.SensedNode).HumRatMax + 1.0e-5)) {
    1812             :                         // Turn on humidity control and restart controller
    1813       62078 :                         IsConvergedFlag = false;
    1814       62078 :                         thisController.HumRatCtrlOverride = true;
    1815       62078 :                         if (thisController.Action == ControllerAction::Reverse) {
    1816             :                             // Cooling coil controller should always be Reverse, but skip this if not
    1817       62078 :                             RootFinder::SetupRootFinder(state,
    1818       62078 :                                                         state.dataHVACControllers->RootFinders(ControlNum),
    1819             :                                                         DataRootFinder::Slope::Decreasing,
    1820             :                                                         DataRootFinder::RootFinderMethod::FalsePosition,
    1821             :                                                         DataPrecisionGlobals::constant_zero,
    1822             :                                                         1.0e-6,
    1823             :                                                         1.0e-5);
    1824             :                         }
    1825             :                         // Do a cold start reset, same as iControllerOpColdStart
    1826       62078 :                         ResetController(state, ControlNum, false, IsConvergedFlag);
    1827             :                     }
    1828             :                 }
    1829             :             }
    1830             :         }
    1831             :     }
    1832    20748407 : }
    1833             : 
    1834     9808276 : void ExitCalcController(EnergyPlusData &state,
    1835             :                         int const ControlNum,
    1836             :                         Real64 const NextActuatedValue,
    1837             :                         ControllerMode const Mode,
    1838             :                         bool &IsConvergedFlag,
    1839             :                         bool &IsUpToDateFlag)
    1840             : {
    1841             : 
    1842             :     // SUBROUTINE INFORMATION:
    1843             :     //       AUTHOR         Dimitri Curtil
    1844             :     //       DATE WRITTEN   February 06
    1845             : 
    1846             :     // PURPOSE OF THIS SUBROUTINE:
    1847             :     // Only called when controller is considered as "converged", meaning that we do no longer
    1848             :     // need to continue iterating.
    1849             : 
    1850             :     // METHODOLOGY EMPLOYED:
    1851             :     // Updates:
    1852             :     // - next actuated value
    1853             :     // - controller mode
    1854             :     // - IsConvergedFlag
    1855             :     // - IsUpToDateFlag
    1856             : 
    1857     9808276 :     auto &ControllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    1858             : 
    1859     9808276 :     ControllerProps.NextActuatedValue = NextActuatedValue;
    1860     9808276 :     ControllerProps.Mode = Mode;
    1861     9808276 :     IsConvergedFlag = true;
    1862             : 
    1863             :     // Set IsUpToDateFlag upon exiting to indicate caller whether or not the air loop needs to be
    1864             :     // re-simulated with the current candidate value, ie ControllerProps(ControlNum)%NextActuatedValue
    1865     9808276 :     if (ControllerProps.ActuatedValue != ControllerProps.NextActuatedValue) {
    1866      392731 :         IsUpToDateFlag = false;
    1867             :     } else {
    1868     9415545 :         IsUpToDateFlag = true;
    1869             :     }
    1870     9808276 : }
    1871             : 
    1872           0 : void TrackAirLoopControllers(EnergyPlusData &state,
    1873             :                              int const AirLoopNum,
    1874             :                              DataHVACControllers::ControllerWarmRestart const WarmRestartStatus,
    1875             :                              int const AirLoopIterMax,
    1876             :                              int const AirLoopIterTot,
    1877             :                              int const AirLoopNumCalls)
    1878             : {
    1879             : 
    1880             :     // SUBROUTINE INFORMATION:
    1881             :     //       AUTHOR         Dimitri Curtil
    1882             :     //       DATE WRITTEN   April 2006
    1883             : 
    1884             :     // PURPOSE OF THIS SUBROUTINE:
    1885             :     // Updates runtime statistics for controllers on the specified air loop.
    1886             :     // Used to produce objective metrics when analyzing runtime performance
    1887             :     // of HVAC controllers for different implementations.
    1888             : 
    1889             :     // SUBROUTINE PARAMETER DEFINITIONS:
    1890             :     // See CONTROLLER_WARM_RESTART_<> parameters in DataHVACControllers.cc
    1891             :     // If Status<0, no speculative warm restart.
    1892             :     // If Status==0, speculative warm restart failed.
    1893             :     // If Status>0, speculative warm restart succeeded.
    1894             :     // Max number of iterations performed by controllers on this air loop (per call to SimAirLoop)
    1895             :     // Aggregated number of iterations performed by controllers on this air loop (per call to SimAirLoop)
    1896             :     // Number of times SimAirLoopComponents() has been invoked
    1897             : 
    1898           0 :     auto &airLoopStats = state.dataHVACControllers->AirLoopStats(AirLoopNum);
    1899             : 
    1900             :     // If no controllers on this air loop then we have nothing to do
    1901           0 :     if (state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers == 0) return;
    1902             :     // To avoid tracking statistics in case of no air loop or no HVAC controllers are defined
    1903           0 :     if (state.dataHVACControllers->NumAirLoopStats == 0) return;
    1904             : 
    1905             :     // Update performance statistics for air loop
    1906           0 :     ++airLoopStats.NumCalls;
    1907             : 
    1908           0 :     switch (WarmRestartStatus) {
    1909           0 :     case DataHVACControllers::ControllerWarmRestart::Success: {
    1910           0 :         ++airLoopStats.NumSuccessfulWarmRestarts;
    1911           0 :     } break;
    1912           0 :     case DataHVACControllers::ControllerWarmRestart::Fail: {
    1913           0 :         ++airLoopStats.NumFailedWarmRestarts;
    1914           0 :     } break;
    1915           0 :     default: {
    1916             :         // Nothing to do if no speculative warm restart used
    1917           0 :     } break;
    1918             :     }
    1919             : 
    1920           0 :     airLoopStats.TotSimAirLoopComponents += AirLoopNumCalls;
    1921             : 
    1922           0 :     airLoopStats.MaxSimAirLoopComponents = max(airLoopStats.MaxSimAirLoopComponents, AirLoopNumCalls);
    1923             : 
    1924           0 :     airLoopStats.TotIterations += AirLoopIterTot;
    1925             : 
    1926           0 :     airLoopStats.MaxIterations = max(airLoopStats.MaxIterations, AirLoopIterMax);
    1927             : 
    1928             :     // Update performance statistics for each controller on air loop
    1929           0 :     for (int ControllerNum = 1; ControllerNum <= state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers; ++ControllerNum) {
    1930           0 :         TrackAirLoopController(state, AirLoopNum, ControllerNum);
    1931             :     }
    1932             : }
    1933             : 
    1934           0 : void TrackAirLoopController(EnergyPlusData &state,
    1935             :                             int const AirLoopNum,       // Air loop index
    1936             :                             int const AirLoopControlNum // Controller index on this air loop
    1937             : )
    1938             : {
    1939             : 
    1940             :     // SUBROUTINE INFORMATION:
    1941             :     //       AUTHOR         Dimitri Curtil
    1942             :     //       DATE WRITTEN   April 2006
    1943             : 
    1944             :     // PURPOSE OF THIS SUBROUTINE:
    1945             :     // Updates runtime statistics for the specified controller.
    1946             :     // Used to produce objective metrics when analyzing runtime performance
    1947             :     // of HVAC controllers for different implementations.
    1948             : 
    1949           0 :     auto &airLoopStats = state.dataHVACControllers->AirLoopStats(AirLoopNum);
    1950           0 :     int ControlIndex = state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).ControllerIndex(AirLoopControlNum);
    1951           0 :     auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlIndex);
    1952             : 
    1953             :     // We use NumCalcCalls instead of the iteration counter used in SolveAirLoopControllers()
    1954             :     // to avoid having to call TrackAirLoopController() directly from SolveAirLoopControllers().
    1955             :     // The 2 counters should be the same anyway as NumCalcCalls is first reset to zero and
    1956             :     // incremented each time ManageControllers() is invoked with iControllerOpIterate
    1957           0 :     int IterationCount = controllerProps.NumCalcCalls;
    1958           0 :     ControllerMode Mode = controllerProps.Mode;
    1959             : 
    1960           0 :     if (Mode != ControllerMode::None) {
    1961             : 
    1962           0 :         ++airLoopStats.ControllerStats(AirLoopControlNum).NumCalls(static_cast<int>(Mode));
    1963             : 
    1964           0 :         airLoopStats.ControllerStats(AirLoopControlNum).TotIterations(static_cast<int>(Mode)) += IterationCount;
    1965             : 
    1966           0 :         airLoopStats.ControllerStats(AirLoopControlNum).MaxIterations(static_cast<int>(Mode)) =
    1967           0 :             max(airLoopStats.ControllerStats(AirLoopControlNum).MaxIterations(static_cast<int>(Mode)), IterationCount);
    1968             :     }
    1969           0 : }
    1970             : 
    1971         794 : void DumpAirLoopStatistics(EnergyPlusData &state)
    1972             : {
    1973             : 
    1974             :     // SUBROUTINE INFORMATION:
    1975             :     //       AUTHOR         Dimitri Curtil
    1976             :     //       DATE WRITTEN   April 2006
    1977             : 
    1978             :     // PURPOSE OF THIS SUBROUTINE:
    1979             :     // Writes runtime statistics for controllers on all air loops
    1980             :     // to a CSV file named "statistics.HVACControllers.csv".
    1981             : 
    1982             :     // Detect if statistics have been generated or not for this run
    1983         794 :     if (!state.dataSysVars->TrackAirLoopEnvFlag) {
    1984         794 :         return;
    1985             :     }
    1986             : 
    1987           0 :     InputOutputFilePath StatisticsFilePath{"statistics.HVACControllers.csv"};
    1988           0 :     auto statisticsFile = StatisticsFilePath.open(state, "DumpAirLoopStatistics"); // (THIS_AUTO_OK)
    1989             : 
    1990             :     // note that the AirLoopStats object does not seem to be initialized when this code
    1991             :     // is executed and it causes a crash here
    1992           0 :     for (int AirLoopNum = 1; AirLoopNum <= state.dataHVACGlobal->NumPrimaryAirSys; ++AirLoopNum) {
    1993           0 :         WriteAirLoopStatistics(
    1994           0 :             state, statisticsFile, state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum), state.dataHVACControllers->AirLoopStats(AirLoopNum));
    1995             :     }
    1996           0 : }
    1997             : 
    1998           0 : void WriteAirLoopStatistics(EnergyPlusData &state,
    1999             :                             InputOutputFile &statisticsFile,
    2000             :                             DefinePrimaryAirSystem const &ThisPrimaryAirSystem,
    2001             :                             AirLoopStatsType const &ThisAirLoopStats)
    2002             : {
    2003             : 
    2004             :     // SUBROUTINE INFORMATION:
    2005             :     //       AUTHOR         Dimitri Curtil
    2006             :     //       DATE WRITTEN   April 2006
    2007             : 
    2008             :     // PURPOSE OF THIS SUBROUTINE:
    2009             :     // Writes runtime statistics for controllers on the specified air loop
    2010             :     // to the specified file.
    2011             : 
    2012             :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    2013             :     Real64 WarmRestartSuccessRatio;
    2014             :     Real64 AvgIterations;
    2015             : 
    2016           0 :     print(statisticsFile, "{},\n", ThisPrimaryAirSystem.Name);
    2017             : 
    2018             :     // Number of calls to SimAirLoop() has been invoked over the course of the simulation
    2019             :     // to simulate the specified air loop
    2020           0 :     print(statisticsFile, "NumCalls,{}\n", ThisAirLoopStats.NumCalls);
    2021             : 
    2022             :     // Warm restart success ratio
    2023           0 :     int NumWarmRestarts = ThisAirLoopStats.NumSuccessfulWarmRestarts + ThisAirLoopStats.NumFailedWarmRestarts;
    2024           0 :     if (NumWarmRestarts == 0) {
    2025           0 :         WarmRestartSuccessRatio = 0.0;
    2026             :     } else {
    2027           0 :         WarmRestartSuccessRatio = double(ThisAirLoopStats.NumSuccessfulWarmRestarts) / double(NumWarmRestarts);
    2028             :     }
    2029             : 
    2030           0 :     print(statisticsFile, "NumWarmRestarts,{}\n", NumWarmRestarts);
    2031           0 :     print(statisticsFile, "NumSuccessfulWarmRestarts,{}\n", ThisAirLoopStats.NumSuccessfulWarmRestarts);
    2032           0 :     print(statisticsFile, "NumFailedWarmRestarts,{}\n", ThisAirLoopStats.NumFailedWarmRestarts);
    2033           0 :     print(statisticsFile, "WarmRestartSuccessRatio,{:.10T}\n", WarmRestartSuccessRatio);
    2034             : 
    2035             :     // Total number of times SimAirLoopComponents() has been invoked over the course of the simulation
    2036             :     // to simulate the specified air loop
    2037           0 :     print(statisticsFile, "TotSimAirLoopComponents,{}\n", ThisAirLoopStats.TotSimAirLoopComponents);
    2038             :     // Maximum number of times SimAirLoopComponents() has been invoked over the course of the simulation
    2039             :     // to simulate the specified air loop
    2040           0 :     print(statisticsFile, "MaxSimAirLoopComponents,{}\n", ThisAirLoopStats.MaxSimAirLoopComponents);
    2041             : 
    2042             :     // Aggregated number of iterations needed by all controllers to simulate the specified air loop
    2043           0 :     print(statisticsFile, "TotIterations,{}\n", ThisAirLoopStats.TotIterations);
    2044             :     // Maximum number of iterations needed by controllers to simulate the specified air loop
    2045           0 :     print(statisticsFile, "MaxIterations,{}\n", ThisAirLoopStats.MaxIterations);
    2046             : 
    2047             :     // Average number of iterations needed by controllers to simulate the specified air loop
    2048           0 :     if (ThisAirLoopStats.NumCalls == 0) {
    2049           0 :         AvgIterations = 0.0;
    2050             :     } else {
    2051           0 :         AvgIterations = double(ThisAirLoopStats.TotIterations) / double(ThisAirLoopStats.NumCalls);
    2052             :     }
    2053             : 
    2054           0 :     print(statisticsFile, "AvgIterations,{:.10T}\n", AvgIterations);
    2055             : 
    2056             :     // Dump statistics for each controller on this air loop
    2057           0 :     for (int AirLoopControlNum = 1; AirLoopControlNum <= ThisPrimaryAirSystem.NumControllers; ++AirLoopControlNum) {
    2058             : 
    2059           0 :         print(statisticsFile, "{},\n", ThisPrimaryAirSystem.ControllerName(AirLoopControlNum));
    2060             : 
    2061             :         // Aggregate iteration trackers across all operating modes
    2062           0 :         int NumCalls = 0;
    2063           0 :         int TotIterations = 0;
    2064           0 :         int MaxIterations = 0;
    2065             : 
    2066           0 :         for (int iModeNum = iFirstMode; iModeNum <= iLastMode; ++iModeNum) {
    2067           0 :             NumCalls += ThisAirLoopStats.ControllerStats(AirLoopControlNum).NumCalls(iModeNum);
    2068             : 
    2069           0 :             TotIterations += ThisAirLoopStats.ControllerStats(AirLoopControlNum).TotIterations(iModeNum);
    2070             : 
    2071           0 :             MaxIterations = max(MaxIterations, ThisAirLoopStats.ControllerStats(AirLoopControlNum).MaxIterations(iModeNum));
    2072             :         }
    2073             : 
    2074             :         // Number of times this controller was simulated (should match air loop num calls)
    2075           0 :         print(statisticsFile, "NumCalls,{}\n", NumCalls);
    2076             :         // Aggregated number of iterations needed by this controller
    2077           0 :         print(statisticsFile, "TotIterations,{}\n", TotIterations);
    2078             :         // Aggregated number of iterations needed by this controller
    2079           0 :         print(statisticsFile, "MaxIterations,{}\n", MaxIterations);
    2080             : 
    2081             :         // Average number of iterations needed by controllers to simulate the specified air loop
    2082           0 :         if (NumCalls == 0) {
    2083           0 :             AvgIterations = 0.0;
    2084             :         } else {
    2085           0 :             AvgIterations = double(TotIterations) / double(NumCalls);
    2086             :         }
    2087           0 :         print(statisticsFile, "AvgIterations,{:.10T}\n", AvgIterations);
    2088             : 
    2089             :         // Dump iteration trackers for each operating mode
    2090           0 :         for (int iModeNum = iFirstMode; iModeNum <= iLastMode; ++iModeNum) {
    2091             : 
    2092           0 :             print(statisticsFile, "{},\n", state.dataHVACCtrl->ControllerModeTypes(iModeNum));
    2093             : 
    2094             :             // Number of times this controller operated in this mode
    2095           0 :             print(statisticsFile, "NumCalls,{}\n", ThisAirLoopStats.ControllerStats(AirLoopControlNum).NumCalls(iModeNum));
    2096             : 
    2097             :             // Aggregated number of iterations needed by this controller
    2098           0 :             print(statisticsFile, "TotIterations,{}\n", ThisAirLoopStats.ControllerStats(AirLoopControlNum).TotIterations(iModeNum));
    2099             :             // Aggregated number of iterations needed by this controller
    2100           0 :             print(statisticsFile, "MaxIterations,{}\n", ThisAirLoopStats.ControllerStats(AirLoopControlNum).MaxIterations(iModeNum));
    2101             : 
    2102             :             // Average number of iterations needed by controllers to simulate the specified air loop
    2103           0 :             if (ThisAirLoopStats.ControllerStats(AirLoopControlNum).NumCalls(iModeNum) == 0) {
    2104           0 :                 AvgIterations = 0.0;
    2105             :             } else {
    2106           0 :                 AvgIterations = double(ThisAirLoopStats.ControllerStats(AirLoopControlNum).TotIterations(iModeNum)) /
    2107           0 :                                 double(ThisAirLoopStats.ControllerStats(AirLoopControlNum).NumCalls(iModeNum));
    2108             :             }
    2109           0 :             print(statisticsFile, "AvgIterations,{:.10T}\n", AvgIterations);
    2110             :         }
    2111             :     }
    2112           0 : }
    2113             : 
    2114           0 : void SetupAirLoopControllersTracer(EnergyPlusData &state, int const AirLoopNum)
    2115             : {
    2116             : 
    2117             :     // SUBROUTINE INFORMATION:
    2118             :     //       AUTHOR         Dimitri Curtil
    2119             :     //       DATE WRITTEN   February 2006
    2120             : 
    2121             :     // PURPOSE OF THIS SUBROUTINE:
    2122             :     // Opens main trace file for controllers on specific air loop
    2123             :     // and writes header row with titles.
    2124             : 
    2125             :     // Open main controller trace file for each air loop
    2126           0 :     const std::string TraceFilePath = fmt::format("controller.{}.csv", state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).Name);
    2127             : 
    2128           0 :     auto &airLoopStats = state.dataHVACControllers->AirLoopStats(AirLoopNum);
    2129             : 
    2130             :     // Store file unit in air loop stats
    2131           0 :     airLoopStats.TraceFile->filePath = TraceFilePath;
    2132           0 :     airLoopStats.TraceFile->open();
    2133             : 
    2134           0 :     if (!airLoopStats.TraceFile->good()) {
    2135           0 :         ShowFatalError(state, format("SetupAirLoopControllersTracer: Failed to open air loop trace file \"{}\" for output (write).", TraceFilePath));
    2136           0 :         return;
    2137             :     }
    2138             : 
    2139           0 :     auto &TraceFile = *airLoopStats.TraceFile;
    2140             : 
    2141             :     // List all controllers and their corresponding handles into main trace file
    2142           0 :     print(TraceFile, "Num,Name,\n");
    2143             : 
    2144           0 :     for (int ControllerNum = 1; ControllerNum <= state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers; ++ControllerNum) {
    2145           0 :         print(TraceFile, "{},{},\n", ControllerNum, state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).ControllerName(ControllerNum));
    2146             :         // SAME AS ControllerProps(ControllerIndex)%ControllerName BUT NOT YET AVAILABLE
    2147             :     }
    2148             : 
    2149             :     // Skip a bunch of lines
    2150           0 :     print(TraceFile, "\n\n\n");
    2151             : 
    2152             :     // Write column header in main controller trace file
    2153           0 :     print(TraceFile,
    2154             :           "ZoneSizingCalc,SysSizingCalc,EnvironmentNum,WarmupFlag,SysTimeStamp,SysTimeInterval,BeginTimeStepFlag,FirstTimeStepSysFlag,"
    2155             :           "FirstHVACIteration,AirLoopPass,AirLoopNumCallsTot,AirLoopConverged,");
    2156             : 
    2157             :     // Write headers for final state
    2158           0 :     for (int ControllerNum = 1; ControllerNum <= state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers; ++ControllerNum) {
    2159           0 :         print(
    2160             :             TraceFile, "Mode{},IterMax{},XRoot{},YRoot{},YSetPoint{},\n", ControllerNum, ControllerNum, ControllerNum, ControllerNum, ControllerNum);
    2161             :     }
    2162             : 
    2163           0 :     print(TraceFile, "\n");
    2164           0 : }
    2165             : 
    2166           0 : void TraceAirLoopControllers(EnergyPlusData &state,
    2167             :                              bool const FirstHVACIteration,
    2168             :                              int const AirLoopNum,
    2169             :                              int const AirLoopPass,
    2170             :                              bool const AirLoopConverged,
    2171             :                              int const AirLoopNumCalls)
    2172             : {
    2173             : 
    2174             :     // SUBROUTINE INFORMATION:
    2175             :     //       AUTHOR         Dimitri Curtil
    2176             :     //       DATE WRITTEN   January 2006
    2177             : 
    2178             :     // PURPOSE OF THIS SUBROUTINE:
    2179             :     // This subroutine writes diagnostic to the trace file attached to each air loop.
    2180             : 
    2181             :     // SUBROUTINE ARGUMENT DEFINITIONS:
    2182             :     // TRUE when primary air system & controllers simulation has converged;
    2183             :     // Number of times SimAirLoopComponents() has been invoked
    2184             : 
    2185           0 :     auto &airLoopStats = state.dataHVACControllers->AirLoopStats(AirLoopNum);
    2186             : 
    2187             :     // IF no controllers on this air loop then we have nothing to do
    2188           0 :     if (state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers == 0) return;
    2189             :     // To avoid tracking statistics in case of no air loop or no HVAC controllers are defined
    2190           0 :     if (state.dataHVACControllers->NumAirLoopStats == 0) return;
    2191             : 
    2192             :     // Setup trace file on first call only
    2193           0 :     if (airLoopStats.FirstTraceFlag) {
    2194           0 :         SetupAirLoopControllersTracer(state, AirLoopNum);
    2195             : 
    2196           0 :         airLoopStats.FirstTraceFlag = false;
    2197             :     }
    2198             : 
    2199           0 :     auto &TraceFile = *airLoopStats.TraceFile;
    2200             : 
    2201           0 :     if (!TraceFile.good()) return;
    2202             : 
    2203             :     // Write iteration stamp first
    2204           0 :     TraceIterationStamp(state, TraceFile, FirstHVACIteration, AirLoopPass, AirLoopConverged, AirLoopNumCalls);
    2205             : 
    2206             :     // Loop over the air sys controllers and write diagnostic to trace file
    2207           0 :     for (int ControllerNum = 1; ControllerNum <= state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers; ++ControllerNum) {
    2208           0 :         TraceAirLoopController(state, TraceFile, state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).ControllerIndex(ControllerNum));
    2209             :     }
    2210             : 
    2211             :     // Go to next line
    2212           0 :     print(TraceFile, "\n");
    2213             : }
    2214             : 
    2215           0 : void TraceIterationStamp(EnergyPlusData &state,
    2216             :                          InputOutputFile &TraceFile,
    2217             :                          bool const FirstHVACIteration,
    2218             :                          int const AirLoopPass,
    2219             :                          bool const AirLoopConverged,
    2220             :                          int const AirLoopNumCalls)
    2221             : {
    2222             : 
    2223             :     // SUBROUTINE INFORMATION:
    2224             :     //       AUTHOR         Dimitri Curtil
    2225             :     //       DATE WRITTEN   February 2006
    2226             : 
    2227             :     // PURPOSE OF THIS SUBROUTINE:
    2228             :     // Writes current iteration time stamp to specified trace file.
    2229             : 
    2230             :     // SUBROUTINE PARAMETER DEFINITIONS:
    2231             :     // TRUE when primary air system and controllers simulation has converged;
    2232             :     // Number of times SimAirLoopComponents() has been invoked
    2233             : 
    2234             :     // Write step stamp to air loop trace file after reset
    2235             :     // Note that we do not go to the next line
    2236           0 :     print(TraceFile,
    2237             :           "{},{},{},{},{},{},{},{},{},{},{},{},",
    2238           0 :           static_cast<int>(state.dataGlobal->ZoneSizingCalc),
    2239           0 :           static_cast<int>(state.dataGlobal->SysSizingCalc),
    2240           0 :           state.dataEnvrn->CurEnvirNum,
    2241           0 :           static_cast<int>(state.dataGlobal->WarmupFlag),
    2242           0 :           CreateHVACTimeString(state),
    2243           0 :           MakeHVACTimeIntervalString(state),
    2244           0 :           static_cast<int>(state.dataGlobal->BeginTimeStepFlag),
    2245           0 :           static_cast<int>(state.dataHVACGlobal->FirstTimeStepSysFlag),
    2246           0 :           static_cast<int>(FirstHVACIteration),
    2247             :           AirLoopPass,
    2248             :           AirLoopNumCalls,
    2249           0 :           static_cast<int>(AirLoopConverged));
    2250           0 : }
    2251             : 
    2252           0 : void TraceAirLoopController(EnergyPlusData &state, InputOutputFile &TraceFile, int const ControlNum)
    2253             : {
    2254             : 
    2255             :     // SUBROUTINE INFORMATION:
    2256             :     //       AUTHOR         Dimitri Curtil
    2257             :     //       DATE WRITTEN   January 2006
    2258             : 
    2259             :     // PURPOSE OF THIS SUBROUTINE:
    2260             :     // This subroutine writes convergence diagnostic to the air loop trace file
    2261             :     // for the specified controller index.
    2262             : 
    2263           0 :     auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    2264             : 
    2265           0 :     print(TraceFile,
    2266             :           "{},{},{:.10T},{:.10T},{:.10T},",
    2267           0 :           controllerProps.Mode,
    2268           0 :           controllerProps.NumCalcCalls,
    2269           0 :           state.dataLoopNodes->Node(controllerProps.ActuatedNode).MassFlowRate,
    2270           0 :           state.dataLoopNodes->Node(controllerProps.SensedNode).Temp,
    2271           0 :           state.dataLoopNodes->Node(controllerProps.SensedNode).TempSetPoint);
    2272           0 : }
    2273             : 
    2274           0 : void SetupIndividualControllerTracer(EnergyPlusData &state, int const ControlNum)
    2275             : {
    2276             : 
    2277             :     // SUBROUTINE INFORMATION:
    2278             :     //       AUTHOR         Dimitri Curtil
    2279             :     //       DATE WRITTEN   February 2006
    2280             : 
    2281             :     // PURPOSE OF THIS SUBROUTINE:
    2282             :     // Opens individual controller trace file for the specified controller
    2283             :     // and writes header row.
    2284             : 
    2285           0 :     auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    2286             : 
    2287           0 :     const std::string TraceFilePath = fmt::format("controller.{}.csv", controllerProps.ControllerName);
    2288           0 :     auto &TraceFile = *controllerProps.TraceFile;
    2289           0 :     TraceFile.filePath = TraceFilePath;
    2290           0 :     TraceFile.open();
    2291             : 
    2292           0 :     if (!TraceFile.good()) {
    2293           0 :         ShowFatalError(state,
    2294           0 :                        format("SetupIndividualControllerTracer: Failed to open controller trace file \"{}\" for output (write).", TraceFilePath));
    2295           0 :         return;
    2296             :     }
    2297             : 
    2298             :     // Write header row
    2299             :     // Masss flow rate
    2300             :     // Convergence analysis
    2301           0 :     print(TraceFile,
    2302             :           "EnvironmentNum,WarmupFlag,SysTimeStamp,SysTimeInterval,AirLoopPass,FirstHVACIteration,Operation,NumCalcCalls,SensedNode%MassFlowRate,"
    2303             :           "ActuatedNode%MassFlowRateMinAvail,ActuatedNode%MassFlowRateMaxAvail,X,Y,Setpoint,DeltaSensed,Offset,Mode,IsConvergedFlag,"
    2304             :           "NextActuatedValue");
    2305             : 
    2306           0 :     RootFinder::WriteRootFinderTraceHeader(TraceFile);
    2307             : 
    2308             :     // Finally skip line
    2309           0 :     print(TraceFile, "\n");
    2310           0 : }
    2311             : 
    2312           0 : void TraceIndividualController(EnergyPlusData &state,
    2313             :                                int const ControlNum,
    2314             :                                bool const FirstHVACIteration,
    2315             :                                int const AirLoopPass,
    2316             :                                DataHVACControllers::ControllerOperation const Operation, // Operation to execute
    2317             :                                bool const IsConvergedFlag)
    2318             : {
    2319             : 
    2320             :     // SUBROUTINE INFORMATION:
    2321             :     //       AUTHOR         Dimitri Curtil
    2322             :     //       DATE WRITTEN   January 2006
    2323             : 
    2324             :     // PURPOSE OF THIS SUBROUTINE:
    2325             :     // This subroutine writes convergence diagnostic to the trace file for the specified controller.
    2326             : 
    2327             :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    2328             :     bool SkipLineFlag;
    2329             : 
    2330           0 :     auto &ControllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
    2331             : 
    2332             :     // Setup individual trace file on first trace only
    2333           0 :     if (ControllerProps.FirstTraceFlag) {
    2334           0 :         SetupIndividualControllerTracer(state, ControlNum);
    2335             : 
    2336           0 :         ControllerProps.FirstTraceFlag = false;
    2337           0 :         SkipLineFlag = false;
    2338             :     } else {
    2339           0 :         SkipLineFlag = FirstHVACIteration && (ControllerProps.NumCalcCalls == 0);
    2340             :     }
    2341             : 
    2342           0 :     auto &TraceFile = *ControllerProps.TraceFile;
    2343             : 
    2344             :     // Nothing to do if trace file not registered
    2345           0 :     if (!TraceFile.good()) return;
    2346             : 
    2347             :     // Skip a line before each new HVAC step
    2348           0 :     if (SkipLineFlag) {
    2349           0 :         print(TraceFile, "\n");
    2350             :     }
    2351             : 
    2352             :     // Set the sensed and actuated node numbers
    2353           0 :     int ActuatedNode = ControllerProps.ActuatedNode;
    2354           0 :     int SensedNode = ControllerProps.SensedNode;
    2355             : 
    2356             :     // Write iteration stamp
    2357           0 :     print(TraceFile,
    2358             :           "{},{},{},{},{},{},{},{},",
    2359           0 :           state.dataEnvrn->CurEnvirNum,
    2360           0 :           static_cast<int>(state.dataGlobal->WarmupFlag),
    2361           0 :           CreateHVACTimeString(state),
    2362           0 :           MakeHVACTimeIntervalString(state),
    2363             :           AirLoopPass,
    2364           0 :           static_cast<int>(FirstHVACIteration),
    2365             :           Operation,
    2366           0 :           ControllerProps.NumCalcCalls);
    2367             : 
    2368             :     // Write detailed diagnostic
    2369           0 :     switch (Operation) {
    2370           0 :     case DataHVACControllers::ControllerOperation::ColdStart:
    2371             :     case DataHVACControllers::ControllerOperation::WarmRestart: {
    2372           0 :         print(TraceFile,
    2373             :               "{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{},{},{},{},{:.10T},",
    2374           0 :               state.dataLoopNodes->Node(SensedNode).MassFlowRate,
    2375           0 :               state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMinAvail,
    2376           0 :               state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMaxAvail,
    2377           0 :               ControllerProps.ActuatedValue,
    2378           0 :               state.dataLoopNodes->Node(SensedNode).Temp,
    2379           0 :               ControllerProps.SetPointValue,
    2380           0 :               ' ',
    2381           0 :               ' ',
    2382           0 :               ControllerProps.Mode,
    2383           0 :               static_cast<int>(IsConvergedFlag),
    2384           0 :               ControllerProps.NextActuatedValue);
    2385             :         // X | Y | setpoint | DeltaSensed = Y - YRoot | Offset | Mode | IsConvergedFlag
    2386             : 
    2387             :         // No trace available for root finder yet
    2388             :         // Skip call to WriteRootFinderTrace()
    2389             : 
    2390             :         // Finally skip line
    2391           0 :         print(TraceFile, "\n");
    2392           0 :     } break;
    2393           0 :     case DataHVACControllers::ControllerOperation::Iterate: {
    2394             :         // Masss flow rate
    2395             :         // Convergence analysis
    2396             : 
    2397           0 :         print(TraceFile,
    2398             :               "{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{},{},{:.10T},",
    2399           0 :               state.dataLoopNodes->Node(SensedNode).MassFlowRate,
    2400           0 :               state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMinAvail,
    2401           0 :               state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMaxAvail,
    2402           0 :               ControllerProps.ActuatedValue,
    2403           0 :               state.dataLoopNodes->Node(SensedNode).Temp,
    2404           0 :               ControllerProps.SetPointValue,
    2405           0 :               ControllerProps.DeltaSensed,
    2406           0 :               ControllerProps.Offset,
    2407           0 :               ControllerProps.Mode,
    2408           0 :               static_cast<int>(IsConvergedFlag),
    2409           0 :               ControllerProps.NextActuatedValue);
    2410             : 
    2411             :         // X | Y | setpoint | DeltaSensed = Y - YRoot | Offset | Mode | IsConvergedFlag
    2412             : 
    2413             :         // Append trace for root finder
    2414           0 :         RootFinder::WriteRootFinderTrace(TraceFile, state.dataHVACControllers->RootFinders(ControlNum));
    2415             : 
    2416             :         // Finally skip line
    2417           0 :         print(TraceFile, "\n");
    2418             : 
    2419           0 :     } break;
    2420           0 :     case DataHVACControllers::ControllerOperation::End: {
    2421             :         // Masss flow rate
    2422             :         // Convergence analysis
    2423           0 :         print(TraceFile,
    2424             :               "{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{},{},{:.10T},",
    2425           0 :               state.dataLoopNodes->Node(SensedNode).MassFlowRate,
    2426           0 :               state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMinAvail,
    2427           0 :               state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMaxAvail,
    2428           0 :               ControllerProps.ActuatedValue,
    2429           0 :               state.dataLoopNodes->Node(SensedNode).Temp,
    2430           0 :               ControllerProps.SetPointValue,
    2431           0 :               ControllerProps.DeltaSensed,
    2432           0 :               ControllerProps.Offset,
    2433           0 :               ControllerProps.Mode,
    2434           0 :               static_cast<int>(IsConvergedFlag),
    2435           0 :               ControllerProps.NextActuatedValue);
    2436             : 
    2437             :         // X | Y | setpoint | DeltaSensed = Y - YRoot | Offset | Mode | IsConvergedFlag
    2438             : 
    2439             :         // No trace available for root finder yet
    2440             :         // Skip call to WriteRootFinderTrace()
    2441             : 
    2442             :         // Finally skip line
    2443           0 :         print(TraceFile, "\n");
    2444             : 
    2445             :         // Skip an additional line to indicate end of current HVAC step
    2446           0 :         print(TraceFile, "\n");
    2447             : 
    2448           0 :     } break;
    2449           0 :     default: {
    2450             :         // Should never happen
    2451           0 :         ShowFatalError(
    2452           0 :             state, format("TraceIndividualController: Invalid Operation passed={}, Controller name={}", Operation, ControllerProps.ControllerName));
    2453           0 :     } break;
    2454             :     }
    2455             : 
    2456           0 :     TraceFile.flush();
    2457             : }
    2458             : 
    2459           0 : Real64 GetCurrentHVACTime(const EnergyPlusData &state)
    2460             : {
    2461             :     // SUBROUTINE INFORMATION:
    2462             :     //       AUTHOR         Dimitri Curtil
    2463             :     //       DATE WRITTEN   November 2004
    2464             :     //       MODIFIED       na
    2465             :     //       RE-ENGINEERED  na
    2466             : 
    2467             :     // PURPOSE OF THIS FUNCTION:
    2468             :     // This routine returns the time in seconds at the end of the current HVAC step.
    2469             : 
    2470             :     // This is the correct formula that does not use MinutesPerSystemTimeStep, which would
    2471             :     // erronously truncate all sub-minute system time steps down to the closest full minute.
    2472             :     // Maybe later TimeStepZone, TimeStepSys and SysTimeElapsed could also be specified
    2473             :     // as real.
    2474             :     Real64 const CurrentHVACTime =
    2475           0 :         (state.dataGlobal->CurrentTime - state.dataGlobal->TimeStepZone) + state.dataHVACGlobal->SysTimeElapsed + state.dataHVACGlobal->TimeStepSys;
    2476           0 :     return CurrentHVACTime * Constant::SecInHour;
    2477             : }
    2478             : 
    2479     2838329 : Real64 GetPreviousHVACTime(const EnergyPlusData &state)
    2480             : {
    2481             :     // SUBROUTINE INFORMATION:
    2482             :     //       AUTHOR         Dimitri Curtil
    2483             :     //       DATE WRITTEN   November 2004
    2484             : 
    2485             :     // PURPOSE OF THIS FUNCTION:
    2486             :     // This routine returns the time in seconds at the beginning of the current HVAC step.
    2487             : 
    2488             :     // This is the correct formula that does not use MinutesPerSystemTimeStep, which would
    2489             :     // erronously truncate all sub-minute system time steps down to the closest full minute.
    2490     2838329 :     Real64 const PreviousHVACTime = (state.dataGlobal->CurrentTime - state.dataGlobal->TimeStepZone) + state.dataHVACGlobal->SysTimeElapsed;
    2491     2838329 :     return PreviousHVACTime * Constant::SecInHour;
    2492             : }
    2493             : 
    2494           0 : std::string CreateHVACTimeString(const EnergyPlusData &state)
    2495             : {
    2496             : 
    2497             :     // FUNCTION INFORMATION:
    2498             :     //       AUTHOR         Dimitri Curtil
    2499             :     //       DATE WRITTEN   January 2006
    2500             : 
    2501             :     // PURPOSE OF THIS FUNCTION:
    2502             :     // This function creates a string describing the current time stamp of the system
    2503             :     // time step.
    2504             : 
    2505           0 :     std::string Buffer = General::CreateTimeString(GetCurrentHVACTime(state));
    2506           0 :     return state.dataEnvrn->CurMnDy + ' ' + stripped(Buffer);
    2507           0 : }
    2508             : 
    2509           0 : std::string CreateHVACStepFullString(const EnergyPlusData &state)
    2510             : {
    2511             : 
    2512             :     // FUNCTION INFORMATION:
    2513             :     //       AUTHOR         Dimitri Curtil
    2514             :     //       DATE WRITTEN   April 2006
    2515             : 
    2516             :     // PURPOSE OF THIS FUNCTION:
    2517             :     // This function creates a string describing the current HVAC step.
    2518             :     // It includes the environment name, the current day/month and the current
    2519             :     // time stamp for the system time step.
    2520             :     // It is used in error messages only.
    2521             : 
    2522           0 :     return state.dataEnvrn->EnvironmentName + ", " + MakeHVACTimeIntervalString(state);
    2523             : }
    2524             : 
    2525           0 : std::string MakeHVACTimeIntervalString(const EnergyPlusData &state)
    2526             : {
    2527             : 
    2528             :     // FUNCTION INFORMATION:
    2529             :     //       AUTHOR         Dimitri Curtil
    2530             :     //       DATE WRITTEN   January 2006
    2531             : 
    2532             :     // PURPOSE OF THIS FUNCTION:
    2533             :     // This function creates a string describing the current time interval of the system
    2534             :     // time step.
    2535             : 
    2536           0 :     return format("{} - {}", General::CreateTimeString(GetPreviousHVACTime(state)), General::CreateTimeString(GetCurrentHVACTime(state)));
    2537             : }
    2538             : 
    2539         296 : void CheckControllerListOrder(EnergyPlusData &state)
    2540             : {
    2541             : 
    2542             :     // SUBROUTINE INFORMATION:
    2543             :     //       AUTHOR         B. Griffith
    2544             :     //       DATE WRITTEN   Oct 10.
    2545             : 
    2546             :     // PURPOSE OF THIS SUBROUTINE:
    2547             :     // check that if multiple controllers on an air loop, that they aren't listed in a bad order
    2548             :     // CR 8253
    2549             : 
    2550             :     // METHODOLOGY EMPLOYED:
    2551             :     // setup data for sensed nodes and compare positions if on the same branch
    2552             : 
    2553             :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    2554         296 :     Array2D_int ContrlSensedNodeNums; // array for storing sense node info
    2555             : 
    2556         325 :     for (int AirSysNum = 1; AirSysNum <= state.dataHVACGlobal->NumPrimaryAirSys; ++AirSysNum) {
    2557             : 
    2558          29 :         if (state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).NumControllers > 1) {
    2559             :             // first see how many are water coil controllers
    2560           7 :             int WaterCoilContrlCount = 0; // init
    2561          21 :             for (int ContrlNum = 1; ContrlNum <= state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).NumControllers; ++ContrlNum) {
    2562          14 :                 if (Util::SameString(state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).ControllerType(ContrlNum), "CONTROLLER:WATERCOIL")) {
    2563          14 :                     ++WaterCoilContrlCount;
    2564             :                 }
    2565             :             }
    2566             : 
    2567           7 :             if (WaterCoilContrlCount > 1) {
    2568           7 :                 ContrlSensedNodeNums.allocate(3, WaterCoilContrlCount);
    2569           7 :                 ContrlSensedNodeNums = 0;
    2570           7 :                 int SensedNodeIndex = 0;
    2571          21 :                 for (int ContrlNum = 1; ContrlNum <= state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).NumControllers; ++ContrlNum) {
    2572          14 :                     if (Util::SameString(state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).ControllerType(ContrlNum), "CONTROLLER:WATERCOIL")) {
    2573          14 :                         ++SensedNodeIndex;
    2574          14 :                         int foundControl = Util::FindItemInList(state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).ControllerName(ContrlNum),
    2575          14 :                                                                 state.dataHVACControllers->ControllerProps,
    2576             :                                                                 &ControllerPropsType::ControllerName);
    2577          14 :                         if (foundControl > 0) {
    2578          14 :                             ContrlSensedNodeNums(1, SensedNodeIndex) = state.dataHVACControllers->ControllerProps(foundControl).SensedNode;
    2579             :                         }
    2580             :                     }
    2581             :                 }
    2582             :             }
    2583             : 
    2584             :             // fill branch index for sensed nodes
    2585           7 :             if (allocated(ContrlSensedNodeNums)) {
    2586          26 :                 for (int BranchNum = 1; BranchNum <= state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).NumBranches; ++BranchNum) {
    2587          57 :                     for (int SensedNodeIndex = 1; SensedNodeIndex <= WaterCoilContrlCount; ++SensedNodeIndex) {
    2588         128 :                         for (int BranchNodeIndex = 1;
    2589         128 :                              BranchNodeIndex <= state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).Branch(BranchNum).TotalNodes;
    2590             :                              ++BranchNodeIndex) {
    2591          90 :                             if (ContrlSensedNodeNums(1, SensedNodeIndex) ==
    2592          90 :                                 state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).Branch(BranchNum).NodeNum(BranchNodeIndex)) {
    2593          14 :                                 ContrlSensedNodeNums(2, SensedNodeIndex) = BranchNodeIndex;
    2594          14 :                                 ContrlSensedNodeNums(3, SensedNodeIndex) = BranchNum;
    2595             :                             }
    2596             :                         }
    2597             :                     }
    2598             :                 }
    2599             :             }
    2600             :             // check if flow order doesn't agree with controller order
    2601           7 :             if (allocated(ContrlSensedNodeNums)) {
    2602          21 :                 for (int SensedNodeIndex = 1; SensedNodeIndex <= WaterCoilContrlCount; ++SensedNodeIndex) {
    2603          14 :                     if (SensedNodeIndex == 1) continue;
    2604           7 :                     if (ContrlSensedNodeNums(2, SensedNodeIndex) < ContrlSensedNodeNums(2, SensedNodeIndex - 1)) {
    2605             :                         // now see if on the same branch
    2606           1 :                         if (ContrlSensedNodeNums(3, SensedNodeIndex) == ContrlSensedNodeNums(3, SensedNodeIndex - 1)) {
    2607             :                             // we have a flow order problem with water coil controllers
    2608           0 :                             ShowSevereError(state, "CheckControllerListOrder: A water coil controller list has the wrong order");
    2609           0 :                             ShowContinueError(state,
    2610           0 :                                               format("Check the AirLoopHVAC:ControllerList for the air loop called \"{}\"",
    2611           0 :                                                      state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).Name));
    2612           0 :                             ShowContinueError(state,
    2613             :                                               "When there are multiple Controller:WaterCoil objects for the same air loop, they need to be "
    2614             :                                               "listed in the proper order.");
    2615           0 :                             ShowContinueError(state,
    2616             :                                               "The controllers should be listed in natural flow order with those for upstream coils listed "
    2617             :                                               "before those for downstream coils.");
    2618           0 :                             ShowContinueError(state, "The sensed nodes specified for the respective controllers should also reflect this order.");
    2619             :                         }
    2620             :                     }
    2621             :                 }
    2622             :             }
    2623             : 
    2624           7 :             if (allocated(ContrlSensedNodeNums)) ContrlSensedNodeNums.deallocate();
    2625             : 
    2626             :         } // controllers > 1
    2627             :     }
    2628         296 : }
    2629             : 
    2630         863 : void CheckCoilWaterInletNode(EnergyPlusData &state,
    2631             :                              int const WaterInletNodeNum, // input actuator node number
    2632             :                              bool &NodeNotFound           // true if matching actuator node not found
    2633             : )
    2634             : {
    2635             : 
    2636             :     // SUBROUTINE INFORMATION:
    2637             :     //       AUTHOR         Heejin Cho
    2638             :     //       DATE WRITTEN   November 2010
    2639             : 
    2640             :     // PURPOSE OF THIS FUNCTION:
    2641             :     // This subroutine checks that the water inlet node number is matched by
    2642             :     // the actuator node number of some water coil
    2643             : 
    2644         863 :     if (state.dataHVACControllers->GetControllerInputFlag) {
    2645          22 :         GetControllerInput(state);
    2646          22 :         state.dataHVACControllers->GetControllerInputFlag = false;
    2647             :     }
    2648             : 
    2649         863 :     NodeNotFound = true;
    2650        7405 :     for (auto const &ControllerProps : state.dataHVACControllers->ControllerProps) {
    2651        6542 :         if (ControllerProps.ActuatedNode == WaterInletNodeNum) {
    2652         861 :             NodeNotFound = false;
    2653             :         }
    2654             :     }
    2655         863 : }
    2656             : 
    2657        3318 : void GetControllerNameAndIndex(EnergyPlusData &state,
    2658             :                                int const WaterInletNodeNum, // input actuator node number
    2659             :                                std::string &ControllerName, // controller name used by water coil
    2660             :                                int &ControllerIndex,        // controller index used by water coil
    2661             :                                bool &ErrorsFound            // true if matching actuator node not found
    2662             : )
    2663             : {
    2664             : 
    2665             :     // SUBROUTINE INFORMATION:
    2666             :     //       AUTHOR         Richard Raustad
    2667             :     //       DATE WRITTEN   June 2017
    2668             : 
    2669             :     // PURPOSE OF THIS FUNCTION:
    2670             :     // This subroutine checks that the water inlet node number is matched by
    2671             :     // the actuator node number of some water coil and passed back controller name and index
    2672             : 
    2673        3318 :     if (state.dataHVACControllers->GetControllerInputFlag) {
    2674         301 :         GetControllerInput(state);
    2675         301 :         state.dataHVACControllers->GetControllerInputFlag = false;
    2676             :     }
    2677             : 
    2678        3318 :     ControllerName = " ";
    2679        3318 :     ControllerIndex = 0;
    2680       25248 :     for (int ControlNum = 1; ControlNum <= state.dataHVACControllers->NumControllers; ++ControlNum) {
    2681       22801 :         if (state.dataHVACControllers->ControllerProps(ControlNum).ActuatedNode == WaterInletNodeNum) {
    2682         871 :             ControllerIndex = ControlNum;
    2683         871 :             ControllerName = state.dataHVACControllers->ControllerProps(ControlNum).ControllerName;
    2684         871 :             break;
    2685             :         }
    2686             :     }
    2687             : 
    2688        3318 :     if (ControllerIndex == 0) {
    2689        2447 :         ErrorsFound = true;
    2690             :     }
    2691        3318 : }
    2692             : 
    2693          58 : void GetControllerActuatorNodeNum(EnergyPlusData &state,
    2694             :                                   std::string const &ControllerName, // name of coil controller
    2695             :                                   int &WaterInletNodeNum,            // input actuator node number
    2696             :                                   bool &NodeNotFound                 // true if matching actuator node not found
    2697             : )
    2698             : {
    2699             : 
    2700             :     // SUBROUTINE INFORMATION:
    2701             :     //       AUTHOR         Richard Raustad, FSEC
    2702             :     //       DATE WRITTEN   September 2013
    2703             : 
    2704             :     // PURPOSE OF THIS FUNCTION:
    2705             :     // This subroutine finds the controllers actuator node number
    2706             : 
    2707          58 :     if (state.dataHVACControllers->GetControllerInputFlag) {
    2708           2 :         GetControllerInput(state);
    2709           2 :         state.dataHVACControllers->GetControllerInputFlag = false;
    2710             :     }
    2711             : 
    2712          58 :     NodeNotFound = true;
    2713          58 :     int ControlNum = Util::FindItemInList(ControllerName, state.dataHVACControllers->ControllerProps, &ControllerPropsType::ControllerName);
    2714          58 :     if (ControlNum > 0 && ControlNum <= state.dataHVACControllers->NumControllers) {
    2715          58 :         WaterInletNodeNum = state.dataHVACControllers->ControllerProps(ControlNum).ActuatedNode;
    2716          58 :         NodeNotFound = false;
    2717             :     }
    2718          58 : }
    2719             : 
    2720         864 : int GetControllerIndex(EnergyPlusData &state, std::string const &ControllerName // name of coil controller
    2721             : )
    2722             : {
    2723             : 
    2724             :     // SUBROUTINE INFORMATION:
    2725             :     //       AUTHOR         Richard Raustad, FSEC
    2726             :     //       DATE WRITTEN   January 2018
    2727             : 
    2728             :     // This subroutine finds the controllers actuator node number
    2729             : 
    2730         864 :     if (state.dataHVACControllers->GetControllerInputFlag) {
    2731           0 :         GetControllerInput(state);
    2732           0 :         state.dataHVACControllers->GetControllerInputFlag = false;
    2733             :     }
    2734             : 
    2735         864 :     int ControllerIndex = Util::FindItemInList(ControllerName, state.dataHVACControllers->ControllerProps, &ControllerPropsType::ControllerName);
    2736         864 :     if (ControllerIndex == 0) {
    2737           0 :         ShowFatalError(state,
    2738           0 :                        format("ManageControllers: Invalid controller={}. The only valid controller type for an AirLoopHVAC is Controller:WaterCoil.",
    2739             :                               ControllerName));
    2740             :     }
    2741             : 
    2742         864 :     return ControllerIndex;
    2743             : }
    2744             : 
    2745             : } // namespace EnergyPlus::HVACControllers

Generated by: LCOV version 1.14