LCOV - code coverage report
Current view: top level - EnergyPlus - PlantUtilities.cc (source / functions) Coverage Total Hit
Test: lcov.output.filtered Lines: 69.0 % 869 600
Test Date: 2025-06-02 12:03:30 Functions: 96.9 % 32 31

            Line data    Source code
       1              : // EnergyPlus, Copyright (c) 1996-2025, The Board of Trustees of the University of Illinois,
       2              : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
       3              : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
       4              : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
       5              : // contributors. All rights reserved.
       6              : //
       7              : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
       8              : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
       9              : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
      10              : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
      11              : // derivative works, and perform publicly and display publicly, and to permit others to do so.
      12              : //
      13              : // Redistribution and use in source and binary forms, with or without modification, are permitted
      14              : // provided that the following conditions are met:
      15              : //
      16              : // (1) Redistributions of source code must retain the above copyright notice, this list of
      17              : //     conditions and the following disclaimer.
      18              : //
      19              : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
      20              : //     conditions and the following disclaimer in the documentation and/or other materials
      21              : //     provided with the distribution.
      22              : //
      23              : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
      24              : //     the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
      25              : //     used to endorse or promote products derived from this software without specific prior
      26              : //     written permission.
      27              : //
      28              : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
      29              : //     without changes from the version obtained under this License, or (ii) Licensee makes a
      30              : //     reference solely to the software portion of its product, Licensee must refer to the
      31              : //     software as "EnergyPlus version X" software, where "X" is the version number Licensee
      32              : //     obtained under this License and may not use a different name for the software. Except as
      33              : //     specifically required in this Section (4), Licensee shall not use in a company name, a
      34              : //     product name, in advertising, publicity, or other promotional activities any name, trade
      35              : //     name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
      36              : //     similar designation, without the U.S. Department of Energy's prior written consent.
      37              : //
      38              : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
      39              : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
      40              : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
      41              : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
      42              : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
      43              : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
      44              : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
      45              : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
      46              : // POSSIBILITY OF SUCH DAMAGE.
      47              : 
      48              : // C++ Headers
      49              : #include <cmath>
      50              : 
      51              : // ObjexxFCL Headers
      52              : #include <ObjexxFCL/Array.functions.hh>
      53              : #include <ObjexxFCL/Array1D.hh>
      54              : #include <ObjexxFCL/Fmath.hh>
      55              : 
      56              : // EnergyPlus Headers
      57              : #include <EnergyPlus/BranchInputManager.hh>
      58              : #include <EnergyPlus/Data/EnergyPlusData.hh>
      59              : #include <EnergyPlus/DataBranchAirLoopPlant.hh>
      60              : #include <EnergyPlus/DataLoopNode.hh>
      61              : #include <EnergyPlus/DataSizing.hh>
      62              : #include <EnergyPlus/FluidProperties.hh>
      63              : #include <EnergyPlus/Plant/DataPlant.hh>
      64              : #include <EnergyPlus/PlantUtilities.hh>
      65              : #include <EnergyPlus/Pumps.hh>
      66              : #include <EnergyPlus/UtilityRoutines.hh>
      67              : 
      68              : namespace EnergyPlus::PlantUtilities {
      69              : 
      70              : // Module containing the routines dealing with the <module_name>
      71              : 
      72              : // MODULE INFORMATION:
      73              : //       AUTHOR         <author>
      74              : //       DATE WRITTEN   <date_written>
      75              : //       MODIFIED       na
      76              : //       RE-ENGINEERED  na
      77              : 
      78          941 : void InitComponentNodes(EnergyPlusData &state, Real64 const MinCompMdot, Real64 const MaxCompMdot, int const InletNode, int const OutletNode)
      79              : {
      80              : 
      81              :     // SUBROUTINE INFORMATION:
      82              :     //       AUTHOR         Brent Griffith
      83              :     //       DATE WRITTEN   Sept 2010
      84              :     //       MODIFIED       na
      85              :     //       RE-ENGINEERED  na
      86              : 
      87              :     // PURPOSE OF THIS SUBROUTINE:
      88              :     //  Central routine for initializing plant nodes connected to components
      89              :     //  typically used for BeginEnvrnFlag
      90              : 
      91              :     // METHODOLOGY EMPLOYED:
      92              :     // set MassFlowRate variables on inlet node
      93              :     //  reset inlet node if more restrictive
      94              : 
      95              :     // Using/Aliasing
      96              : 
      97              :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
      98              :     Real64 tmpMinCompMdot; // local value
      99              :     Real64 tmpMaxCompMdot; // local value
     100              : 
     101          941 :     tmpMinCompMdot = MinCompMdot;
     102          941 :     tmpMaxCompMdot = MaxCompMdot;
     103              :     // trap bad values that can happen before all the setup is done
     104          941 :     if (tmpMinCompMdot < 0.0) {
     105            0 :         tmpMinCompMdot = 0.0;
     106              :     }
     107          941 :     if (tmpMaxCompMdot < 0.0) {
     108           10 :         tmpMaxCompMdot = 0.0;
     109              :     }
     110              : 
     111              :     // reset outlet node
     112          941 :     state.dataLoopNodes->Node(OutletNode).MassFlowRate = 0.0;
     113              : 
     114          941 :     state.dataLoopNodes->Node(InletNode).MassFlowRateMin = tmpMinCompMdot;
     115          941 :     state.dataLoopNodes->Node(InletNode).MassFlowRateMinAvail = tmpMinCompMdot;
     116          941 :     state.dataLoopNodes->Node(InletNode).MassFlowRateMax = tmpMaxCompMdot;
     117          941 :     state.dataLoopNodes->Node(InletNode).MassFlowRateMaxAvail = tmpMaxCompMdot;
     118              :     // reset inlet node, but only change from inlet setting if set and more restrictive
     119          941 :     state.dataLoopNodes->Node(InletNode).MassFlowRate = 0.0;
     120          941 :     state.dataLoopNodes->Node(InletNode).MassFlowRateRequest = 0.0;
     121          941 : }
     122              : 
     123       725301 : void SetComponentFlowRate(EnergyPlusData &state,
     124              :                           Real64 &CompFlow,             // [kg/s]
     125              :                           int const InletNode,          // component's inlet node index in node structure
     126              :                           int const OutletNode,         // component's outlet node index in node structure
     127              :                           PlantLocation const &plantLoc // component location for PlantLoop
     128              : )
     129              : {
     130              : 
     131              :     // SUBROUTINE INFORMATION:
     132              :     //       AUTHOR         Dan Fisher
     133              :     //       DATE WRITTEN   August 2009
     134              :     //       MODIFIED       na
     135              :     //       RE-ENGINEERED  na
     136              : 
     137              :     // PURPOSE OF THIS SUBROUTINE:
     138              :     // General purpose worker routine to set flows for a component model
     139              : 
     140       725301 :     if (plantLoc.loopNum == 0) { // protect from hard crash below
     141            8 :         if (InletNode > 0) {
     142           16 :             ShowSevereError(state,
     143           16 :                             format("SetComponentFlowRate: trapped plant loop index = 0, check component with inlet node named={}",
     144            8 :                                    state.dataLoopNodes->NodeID(InletNode)));
     145              :         } else {
     146            0 :             ShowSevereError(state, "SetComponentFlowRate: trapped plant loop node id = 0");
     147              :         }
     148            8 :         return;
     149              :         // this crashes during ManageSizing, maybe it's just an init thing...
     150              :         // ShowFatalError(state, "Preceding loop index error causes program termination");
     151              :     }
     152              : 
     153       725293 :     Real64 const MdotOldRequest = state.dataLoopNodes->Node(InletNode).MassFlowRateRequest;
     154              : 
     155       725293 :     if (plantLoc.comp->CurOpSchemeType == DataPlant::OpScheme::Demand) {
     156              :         // store flow request on inlet node
     157       366474 :         state.dataLoopNodes->Node(InletNode).MassFlowRateRequest = CompFlow;
     158       366474 :         state.dataLoopNodes->Node(OutletNode).MassFlowRateMinAvail =
     159       366474 :             max(state.dataLoopNodes->Node(InletNode).MassFlowRateMinAvail, state.dataLoopNodes->Node(InletNode).MassFlowRateMin);
     160       366474 :         state.dataLoopNodes->Node(OutletNode).MassFlowRateMaxAvail =
     161       366474 :             min(state.dataLoopNodes->Node(InletNode).MassFlowRateMaxAvail, state.dataLoopNodes->Node(InletNode).MassFlowRateMax);
     162              :         // virtual 2-way valve (was tried but it clamps down demand side component's flow options so they can't find proper solutions)
     163              :     } else {
     164              :         // lodge the original request for all types
     165       358819 :         state.dataLoopNodes->Node(InletNode).MassFlowRateRequest = CompFlow;
     166              :     }
     167              : 
     168              :     // Update Min/Max Avail
     169              : 
     170       725293 :     state.dataLoopNodes->Node(OutletNode).MassFlowRateMinAvail =
     171       725293 :         max(state.dataLoopNodes->Node(InletNode).MassFlowRateMinAvail, state.dataLoopNodes->Node(InletNode).MassFlowRateMin);
     172       725293 :     if (state.dataLoopNodes->Node(InletNode).MassFlowRateMax >= 0.0) {
     173       725212 :         state.dataLoopNodes->Node(OutletNode).MassFlowRateMaxAvail =
     174       725212 :             min(state.dataLoopNodes->Node(InletNode).MassFlowRateMaxAvail, state.dataLoopNodes->Node(InletNode).MassFlowRateMax);
     175              :     } else {
     176           81 :         if (!state.dataGlobal->SysSizingCalc && state.dataPlnt->PlantFirstSizesOkayToFinalize) {
     177              :             // throw error for developers, need to change a component model to set hardware limits on inlet
     178           17 :             if (!state.dataLoopNodes->Node(InletNode).plantNodeErrorMsgIssued) {
     179           28 :                 ShowSevereError(state,
     180           28 :                                 format("SetComponentFlowRate: check component model implementation for component with inlet node named={}",
     181           14 :                                        state.dataLoopNodes->NodeID(InletNode)));
     182           14 :                 ShowContinueError(state, format("Inlet node MassFlowRatMax = {:.8R}", state.dataLoopNodes->Node(InletNode).MassFlowRateMax));
     183           14 :                 state.dataLoopNodes->Node(InletNode).plantNodeErrorMsgIssued = true;
     184              :             }
     185              :         }
     186              :     }
     187              : 
     188              :     // Set loop flow rate
     189       725293 :     if (plantLoc.side->FlowLock == DataPlant::FlowLock::Unlocked) {
     190       495310 :         if (plantLoc.loop->MaxVolFlowRate == DataSizing::AutoSize) { // still haven't sized the plant loop
     191           79 :             state.dataLoopNodes->Node(OutletNode).MassFlowRate = CompFlow;
     192           79 :             state.dataLoopNodes->Node(InletNode).MassFlowRate = state.dataLoopNodes->Node(OutletNode).MassFlowRate;
     193              :         } else { // bound the flow by Min/Max available and hardware limits
     194       495231 :             if (plantLoc.comp->FlowCtrl == DataBranchAirLoopPlant::ControlType::SeriesActive) {
     195              :                 // determine highest flow request for all the components on the branch
     196            0 :                 Real64 SeriesBranchHighFlowRequest = 0.0;
     197            0 :                 Real64 SeriesBranchHardwareMaxLim = state.dataLoopNodes->Node(InletNode).MassFlowRateMax;
     198            0 :                 Real64 SeriesBranchHardwareMinLim = 0.0;
     199            0 :                 Real64 SeriesBranchMaxAvail = state.dataLoopNodes->Node(InletNode).MassFlowRateMaxAvail;
     200            0 :                 Real64 SeriesBranchMinAvail = 0.0;
     201              : 
     202              :                 // inserting EMS On/Off Supervisory control here to series branch constraint and assuming EMS should shut off flow completely
     203              :                 // action here means EMS will not impact the FlowLock == FlowLocked condition (which should still show EMS intent)
     204            0 :                 bool EMSLoadOverride = false;
     205              : 
     206            0 :                 for (int CompNum = 1; CompNum <= plantLoc.branch->TotalComponents; ++CompNum) {
     207            0 :                     auto const &thisComp = plantLoc.branch->Comp(CompNum);
     208            0 :                     int const CompInletNodeNum = thisComp.NodeNumIn;
     209            0 :                     auto const &thisInletNode = state.dataLoopNodes->Node(CompInletNodeNum);
     210            0 :                     SeriesBranchHighFlowRequest = max(thisInletNode.MassFlowRateRequest, SeriesBranchHighFlowRequest);
     211            0 :                     SeriesBranchHardwareMaxLim = min(thisInletNode.MassFlowRateMax, SeriesBranchHardwareMaxLim);
     212            0 :                     SeriesBranchHardwareMinLim = max(thisInletNode.MassFlowRateMin, SeriesBranchHardwareMinLim);
     213            0 :                     SeriesBranchMaxAvail = min(thisInletNode.MassFlowRateMaxAvail, SeriesBranchMaxAvail);
     214            0 :                     SeriesBranchMinAvail = max(thisInletNode.MassFlowRateMinAvail, SeriesBranchMinAvail);
     215              :                     // check to see if any component on branch uses EMS On/Off Supervisory control to shut down flow
     216            0 :                     if (thisComp.EMSLoadOverrideOn && thisComp.EMSLoadOverrideValue == 0.0) {
     217            0 :                         EMSLoadOverride = true;
     218              :                     }
     219              :                 }
     220              : 
     221            0 :                 if (EMSLoadOverride) { // actuate EMS controlled components to 0 if On/Off Supervisory control is active off
     222            0 :                     SeriesBranchHardwareMaxLim = 0.0;
     223              :                 }
     224              : 
     225              :                 // take higher of branch max flow request and this new flow request
     226            0 :                 CompFlow = max(CompFlow, SeriesBranchHighFlowRequest);
     227              : 
     228              :                 // apply constraints on component flow
     229            0 :                 CompFlow = max(CompFlow, SeriesBranchHardwareMinLim);
     230            0 :                 CompFlow = max(CompFlow, SeriesBranchMinAvail);
     231            0 :                 CompFlow = min(CompFlow, SeriesBranchHardwareMaxLim);
     232            0 :                 CompFlow = min(CompFlow, SeriesBranchMaxAvail);
     233              : 
     234            0 :                 if (CompFlow < DataBranchAirLoopPlant::MassFlowTolerance) {
     235            0 :                     CompFlow = 0.0;
     236              :                 }
     237            0 :                 state.dataLoopNodes->Node(OutletNode).MassFlowRate = CompFlow;
     238            0 :                 state.dataLoopNodes->Node(InletNode).MassFlowRate = state.dataLoopNodes->Node(OutletNode).MassFlowRate;
     239            0 :                 for (int CompNum = 1; CompNum <= plantLoc.branch->TotalComponents; ++CompNum) {
     240            0 :                     auto const &thisComp = plantLoc.branch->Comp(CompNum);
     241            0 :                     int const CompInletNodeNum = thisComp.NodeNumIn;
     242            0 :                     int const CompOutletNodeNum = thisComp.NodeNumOut;
     243            0 :                     state.dataLoopNodes->Node(CompInletNodeNum).MassFlowRate = state.dataLoopNodes->Node(OutletNode).MassFlowRate;
     244            0 :                     state.dataLoopNodes->Node(CompOutletNodeNum).MassFlowRate = state.dataLoopNodes->Node(OutletNode).MassFlowRate;
     245              :                 }
     246              : 
     247              :             } else { // not series active
     248       495231 :                 state.dataLoopNodes->Node(OutletNode).MassFlowRate = max(state.dataLoopNodes->Node(OutletNode).MassFlowRateMinAvail, CompFlow);
     249       495231 :                 state.dataLoopNodes->Node(OutletNode).MassFlowRate =
     250       495231 :                     max(state.dataLoopNodes->Node(InletNode).MassFlowRateMin, state.dataLoopNodes->Node(OutletNode).MassFlowRate);
     251       495231 :                 state.dataLoopNodes->Node(OutletNode).MassFlowRate =
     252       495231 :                     min(state.dataLoopNodes->Node(OutletNode).MassFlowRateMaxAvail, state.dataLoopNodes->Node(OutletNode).MassFlowRate);
     253       495231 :                 state.dataLoopNodes->Node(OutletNode).MassFlowRate =
     254       495231 :                     min(state.dataLoopNodes->Node(InletNode).MassFlowRateMax, state.dataLoopNodes->Node(OutletNode).MassFlowRate);
     255              : 
     256              :                 // inserting EMS On/Off Supervisory control here to override min constraint assuming EMS should shut off flow completely
     257              :                 // action here means EMS will not impact the FlowLock == FlowLocked condition (which should still show EMS intent)
     258       495231 :                 bool EMSLoadOverride = false;
     259              : 
     260       990533 :                 for (int CompNum = 1; CompNum <= plantLoc.branch->TotalComponents; ++CompNum) {
     261              :                     // check to see if any component on branch uses EMS On/Off Supervisory control to shut down flow
     262       495302 :                     auto const &thisComp = plantLoc.branch->Comp(CompNum);
     263       495302 :                     if (thisComp.EMSLoadOverrideOn && thisComp.EMSLoadOverrideValue == 0.0) {
     264            2 :                         EMSLoadOverride = true;
     265              :                     }
     266              :                 }
     267              : 
     268       495231 :                 if (EMSLoadOverride) { // actuate EMS controlled components to 0 if On/Off Supervisory control is active off
     269            2 :                     state.dataLoopNodes->Node(OutletNode).MassFlowRate = 0.0;
     270              :                 }
     271              : 
     272       495231 :                 if (state.dataLoopNodes->Node(OutletNode).MassFlowRate < DataBranchAirLoopPlant::MassFlowTolerance) {
     273       223778 :                     state.dataLoopNodes->Node(OutletNode).MassFlowRate = 0.0;
     274              :                 }
     275       495231 :                 CompFlow = state.dataLoopNodes->Node(OutletNode).MassFlowRate;
     276       495231 :                 state.dataLoopNodes->Node(InletNode).MassFlowRate = state.dataLoopNodes->Node(OutletNode).MassFlowRate;
     277              :             }
     278              :         }
     279       229983 :     } else if (plantLoc.side->FlowLock == DataPlant::FlowLock::Locked) {
     280       229983 :         state.dataLoopNodes->Node(OutletNode).MassFlowRate = state.dataLoopNodes->Node(InletNode).MassFlowRate;
     281       229983 :         CompFlow = state.dataLoopNodes->Node(OutletNode).MassFlowRate;
     282              :     } else {
     283              :         ShowFatalError(state, "SetComponentFlowRate: Flow lock out of range"); // DEBUG error...should never get here LCOV_EXCL_LINE
     284              :     }
     285              : 
     286       725293 :     if (plantLoc.comp->CurOpSchemeType == DataPlant::OpScheme::Demand) {
     287       366474 :         if ((MdotOldRequest > 0.0) && (CompFlow > 0.0)) { // sure that not coming back from a no flow reset
     288       182348 :             if (std::abs(MdotOldRequest - state.dataLoopNodes->Node(InletNode).MassFlowRateRequest) >
     289              :                 DataBranchAirLoopPlant::MassFlowTolerance) { // demand comp changed its flow request
     290       100537 :                 plantLoc.side->SimLoopSideNeeded = true;
     291              :             }
     292              :         }
     293              :     }
     294              : }
     295              : 
     296       228545 : void SetActuatedBranchFlowRate(EnergyPlusData &state,
     297              :                                Real64 &CompFlow,
     298              :                                int const ActuatedNode,
     299              :                                PlantLocation const &plantLoc,
     300              :                                bool const ResetMode // flag to indicate if this is a real flow set, or a reset flow setting.
     301              : )
     302              : {
     303              : 
     304              :     // SUBROUTINE INFORMATION:
     305              :     //       AUTHOR         B. Griffith
     306              :     //       DATE WRITTEN   Feb 2010
     307              :     //       MODIFIED       na
     308              :     //       RE-ENGINEERED  na
     309              : 
     310              :     // PURPOSE OF THIS SUBROUTINE:
     311              :     // general purpse worker routine to set plant node variables for node
     312              :     // and all nodes on the branch.  Used by HVAC water coil controller, that do not
     313              :     //  distinguish single component and have no inlet-outlet pair
     314              :     //  only a actuated noded of no clear position.  set flow on entire branch
     315              : 
     316              :     // METHODOLOGY EMPLOYED:
     317              :     // Set flow on node and branch while honoring constraints on actuated node
     318              : 
     319       228545 :     auto &a_node = state.dataLoopNodes->Node(ActuatedNode);
     320       228545 :     if (plantLoc.loopNum == 0 || plantLoc.loopSideNum == DataPlant::LoopSideLocation::Invalid) {
     321              :         // early in simulation before plant loops are setup and found
     322            1 :         a_node.MassFlowRate = CompFlow;
     323            1 :         return;
     324              :     }
     325              : 
     326       228544 :     auto &loop_side = state.dataPlnt->PlantLoop(plantLoc.loopNum).LoopSide(plantLoc.loopSideNum);
     327              : 
     328              :     // store original flow
     329       228544 :     Real64 const MdotOldRequest = a_node.MassFlowRateRequest;
     330       228544 :     a_node.MassFlowRateRequest = CompFlow;
     331       228544 :     if (plantLoc.loopNum > 0 && plantLoc.loopSideNum != DataPlant::LoopSideLocation::Invalid && (!ResetMode)) {
     332       195395 :         if ((MdotOldRequest > 0.0) && (CompFlow > 0.0)) { // sure that not coming back from a no flow reset
     333       175431 :             if ((std::abs(MdotOldRequest - a_node.MassFlowRateRequest) > DataBranchAirLoopPlant::MassFlowTolerance) &&
     334        70037 :                 (loop_side.FlowLock == DataPlant::FlowLock::Unlocked)) {
     335        70037 :                 loop_side.SimLoopSideNeeded = true;
     336              :             }
     337              :         }
     338              :     }
     339              :     // Set loop flow rate
     340              : 
     341       228544 :     if (plantLoc.loopNum > 0 && plantLoc.loopSideNum != DataPlant::LoopSideLocation::Invalid) {
     342       228544 :         auto const &branch = loop_side.Branch(plantLoc.branchNum);
     343       228544 :         if (loop_side.FlowLock == DataPlant::FlowLock::Unlocked) {
     344       228538 :             if (state.dataPlnt->PlantLoop(plantLoc.loopNum).MaxVolFlowRate == DataSizing::AutoSize) { // still haven't sized the plant loop
     345           85 :                 a_node.MassFlowRate = CompFlow;
     346              :             } else { // bound the flow by Min/Max available across entire branch
     347              : 
     348       228453 :                 a_node.MassFlowRate = max(a_node.MassFlowRateMinAvail, CompFlow);
     349       228453 :                 a_node.MassFlowRate = max(a_node.MassFlowRateMin, a_node.MassFlowRate);
     350              :                 // add MassFlowRateMin hardware constraints
     351              : 
     352              :                 // inserting EMS On/Off Supervisory control here to override min constraint assuming EMS should shut off flow completely
     353              :                 // action here means EMS will not impact the FlowLock == FlowLocked condition (which should still show EMS intent)
     354       228453 :                 bool EMSLoadOverride = false;
     355              :                 // check to see if any component on branch uses EMS On/Off Supervisory control to shut down flow
     356       456912 :                 for (int CompNum = 1, CompNum_end = branch.TotalComponents; CompNum <= CompNum_end; ++CompNum) {
     357       228459 :                     auto const &comp = branch.Comp(CompNum);
     358       228459 :                     if (comp.EMSLoadOverrideOn && comp.EMSLoadOverrideValue == 0.0) {
     359            2 :                         EMSLoadOverride = true;
     360              :                     }
     361              :                 }
     362       228453 :                 if (EMSLoadOverride) { // actuate EMS controlled components to 0 if On/Off Supervisory control is active off
     363            2 :                     a_node.MassFlowRate = 0.0;
     364            2 :                     a_node.MassFlowRateRequest = 0.0;
     365              :                 }
     366              : 
     367       228453 :                 a_node.MassFlowRate = min(a_node.MassFlowRateMaxAvail, a_node.MassFlowRate);
     368       228453 :                 a_node.MassFlowRate = min(a_node.MassFlowRateMax, a_node.MassFlowRate);
     369       228453 :                 if (a_node.MassFlowRate < DataBranchAirLoopPlant::MassFlowTolerance) {
     370        95958 :                     a_node.MassFlowRate = 0.0;
     371              :                 }
     372       456912 :                 for (int CompNum = 1, CompNum_end = branch.TotalComponents; CompNum <= CompNum_end; ++CompNum) {
     373       228459 :                     auto const &comp = branch.Comp(CompNum);
     374       228459 :                     if (ActuatedNode == comp.NodeNumIn) {
     375              :                         //            ! found controller set to inlet of a component.  now set that component's outlet
     376       228453 :                         int const NodeNum = comp.NodeNumOut;
     377       228453 :                         state.dataLoopNodes->Node(NodeNum).MassFlowRateMinAvail = max(a_node.MassFlowRateMinAvail, a_node.MassFlowRateMin);
     378       228453 :                         state.dataLoopNodes->Node(NodeNum).MassFlowRateMaxAvail = min(a_node.MassFlowRateMaxAvail, a_node.MassFlowRateMax);
     379       228453 :                         state.dataLoopNodes->Node(NodeNum).MassFlowRate = a_node.MassFlowRate;
     380              :                     }
     381              :                 }
     382              :             }
     383              : 
     384            6 :         } else if (loop_side.FlowLock == DataPlant::FlowLock::Locked) {
     385              : 
     386            6 :             CompFlow = a_node.MassFlowRate;
     387              :             // do not change requested flow rate either
     388            6 :             a_node.MassFlowRateRequest = MdotOldRequest;
     389            6 :             if ((CompFlow - a_node.MassFlowRateMaxAvail > DataBranchAirLoopPlant::MassFlowTolerance) ||
     390            6 :                 (a_node.MassFlowRateMinAvail - CompFlow > DataBranchAirLoopPlant::MassFlowTolerance)) {
     391            0 :                 ShowSevereError(state, "SetActuatedBranchFlowRate: Flow rate is out of range"); // DEBUG error...should never get here
     392            0 :                 ShowContinueErrorTimeStamp(state, "");
     393            0 :                 ShowContinueError(state, format("Component flow rate [kg/s] = {:.8R}", CompFlow));
     394            0 :                 ShowContinueError(state, format("Node maximum flow rate available [kg/s] = {:.8R}", a_node.MassFlowRateMaxAvail));
     395            0 :                 ShowContinueError(state, format("Node minimum flow rate available [kg/s] = {:.8R}", a_node.MassFlowRateMinAvail));
     396              :             }
     397              :         } else {
     398            0 :             ShowFatalError(state,
     399            0 :                            format("SetActuatedBranchFlowRate: Flowlock out of range, value={}",
     400              :                                   loop_side.FlowLock)); // DEBUG error...should never get here LCOV_EXCL_LINE
     401              :         }
     402              : 
     403       228544 :         Real64 const a_node_MasFlowRate(a_node.MassFlowRate);
     404       228544 :         Real64 const a_node_MasFlowRateRequest(a_node.MassFlowRateRequest);
     405       457094 :         for (int CompNum = 1, CompNum_end = branch.TotalComponents; CompNum <= CompNum_end; ++CompNum) {
     406       228550 :             auto const &comp = branch.Comp(CompNum);
     407       228550 :             int NodeNum = comp.NodeNumIn;
     408       228550 :             state.dataLoopNodes->Node(NodeNum).MassFlowRate = a_node_MasFlowRate;
     409       228550 :             state.dataLoopNodes->Node(NodeNum).MassFlowRateRequest = a_node_MasFlowRateRequest;
     410       228550 :             NodeNum = comp.NodeNumOut;
     411       228550 :             state.dataLoopNodes->Node(NodeNum).MassFlowRate = a_node_MasFlowRate;
     412       228550 :             state.dataLoopNodes->Node(NodeNum).MassFlowRateRequest = a_node_MasFlowRateRequest;
     413              :         }
     414              :     }
     415              : }
     416              : 
     417        28540 : Real64 RegulateCondenserCompFlowReqOp(EnergyPlusData &state, PlantLocation const &plantLoc, Real64 const TentativeFlowRequest)
     418              : {
     419              : 
     420              :     // FUNCTION INFORMATION:
     421              :     //       AUTHOR         Edwin Lee
     422              :     //       DATE WRITTEN   April 2012
     423              :     //       MODIFIED       na
     424              :     //       RE-ENGINEERED  na
     425              : 
     426              :     // PURPOSE OF THIS FUNCTION:
     427              :     // This function will do some intelligent flow request logic for condenser equipment.
     428              :     // Some condenser equipment (ground heat exchangers, etc.) may not have a meaningful load value
     429              :     //  since this is an environment heat transfer component.
     430              :     // The runflag is set, but may not be properly set, and the component may still request flow even
     431              :     //  when it doesn't need to.
     432              :     // This function will do a little more advanced logic than just checking runflag to determine whether
     433              :     //  to request any flow
     434              : 
     435              :     // METHODOLOGY EMPLOYED:
     436              :     // Query run flag and MyLoad
     437              :     // If run flag is OFF, then the component should actually be OFF, and tentative flow request will be zeroed
     438              :     // If the run flag is ON, then check the control type to determine if MyLoad is a meaningful value
     439              :     // If it is meaningful then determine whether to do flow request based on MyLoad
     440              :     // If not then we will have no choice but to leave the flow request alone (uncontrolled operation?)
     441              : 
     442              :     // Using/Aliasing
     443              : 
     444              :     // Return value
     445              :     Real64 FlowVal;
     446              : 
     447              :     // FUNCTION PARAMETER DEFINITIONS:
     448        28540 :     Real64 constexpr ZeroLoad(0.0001);
     449              : 
     450              :     // FUNCTION LOCAL VARIABLE DECLARATIONS:
     451              :     Real64 CompCurLoad;
     452              :     bool CompRunFlag;
     453              : 
     454        28540 :     CompCurLoad = DataPlant::CompData::getPlantComponent(state, plantLoc).MyLoad;
     455        28540 :     CompRunFlag = DataPlant::CompData::getPlantComponent(state, plantLoc).ON;
     456        28540 :     DataPlant::OpScheme CompOpScheme = DataPlant::CompData::getPlantComponent(state, plantLoc).CurOpSchemeType;
     457              : 
     458        28540 :     if (CompRunFlag) {
     459              : 
     460        22313 :         switch (CompOpScheme) {
     461              : 
     462         8501 :         case DataPlant::OpScheme::HeatingRB:
     463              :         case DataPlant::OpScheme::CoolingRB:
     464              :         case DataPlant::OpScheme::CompSetPtBased: { // These provide meaningful MyLoad values
     465         8501 :             if (std::abs(CompCurLoad) > ZeroLoad) {
     466         8498 :                 FlowVal = TentativeFlowRequest;
     467              :             } else { // no load
     468            3 :                 FlowVal = 0.0;
     469              :             }
     470         8501 :             break;
     471              :         }
     472        13812 :         default: { // Types that don't provide meaningful MyLoad values
     473        13812 :             FlowVal = TentativeFlowRequest;
     474        13812 :             break;
     475              :         }
     476              :         }
     477              : 
     478              :     } else { // runflag OFF
     479              : 
     480         6227 :         FlowVal = 0.0;
     481              :     }
     482              : 
     483        28540 :     return FlowVal;
     484              : }
     485              : 
     486        12437 : bool AnyPlantSplitterMixerLacksContinuity(EnergyPlusData &state)
     487              : {
     488              : 
     489              :     // FUNCTION INFORMATION:
     490              :     //       AUTHOR         B. Griffith
     491              :     //       DATE WRITTEN   April 2012
     492              :     //       MODIFIED       na
     493              :     //       RE-ENGINEERED  na
     494              : 
     495              :     // PURPOSE OF THIS FUNCTION:
     496              :     // Similar to CheckPlantMixerSplitterConsistency, but used to decide if plant needs to iterate again
     497        33913 :     for (int LoopNum = 1; LoopNum <= state.dataPlnt->TotNumLoops; ++LoopNum) {
     498        64432 :         for (DataPlant::LoopSideLocation LoopSide : DataPlant::LoopSideKeys) {
     499        42956 :             if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).Splitter.Exists) {
     500        42952 :                 int const SplitterInletNode = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).Splitter.NodeNumIn;
     501              :                 // loop across branch outlet nodes and check mass continuity
     502        42952 :                 int const NumSplitterOutlets = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).Splitter.TotalOutletNodes;
     503        42952 :                 Real64 SumOutletFlow = 0.0;
     504       152278 :                 for (int OutletNum = 1; OutletNum <= NumSplitterOutlets; ++OutletNum) {
     505       109326 :                     int const BranchNum = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).Splitter.BranchNumOut(OutletNum);
     506       109326 :                     int const LastNodeOnBranch = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).Branch(BranchNum).NodeNumOut;
     507       109326 :                     SumOutletFlow += state.dataLoopNodes->Node(LastNodeOnBranch).MassFlowRate;
     508              :                 }
     509        42952 :                 Real64 const AbsDifference = std::abs(state.dataLoopNodes->Node(SplitterInletNode).MassFlowRate - SumOutletFlow);
     510        42952 :                 if (AbsDifference > DataPlant::CriteriaDelta_MassFlowRate) {
     511            2 :                     return true;
     512              :                 }
     513              :             }
     514              :         }
     515              :     }
     516        12435 :     return false;
     517              : }
     518              : 
     519        42948 : void CheckPlantMixerSplitterConsistency(EnergyPlusData &state,
     520              :                                         int const LoopNum,
     521              :                                         const DataPlant::LoopSideLocation LoopSideNum,
     522              :                                         bool const FirstHVACIteration)
     523              : {
     524              : 
     525              :     // SUBROUTINE INFORMATION:
     526              :     //       AUTHOR         Brent Griffith
     527              :     //       DATE WRITTEN   Oct 2007
     528              :     //       MODIFIED       na
     529              :     //       RE-ENGINEERED  na
     530              : 
     531              :     // PURPOSE OF THIS SUBROUTINE:
     532              :     // Check for plant flow resolver errors
     533              : 
     534              :     // METHODOLOGY EMPLOYED:
     535              :     // compare flow rate of splitter inlet to flow rate of mixer outlet
     536              : 
     537              :     // Using/Aliasing
     538              :     using DataPlant::CriteriaDelta_MassFlowRate;
     539              : 
     540              :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
     541              :     Real64 AbsDifference;
     542              :     Real64 SumOutletFlow;
     543              : 
     544        42948 :     if (!state.dataPlnt->PlantLoop(LoopNum).LoopHasConnectionComp) {
     545        85896 :         if (!state.dataGlobal->DoingSizing && !state.dataGlobal->WarmupFlag &&
     546        85896 :             state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Mixer.Exists && !FirstHVACIteration) {
     547              :             // Find mixer outlet node number
     548         7666 :             int MixerOutletNode = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Mixer.NodeNumOut;
     549              :             // Find splitter inlet node number
     550         7666 :             int SplitterInletNode = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Splitter.NodeNumIn;
     551              : 
     552         7666 :             AbsDifference =
     553         7666 :                 std::abs(state.dataLoopNodes->Node(SplitterInletNode).MassFlowRate - state.dataLoopNodes->Node(MixerOutletNode).MassFlowRate);
     554         7666 :             if (AbsDifference > DataBranchAirLoopPlant::MassFlowTolerance) {
     555            0 :                 if (state.dataPlnt->PlantLoop(LoopNum).MFErrIndex1 == 0) {
     556            0 :                     ShowSevereMessage(state, "Plant flows do not resolve -- splitter inlet flow does not match mixer outlet flow ");
     557            0 :                     ShowContinueErrorTimeStamp(state, "");
     558            0 :                     ShowContinueError(state, format("PlantLoop name= {}", state.dataPlnt->PlantLoop(LoopNum).Name));
     559            0 :                     ShowContinueError(state,
     560            0 :                                       format("Plant Connector:Mixer name= {}", state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Mixer.Name));
     561            0 :                     ShowContinueError(
     562            0 :                         state, format("Mixer outlet mass flow rate= {:.6R} {{kg/s}}", state.dataLoopNodes->Node(MixerOutletNode).MassFlowRate));
     563            0 :                     ShowContinueError(
     564            0 :                         state, format("Plant Connector:Splitter name= {}", state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Splitter.Name));
     565            0 :                     ShowContinueError(
     566            0 :                         state, format("Splitter inlet mass flow rate= {:.6R} {{kg/s}}", state.dataLoopNodes->Node(SplitterInletNode).MassFlowRate));
     567            0 :                     ShowContinueError(state, format("Difference in two mass flow rates= {:.6R} {{kg/s}}", AbsDifference));
     568              :                 }
     569            0 :                 ShowRecurringSevereErrorAtEnd(state,
     570            0 :                                               "Plant Flows (Loop=" + state.dataPlnt->PlantLoop(LoopNum).Name +
     571              :                                                   ") splitter inlet flow not match mixer outlet flow",
     572            0 :                                               state.dataPlnt->PlantLoop(LoopNum).MFErrIndex1,
     573              :                                               AbsDifference,
     574              :                                               AbsDifference,
     575              :                                               _,
     576              :                                               "kg/s",
     577              :                                               "kg/s");
     578            0 :                 if (AbsDifference > DataBranchAirLoopPlant::MassFlowTolerance * 10.0) {
     579            0 :                     ShowSevereError(state, "Plant flows do not resolve -- splitter inlet flow does not match mixer outlet flow ");
     580            0 :                     ShowContinueErrorTimeStamp(state, "");
     581            0 :                     ShowContinueError(state, format("PlantLoop name= {}", state.dataPlnt->PlantLoop(LoopNum).Name));
     582            0 :                     ShowContinueError(state,
     583            0 :                                       format("Plant Connector:Mixer name= {}", state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Mixer.Name));
     584            0 :                     ShowContinueError(
     585            0 :                         state, format("Mixer outlet mass flow rate= {:.6R} {{kg/s}}", state.dataLoopNodes->Node(MixerOutletNode).MassFlowRate));
     586            0 :                     ShowContinueError(
     587            0 :                         state, format("Plant Connector:Splitter name= {}", state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Splitter.Name));
     588            0 :                     ShowContinueError(
     589            0 :                         state, format("Splitter inlet mass flow rate= {:.6R} {{kg/s}}", state.dataLoopNodes->Node(SplitterInletNode).MassFlowRate));
     590            0 :                     ShowContinueError(state, format("Difference in two mass flow rates= {:.6R} {{kg/s}}", AbsDifference));
     591            0 :                     ShowFatalError(state, "CheckPlantMixerSplitterConsistency: Simulation terminated because of problems in plant flow resolver");
     592              :                 }
     593              :             }
     594              : 
     595              :             // now check inside s/m to see if there are problems
     596              : 
     597              :             // loop across branch outlet nodes and check mass continuity
     598         7666 :             int NumSplitterOutlets = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Splitter.TotalOutletNodes;
     599         7666 :             SumOutletFlow = 0.0;
     600              :             //  SumInletFlow = 0.0;
     601        25045 :             for (int OutletNum = 1; OutletNum <= NumSplitterOutlets; ++OutletNum) {
     602        17379 :                 int BranchNum = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Splitter.BranchNumOut(OutletNum);
     603        17379 :                 int LastNodeOnBranch = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).NodeNumOut;
     604        17379 :                 SumOutletFlow += state.dataLoopNodes->Node(LastNodeOnBranch).MassFlowRate;
     605              :                 //  FirstNodeOnBranch= PlantLoop(LoopNum)%LoopSide(LoopSideNum)%Branch(BranchNum)%NodeNumIn
     606              :                 //  SumInletFlow = SumInletFlow + Node(FirstNodeOnBranch)%MassFlowRate
     607              :             }
     608         7666 :             AbsDifference = std::abs(state.dataLoopNodes->Node(SplitterInletNode).MassFlowRate - SumOutletFlow);
     609         7666 :             if (AbsDifference > CriteriaDelta_MassFlowRate) {
     610            0 :                 if (state.dataPlnt->PlantLoop(LoopNum).MFErrIndex2 == 0) {
     611            0 :                     ShowSevereMessage(state, "Plant flows do not resolve -- splitter inlet flow does not match branch outlet flows");
     612            0 :                     ShowContinueErrorTimeStamp(state, "");
     613            0 :                     ShowContinueError(state, format("PlantLoop name= {}", state.dataPlnt->PlantLoop(LoopNum).Name));
     614            0 :                     ShowContinueError(state,
     615            0 :                                       format("Plant Connector:Mixer name= {}", state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Mixer.Name));
     616            0 :                     ShowContinueError(state, format("Sum of Branch outlet mass flow rates= {:.6R} {{kg/s}}", SumOutletFlow));
     617            0 :                     ShowContinueError(
     618            0 :                         state, format("Plant Connector:Splitter name= {}", state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Splitter.Name));
     619            0 :                     ShowContinueError(
     620            0 :                         state, format("Splitter inlet mass flow rate= {:.6R} {{kg/s}}", state.dataLoopNodes->Node(SplitterInletNode).MassFlowRate));
     621            0 :                     ShowContinueError(state, format("Difference in two mass flow rates= {:.6R} {{kg/s}}", AbsDifference));
     622              :                 }
     623            0 :                 ShowRecurringSevereErrorAtEnd(state,
     624            0 :                                               "Plant Flows (Loop=" + state.dataPlnt->PlantLoop(LoopNum).Name +
     625              :                                                   ") splitter inlet flow does not match branch outlet flows",
     626            0 :                                               state.dataPlnt->PlantLoop(LoopNum).MFErrIndex2,
     627              :                                               AbsDifference,
     628              :                                               AbsDifference,
     629              :                                               _,
     630              :                                               "kg/s",
     631              :                                               "kg/s");
     632              :             }
     633              :         }
     634              :     }
     635        42948 : }
     636              : 
     637        42948 : void CheckForRunawayPlantTemps(EnergyPlusData &state, int const LoopNum, const DataPlant::LoopSideLocation LoopSideNum)
     638              : {
     639              : 
     640              :     // SUBROUTINE INFORMATION:
     641              :     //       AUTHOR         Brent Griffith
     642              :     //       DATE WRITTEN   Sept 2010
     643              :     //       MODIFIED       na
     644              :     //       RE-ENGINEERED  na
     645              : 
     646              :     // PURPOSE OF THIS SUBROUTINE:
     647              :     // Check for plant control errors revealed as run away fluid temps
     648              :     //  halt program so it won't siliently run in out of control state
     649              : 
     650              :     // METHODOLOGY EMPLOYED:
     651              :     //  compare plant temps to plant min and max and halt if things run away
     652              :     //  sensitivity can be adjusted with parameters, picked somewhat arbitrary
     653              : 
     654              :     // REFERENCES:
     655              :     // na
     656              : 
     657              :     // Using/Aliasing
     658              : 
     659              :     // Locals
     660              :     // SUBROUTINE ARGUMENT DEFINITIONS:
     661              : 
     662              :     // SUBROUTINE PARAMETER DEFINITIONS:
     663        42948 :     Real64 constexpr OverShootOffset(5.0);
     664        42948 :     Real64 constexpr UnderShootOffset(5.0);
     665        42948 :     Real64 constexpr FatalOverShootOffset(200.0);
     666        42948 :     Real64 constexpr FatalUnderShootOffset(100.0);
     667              :     // INTERFACE BLOCK SPECIFICATIONS:
     668              :     // na
     669              : 
     670              :     // DERIVED TYPE DEFINITIONS:
     671              :     // na
     672              : 
     673              :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
     674        42948 :     std::string hotcold;
     675              :     bool makefatalerror;
     676              :     Real64 LoopCapacity;
     677              :     Real64 LoopDemandSideCapacity;
     678              :     Real64 LoopSupplySideCapacity;
     679              :     Real64 DispatchedCapacity;
     680              :     Real64 LoopDemandSideDispatchedCapacity;
     681              :     Real64 LoopSupplySideDispatchedCapacity;
     682              : 
     683        42948 :     makefatalerror = false;
     684        42948 :     if (state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).NodeNumOut).Temp >
     685        42948 :         (state.dataPlnt->PlantLoop(LoopNum).MaxTemp + OverShootOffset)) {
     686              : 
     687              :         // first stage, throw recurring warning that plant loop is getting out of control
     688         4508 :         ShowRecurringWarningErrorAtEnd(state,
     689         1288 :                                        "Plant loop exceeding upper temperature limit, PlantLoop=\"" + state.dataPlnt->PlantLoop(LoopNum).Name + "\"",
     690          644 :                                        state.dataPlnt->PlantLoop(LoopNum).MaxTempErrIndex,
     691          644 :                                        state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).NodeNumOut).Temp);
     692              : 
     693          644 :         if (state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).NodeNumOut).Temp >
     694          644 :             (state.dataPlnt->PlantLoop(LoopNum).MaxTemp + FatalOverShootOffset)) {
     695            0 :             hotcold = "hot";
     696            0 :             makefatalerror = true;
     697              :         }
     698              :     }
     699              : 
     700        42948 :     if (state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).NodeNumOut).Temp <
     701        42948 :         (state.dataPlnt->PlantLoop(LoopNum).MinTemp - UnderShootOffset)) {
     702              : 
     703              :         // first stage, throw recurring warning that plant loop is getting out of control
     704            0 :         ShowRecurringWarningErrorAtEnd(state,
     705            0 :                                        "Plant loop falling below lower temperature limit, PlantLoop=\"" + state.dataPlnt->PlantLoop(LoopNum).Name +
     706              :                                            "\"",
     707            0 :                                        state.dataPlnt->PlantLoop(LoopNum).MinTempErrIndex,
     708              :                                        _,
     709            0 :                                        state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).NodeNumOut).Temp);
     710              : 
     711            0 :         if (state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).NodeNumOut).Temp <
     712            0 :             (state.dataPlnt->PlantLoop(LoopNum).MinTemp - FatalUnderShootOffset)) {
     713            0 :             hotcold = "cold";
     714            0 :             makefatalerror = true;
     715              :         }
     716              :     }
     717              : 
     718        42948 :     if (makefatalerror) {
     719            0 :         ShowSevereError(state, format("Plant temperatures are getting far too {}, check controls and relative loads and capacities", hotcold));
     720            0 :         ShowContinueErrorTimeStamp(state, "");
     721            0 :         ShowContinueError(state,
     722            0 :                           format("PlantLoop Name ({} Side) = {}",
     723            0 :                                  DataPlant::DemandSupplyNames[static_cast<int>(LoopSideNum)],
     724            0 :                                  state.dataPlnt->PlantLoop(LoopNum).Name));
     725            0 :         ShowContinueError(state,
     726            0 :                           format("PlantLoop Setpoint Temperature={:.1R} {{C}}",
     727            0 :                                  state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).TempSetPointNodeNum).TempSetPoint));
     728            0 :         if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Supply).InletNodeSetPt) {
     729            0 :             ShowContinueError(state, "PlantLoop Inlet Node (LoopSideLocation::Supply) has a Setpoint.");
     730              :         } else {
     731            0 :             ShowContinueError(state, "PlantLoop Inlet Node (LoopSideLocation::Supply) does not have a Setpoint.");
     732              :         }
     733            0 :         if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Demand).InletNodeSetPt) {
     734            0 :             ShowContinueError(state, "PlantLoop Inlet Node (LoopSideLocation::Demand) has a Setpoint.");
     735              :         } else {
     736            0 :             ShowContinueError(state, "PlantLoop Inlet Node (LoopSideLocation::Demand) does not have a Setpoint.");
     737              :         }
     738            0 :         if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Supply).OutletNodeSetPt) {
     739            0 :             ShowContinueError(state, "PlantLoop Outlet Node (LoopSideLocation::Supply) has a Setpoint.");
     740              :         } else {
     741            0 :             ShowContinueError(state, "PlantLoop Outlet Node (LoopSideLocation::Supply) does not have a Setpoint.");
     742              :         }
     743            0 :         if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Demand).OutletNodeSetPt) {
     744            0 :             ShowContinueError(state, "PlantLoop Outlet Node (LoopSideLocation::Demand) has a Setpoint.");
     745              :         } else {
     746            0 :             ShowContinueError(state, "PlantLoop Outlet Node (LoopSideLocation::Demand) does not have a Setpoint.");
     747              :         }
     748            0 :         ShowContinueError(state,
     749            0 :                           format("PlantLoop Outlet Node ({}Side) \"{}\" has temperature={:.1R} {{C}}",
     750            0 :                                  DataPlant::DemandSupplyNames[static_cast<int>(LoopSideNum)],
     751            0 :                                  state.dataLoopNodes->NodeID(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).NodeNumOut),
     752            0 :                                  state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).NodeNumOut).Temp));
     753            0 :         ShowContinueError(state,
     754            0 :                           format("PlantLoop Inlet Node ({}Side) \"{}\" has temperature={:.1R} {{C}}",
     755            0 :                                  DataPlant::DemandSupplyNames[static_cast<int>(LoopSideNum)],
     756            0 :                                  state.dataLoopNodes->NodeID(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).NodeNumIn),
     757            0 :                                  state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).NodeNumIn).Temp));
     758            0 :         ShowContinueError(state, format("PlantLoop Minimum Temperature={:.1R} {{C}}", state.dataPlnt->PlantLoop(LoopNum).MinTemp));
     759            0 :         ShowContinueError(state, format("PlantLoop Maximum Temperature={:.1R} {{C}}", state.dataPlnt->PlantLoop(LoopNum).MaxTemp));
     760            0 :         ShowContinueError(state,
     761            0 :                           format("PlantLoop Flow Request (LoopSideLocation::Supply)={:.1R} {{kg/s}}",
     762            0 :                                  state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Supply).FlowRequest));
     763            0 :         ShowContinueError(state,
     764            0 :                           format("PlantLoop Flow Request (LoopSideLocation::Demand)={:.1R} {{kg/s}}",
     765            0 :                                  state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Demand).FlowRequest));
     766            0 :         ShowContinueError(state,
     767            0 :                           format("PlantLoop Node ({}Side) \"{}\" has mass flow rate ={:.1R} {{kg/s}}",
     768            0 :                                  DataPlant::DemandSupplyNames[static_cast<int>(LoopSideNum)],
     769            0 :                                  state.dataLoopNodes->NodeID(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).NodeNumOut),
     770            0 :                                  state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).NodeNumOut).MassFlowRate));
     771            0 :         ShowContinueError(state,
     772            0 :                           format("PlantLoop PumpHeat (LoopSideLocation::Supply)={:.1R} {{W}}",
     773            0 :                                  state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Supply).TotalPumpHeat));
     774            0 :         ShowContinueError(state,
     775            0 :                           format("PlantLoop PumpHeat (LoopSideLocation::Demand)={:.1R} {{W}}",
     776            0 :                                  state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Demand).TotalPumpHeat));
     777            0 :         ShowContinueError(state, format("PlantLoop Cooling Demand={:.1R} {{W}}", state.dataPlnt->PlantLoop(LoopNum).CoolingDemand));
     778            0 :         ShowContinueError(state, format("PlantLoop Heating Demand={:.1R} {{W}}", state.dataPlnt->PlantLoop(LoopNum).HeatingDemand));
     779            0 :         ShowContinueError(state, format("PlantLoop Demand not Dispatched={:.1R} {{W}}", state.dataPlnt->PlantLoop(LoopNum).DemandNotDispatched));
     780            0 :         ShowContinueError(state, format("PlantLoop Unmet Demand={:.1R} {{W}}", state.dataPlnt->PlantLoop(LoopNum).UnmetDemand));
     781              : 
     782            0 :         LoopCapacity = 0.0;
     783            0 :         DispatchedCapacity = 0.0;
     784            0 :         for (DataPlant::LoopSideLocation LSN : DataPlant::LoopSideKeys) {
     785            0 :             for (int BrN = 1; BrN <= state.dataPlnt->PlantLoop(LoopNum).LoopSide(LSN).TotalBranches; ++BrN) {
     786            0 :                 for (int CpN = 1; CpN <= state.dataPlnt->PlantLoop(LoopNum).LoopSide(LSN).Branch(BrN).TotalComponents; ++CpN) {
     787            0 :                     LoopCapacity += state.dataPlnt->PlantLoop(LoopNum).LoopSide(LSN).Branch(BrN).Comp(CpN).MaxLoad;
     788            0 :                     DispatchedCapacity += std::abs(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LSN).Branch(BrN).Comp(CpN).MyLoad);
     789              :                 }
     790              :             }
     791            0 :             if (LSN == DataPlant::LoopSideLocation::Demand) {
     792            0 :                 LoopDemandSideCapacity = LoopCapacity;
     793            0 :                 LoopDemandSideDispatchedCapacity = DispatchedCapacity;
     794              :             } else {
     795            0 :                 LoopSupplySideCapacity = LoopCapacity - LoopDemandSideCapacity;
     796            0 :                 LoopSupplySideDispatchedCapacity = DispatchedCapacity - LoopDemandSideDispatchedCapacity;
     797              :             }
     798              :         }
     799            0 :         ShowContinueError(state, format("PlantLoop Capacity={:.1R} {{W}}", LoopCapacity));
     800            0 :         ShowContinueError(state, format("PlantLoop Capacity (LoopSideLocation::Supply)={:.1R} {{W}}", LoopSupplySideCapacity));
     801            0 :         ShowContinueError(state, format("PlantLoop Capacity (LoopSideLocation::Demand)={:.1R} {{W}}", LoopDemandSideCapacity));
     802            0 :         ShowContinueError(state, format("PlantLoop Operation Scheme={}", state.dataPlnt->PlantLoop(LoopNum).OperationScheme));
     803            0 :         ShowContinueError(state, format("PlantLoop Operation Dispatched Load = {:.1R} {{W}}", DispatchedCapacity));
     804            0 :         ShowContinueError(state,
     805            0 :                           format("PlantLoop Operation Dispatched Load (LoopSideLocation::Supply)= {:.1R} {{W}}", LoopSupplySideDispatchedCapacity));
     806            0 :         ShowContinueError(state,
     807            0 :                           format("PlantLoop Operation Dispatched Load (LoopSideLocation::Demand)= {:.1R} {{W}}", LoopDemandSideDispatchedCapacity));
     808            0 :         ShowContinueError(state, "Branches on the Loop.");
     809            0 :         ShowBranchesOnLoop(state, LoopNum);
     810            0 :         ShowContinueError(state, "*************************");
     811            0 :         ShowContinueError(state, "Possible things to look for to correct this problem are:");
     812            0 :         ShowContinueError(state, "  Capacity, Operation Scheme, Mass flow problems, Pump Heat building up over time.");
     813            0 :         ShowContinueError(state, "  Try a shorter runperiod to stop before it fatals and look at");
     814            0 :         ShowContinueError(state, "    lots of node time series data to see what is going wrong.");
     815            0 :         ShowContinueError(state, "  If this is happening during Warmup, you can use Output:Diagnostics,ReportDuringWarmup;");
     816            0 :         ShowContinueError(state, "  This is detected at the loop level, but the typical problems are in the components.");
     817            0 :         ShowFatalError(state, format("CheckForRunawayPlantTemps: Simulation terminated because of run away plant temperatures, too {}", hotcold));
     818              :     }
     819        42948 : }
     820              : 
     821       424800 : void SetAllFlowLocks(EnergyPlusData &state, DataPlant::FlowLock const Value)
     822              : {
     823              : 
     824              :     // SUBROUTINE INFORMATION:
     825              :     //       AUTHOR         Edwin Lee
     826              :     //       DATE WRITTEN   November 2009
     827              :     //       MODIFIED       na
     828              :     //       RE-ENGINEERED  na
     829              : 
     830              :     // PURPOSE OF THIS SUBROUTINE:
     831              :     // This subroutine will set both LoopSide flowlocks on all plant loops to the input value (0 or 1)
     832              :     // Initially this routine is used as a quick replacement for the FlowLock=0 and FlowLock=1 statements
     833              :     //  in order to provide the same behavior through phase I of the demand side rewrite
     834              :     // Eventually this routine may be employed again to quickly initialize all loops once phase III is complete
     835       467789 :     for (int LoopNum = 1; LoopNum <= state.dataPlnt->TotNumLoops; ++LoopNum) {
     836       128967 :         for (DataPlant::LoopSideLocation LoopSideNum : DataPlant::LoopSideKeys) {
     837        85978 :             state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).FlowLock = Value;
     838              :         }
     839              :     }
     840       424800 : }
     841              : 
     842       424777 : void ResetAllPlantInterConnectFlags(EnergyPlusData &state)
     843              : {
     844              : 
     845              :     // SUBROUTINE INFORMATION:
     846              :     //       AUTHOR         Edwin Lee
     847              :     //       DATE WRITTEN   September 2010
     848              :     //       MODIFIED       na
     849              :     //       RE-ENGINEERED  na
     850              : 
     851              :     // PURPOSE OF THIS SUBROUTINE:
     852              :     // This subroutine will reset all interconnected (air, zone, etc.) sim flags for both loopsides of all loops
     853              : 
     854       467727 :     for (int LoopNum = 1; LoopNum <= state.dataPlnt->TotNumLoops; ++LoopNum) {
     855       128850 :         for (auto &e : state.dataPlnt->PlantLoop(LoopNum).LoopSide) {
     856        85900 :             e.SimAirLoopsNeeded = false;
     857        85900 :             e.SimZoneEquipNeeded = false;
     858        85900 :             e.SimNonZoneEquipNeeded = false;
     859        85900 :             e.SimElectLoadCentrNeeded = false;
     860              :         }
     861              :     }
     862       424777 : }
     863              : 
     864          563 : void PullCompInterconnectTrigger(EnergyPlusData &state,
     865              :                                  const PlantLocation &plantLoc,              // Component Location
     866              :                                  int &UniqueCriteriaCheckIndex,              // An integer given to this particular check
     867              :                                  const PlantLocation &ConnectedPlantLoc,     // Interconnected Component's Location
     868              :                                  const DataPlant::CriteriaType CriteriaType, // The criteria check to use, see DataPlant: SimFlagCriteriaTypes
     869              :                                  const Real64 CriteriaValue                  // The value of the criteria check to evaluate
     870              : )
     871              : {
     872              : 
     873              :     // SUBROUTINE INFORMATION:
     874              :     //       AUTHOR         Edwin Lee
     875              :     //       DATE WRITTEN   September 2010
     876              : 
     877              :     // PURPOSE OF THIS SUBROUTINE:
     878              :     // Provides a generic means for components to trigger interconnected loop sides sim flags
     879              : 
     880              :     // METHODOLOGY EMPLOYED:
     881              :     // Determine convergence criteria based on *CriteriaType* variable.  This routine only turns
     882              :     //  the loop side sim flag ON, it doesn't turn it OFF.
     883              :     // The convergence value history was originally going to be put at the Branch()%Comp()%...
     884              :     //  level, but this would be quite difficult if we had multiple convergence checks for the
     885              :     //  same component, such as if a chiller was trying to turn on the condenser side and the
     886              :     //  heat recovery side.
     887              :     // It was determined to use a local array, which is only reallocated during the first stages
     888              :     //  of the simulation when components are first calling their sim flag requests.  After that
     889              :     //  the INOUT index variable will be used to avoid reallocation and string compares.
     890              :     // Error handling will be put in to ensure unique identifiers are used for debugging purposes.
     891              :     // A single component may have multiple check indeces, but a single index will only have one
     892              :     //  associated component.  Therefore whenever we come in with a non-zero index, we will just
     893              :     //  verify that the stored loop/side/branch/comp matches
     894              : 
     895          563 :     if (UniqueCriteriaCheckIndex <= 0) { // If we don't yet have an index, we need to initialize
     896              : 
     897              :         // We need to start by allocating, or REallocating the array
     898           15 :         int const CurrentNumChecksStored(static_cast<int>(state.dataPlantUtilities->CriteriaChecks.size() + 1));
     899           15 :         state.dataPlantUtilities->CriteriaChecks.redimension(CurrentNumChecksStored);
     900              : 
     901              :         // Store the unique name and location
     902           15 :         state.dataPlantUtilities->CriteriaChecks(CurrentNumChecksStored).CallingCompLoopNum = plantLoc.loopNum;
     903           15 :         state.dataPlantUtilities->CriteriaChecks(CurrentNumChecksStored).CallingCompLoopSideNum = plantLoc.loopSideNum;
     904              : 
     905           15 :         if (plantLoc.loopNum == 0 || plantLoc.loopSideNum == DataPlant::LoopSideLocation::Invalid) {
     906            0 :             assert(false); // check that component has been set up correctly
     907              :         }
     908              :         // Since this was the first pass, it is safe to assume something has changed!
     909              :         // Therefore we'll set the sim flag to true
     910           15 :         state.dataPlnt->PlantLoop(ConnectedPlantLoc.loopNum).LoopSide(ConnectedPlantLoc.loopSideNum).SimLoopSideNeeded = true;
     911              : 
     912              :         // Make sure we return the proper value of index
     913           15 :         UniqueCriteriaCheckIndex = CurrentNumChecksStored;
     914              : 
     915              :     } else { // We already have an index
     916              : 
     917              :         // If we have an index, we need to do a brief error handling, then determine
     918              :         //  sim flag status based on the criteria type
     919              : 
     920              :         // First store the current check in a single variable instead of array for readability
     921          548 :         CriteriaData CurCriteria = state.dataPlantUtilities->CriteriaChecks(UniqueCriteriaCheckIndex);
     922              : 
     923              :         // Initialize, then check if we are out of range
     924          548 :         switch (CriteriaType) {
     925          542 :         case DataPlant::CriteriaType::MassFlowRate: {
     926          542 :             if (std::abs(CurCriteria.ThisCriteriaCheckValue - CriteriaValue) > DataPlant::CriteriaDelta_MassFlowRate) {
     927           55 :                 state.dataPlnt->PlantLoop(ConnectedPlantLoc.loopNum).LoopSide(ConnectedPlantLoc.loopSideNum).SimLoopSideNeeded = true;
     928              :             }
     929          542 :         } break;
     930            3 :         case DataPlant::CriteriaType::Temperature: {
     931            3 :             if (std::abs(CurCriteria.ThisCriteriaCheckValue - CriteriaValue) > DataPlant::CriteriaDelta_Temperature) {
     932            1 :                 state.dataPlnt->PlantLoop(ConnectedPlantLoc.loopNum).LoopSide(ConnectedPlantLoc.loopSideNum).SimLoopSideNeeded = true;
     933              :             }
     934            3 :         } break;
     935            3 :         case DataPlant::CriteriaType::HeatTransferRate: {
     936            3 :             if (std::abs(CurCriteria.ThisCriteriaCheckValue - CriteriaValue) > DataPlant::CriteriaDelta_HeatTransferRate) {
     937            1 :                 state.dataPlnt->PlantLoop(ConnectedPlantLoc.loopNum).LoopSide(ConnectedPlantLoc.loopSideNum).SimLoopSideNeeded = true;
     938              :             }
     939            3 :         } break;
     940            0 :         default:
     941            0 :             assert(false);
     942              :             break;
     943              :         }
     944              : 
     945              :     } // if we have an index or not
     946              : 
     947              :     // Store the value for the next pass
     948          563 :     state.dataPlantUtilities->CriteriaChecks(UniqueCriteriaCheckIndex).ThisCriteriaCheckValue = CriteriaValue;
     949          563 : }
     950              : 
     951        14321 : void UpdateChillerComponentCondenserSide(EnergyPlusData &state,
     952              :                                          int const LoopNum,                                   // component's loop index
     953              :                                          const DataPlant::LoopSideLocation LoopSide,          // component's loop side number
     954              :                                          [[maybe_unused]] DataPlant::PlantEquipmentType Type, // Component's type index
     955              :                                          int const InletNodeNum,                              // Component's inlet node pointer
     956              :                                          int const OutletNodeNum,                             // Component's outlet node pointer
     957              :                                          Real64 const ModelCondenserHeatRate,                 // model's heat rejection rate at condenser (W)
     958              :                                          Real64 const ModelInletTemp,                         // model's inlet temperature (C)
     959              :                                          Real64 const ModelOutletTemp,                        // model's outlet temperature (C)
     960              :                                          Real64 const ModelMassFlowRate,                      // model's condenser water mass flow rate (kg/s)
     961              :                                          bool const FirstHVACIteration)
     962              : {
     963              : 
     964              :     // SUBROUTINE INFORMATION:
     965              :     //       AUTHOR         Brent Griffith
     966              :     //       DATE WRITTEN   February 2010
     967              :     //       MODIFIED       na
     968              :     //       RE-ENGINEERED  na
     969              : 
     970              :     // PURPOSE OF THIS SUBROUTINE:
     971              :     // provides reusable update routine for water cooled chiller's condenser water
     972              :     // connection to plant loops
     973              : 
     974              :     // METHODOLOGY EMPLOYED:
     975              :     // check if anything changed or doesn't agree and set simulation flags.
     976              :     // update outlet conditions if needed or possible
     977              : 
     978              :     // SUBROUTINE PARAMETER DEFINITIONS:
     979              :     static constexpr std::string_view RoutineName("UpdateChillerComponentCondenserSide");
     980              : 
     981              :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
     982        14321 :     bool DidAnythingChange(false);             // set to true if conditions changed
     983              :     DataPlant::LoopSideLocation OtherLoopSide; // local loop side pointer for remote connected loop
     984              :     Real64 Cp;
     985              : 
     986              :     // check if any conditions have changed
     987        14321 :     if (state.dataLoopNodes->Node(InletNodeNum).MassFlowRate != ModelMassFlowRate) {
     988            6 :         DidAnythingChange = true;
     989              :     }
     990              : 
     991        14321 :     if (state.dataLoopNodes->Node(OutletNodeNum).MassFlowRate != ModelMassFlowRate) {
     992            6 :         DidAnythingChange = true;
     993              :     }
     994              : 
     995        14321 :     if (state.dataLoopNodes->Node(InletNodeNum).Temp != ModelInletTemp) {
     996         8646 :         DidAnythingChange = true;
     997              :     }
     998              : 
     999        14321 :     if (state.dataLoopNodes->Node(OutletNodeNum).Temp != ModelOutletTemp) {
    1000         4330 :         DidAnythingChange = true;
    1001              :     }
    1002              : 
    1003              :     // could also check heat rate against McDeltaT from node data
    1004              : 
    1005        14321 :     if ((state.dataLoopNodes->Node(InletNodeNum).MassFlowRate == 0.0) && (ModelCondenserHeatRate > 0.0)) {
    1006              : 
    1007              :         // TODO also send a request that condenser loop be made available, interlock message infrastructure??
    1008              : 
    1009            0 :         DidAnythingChange = true;
    1010              :     }
    1011              : 
    1012        14321 :     if (DidAnythingChange || FirstHVACIteration) {
    1013              :         // use current mass flow rate and inlet temp from Node and recalculate outlet temp
    1014        11487 :         if (state.dataLoopNodes->Node(InletNodeNum).MassFlowRate > DataBranchAirLoopPlant::MassFlowTolerance) {
    1015              :             // update node outlet conditions
    1016         8718 :             Cp = state.dataPlnt->PlantLoop(LoopNum).glycol->getSpecificHeat(state, ModelInletTemp, RoutineName);
    1017         8718 :             state.dataLoopNodes->Node(OutletNodeNum).Temp =
    1018         8718 :                 state.dataLoopNodes->Node(InletNodeNum).Temp + ModelCondenserHeatRate / (state.dataLoopNodes->Node(InletNodeNum).MassFlowRate * Cp);
    1019              :         }
    1020              :         // see 10133
    1021              :         // state.dataLoopNodes->Node(OutletNodeNum).MassFlowRate =
    1022              :         //    state.dataLoopNodes->Node(InletNodeNum)
    1023              :         //        .MassFlowRate; // if condenser is on inlet branch, the outlet node needs to be updated or can get splitter/mixer failures
    1024              : 
    1025              :         // set sim flag for this loop
    1026        11487 :         state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).SimLoopSideNeeded = true;
    1027              : 
    1028              :         // set sim flag on connected loops to true because this side changed
    1029        11487 :         if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).TotalConnected > 0) {
    1030        34043 :             for (int ConnectLoopNum = 1; ConnectLoopNum <= state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).TotalConnected; ++ConnectLoopNum) {
    1031              :                 // see 10133
    1032        22556 :                 if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).Connected(ConnectLoopNum).LoopDemandsOnRemote) {
    1033              :                     // full chiller model is not really run when called from the condenser side and the chiller evap loop puts demand on the condenser
    1034              :                     // loop, so this logic is flawed, remove if statement so evap side gets simulated again
    1035            0 :                     int OtherLoopNum = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).Connected(ConnectLoopNum).LoopNum;
    1036            0 :                     OtherLoopSide = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).Connected(ConnectLoopNum).LoopSideNum;
    1037            0 :                     state.dataPlnt->PlantLoop(OtherLoopNum).LoopSide(OtherLoopSide).SimLoopSideNeeded = true;
    1038              :                 }
    1039              :             }
    1040              :         }
    1041              : 
    1042        11487 :     } else { // nothing changed so turn off sim flag
    1043         2834 :         state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).SimLoopSideNeeded = false;
    1044              :     }
    1045        14321 : }
    1046              : 
    1047         5968 : void UpdateComponentHeatRecoverySide(EnergyPlusData &state,
    1048              :                                      int const LoopNum,                                   // component's loop index
    1049              :                                      const DataPlant::LoopSideLocation LoopSide,          // component's loop side number
    1050              :                                      [[maybe_unused]] DataPlant::PlantEquipmentType Type, // Component's type index
    1051              :                                      int const InletNodeNum,                              // Component's inlet node pointer
    1052              :                                      int const OutletNodeNum,                             // Component's outlet node pointer
    1053              :                                      Real64 const ModelRecoveryHeatRate,                  // model's heat rejection rate at recovery (W)
    1054              :                                      Real64 const ModelInletTemp,                         // model's inlet temperature (C)
    1055              :                                      Real64 const ModelOutletTemp,                        // model's outlet temperature (C)
    1056              :                                      Real64 const ModelMassFlowRate,                      // model's condenser water mass flow rate (kg/s)
    1057              :                                      bool const FirstHVACIteration)
    1058              : {
    1059              : 
    1060              :     // SUBROUTINE INFORMATION:
    1061              :     //       AUTHOR         Brent Griffith
    1062              :     //       DATE WRITTEN   Sept 2010
    1063              :     //       MODIFIED       na
    1064              :     //       RE-ENGINEERED  na
    1065              : 
    1066              :     // PURPOSE OF THIS SUBROUTINE:
    1067              :     // provides reusable update routine for heat recovery type
    1068              :     // connection to plant loops
    1069              : 
    1070              :     // METHODOLOGY EMPLOYED:
    1071              :     // check if anything changed or doesn't agree and set simulation flags.
    1072              :     // update outlet conditions if needed or possible
    1073              : 
    1074              :     // SUBROUTINE PARAMETER DEFINITIONS:
    1075              :     static constexpr std::string_view RoutineName("UpdateComponentHeatRecoverySide");
    1076              : 
    1077              :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    1078         5968 :     bool DidAnythingChange(false);             // set to true if conditions changed
    1079              :     DataPlant::LoopSideLocation OtherLoopSide; // local loop side pointer for remote connected loop
    1080              :     Real64 Cp;                                 // local fluid specific heat
    1081              : 
    1082              :     // check if any conditions have changed
    1083         5968 :     if (state.dataLoopNodes->Node(InletNodeNum).MassFlowRate != ModelMassFlowRate) {
    1084            6 :         DidAnythingChange = true;
    1085              :     }
    1086              : 
    1087         5968 :     if (state.dataLoopNodes->Node(OutletNodeNum).MassFlowRate != ModelMassFlowRate) {
    1088            6 :         DidAnythingChange = true;
    1089              :     }
    1090              : 
    1091         5968 :     if (state.dataLoopNodes->Node(InletNodeNum).Temp != ModelInletTemp) {
    1092         2972 :         DidAnythingChange = true;
    1093              :     }
    1094              : 
    1095         5968 :     if (state.dataLoopNodes->Node(OutletNodeNum).Temp != ModelOutletTemp) {
    1096         2238 :         DidAnythingChange = true;
    1097              :     }
    1098              : 
    1099              :     // could also check heat rate against McDeltaT from node data
    1100              : 
    1101         5968 :     if ((state.dataLoopNodes->Node(InletNodeNum).MassFlowRate == 0.0) && (ModelRecoveryHeatRate > 0.0)) {
    1102              :         // no flow but trying to move heat to this loop problem!
    1103              : 
    1104            6 :         DidAnythingChange = true;
    1105              :     }
    1106              : 
    1107         5968 :     if (DidAnythingChange || FirstHVACIteration) {
    1108              :         // use current mass flow rate and inlet temp from Node and recalculate outlet temp
    1109         2988 :         if (state.dataLoopNodes->Node(InletNodeNum).MassFlowRate > DataBranchAirLoopPlant::MassFlowTolerance) {
    1110              :             // update node outlet conditions
    1111         2976 :             Cp = state.dataPlnt->PlantLoop(LoopNum).glycol->getSpecificHeat(state, ModelInletTemp, RoutineName);
    1112         2976 :             state.dataLoopNodes->Node(OutletNodeNum).Temp =
    1113         2976 :                 state.dataLoopNodes->Node(InletNodeNum).Temp + ModelRecoveryHeatRate / (state.dataLoopNodes->Node(InletNodeNum).MassFlowRate * Cp);
    1114              :         }
    1115              :         // see 10133
    1116              :         // state.dataLoopNodes->Node(OutletNodeNum).MassFlowRate =
    1117              :         //    state.dataLoopNodes->Node(InletNodeNum).MassFlowRate; // if heat recovery bundle is on inlet branch, the mass flow needs to be
    1118              :         //// updated or get splitter/mixer failures
    1119              : 
    1120              :         // set sim flag for this loop
    1121         2988 :         state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).SimLoopSideNeeded = true;
    1122              : 
    1123              :         // set sim flag on connected loops to true because this side changed
    1124         2988 :         if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).TotalConnected > 0) {
    1125            0 :             for (int ConnectLoopNum = 1; ConnectLoopNum <= state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).TotalConnected; ++ConnectLoopNum) {
    1126              :                 // see 10133
    1127            0 :                 if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).Connected(ConnectLoopNum).LoopDemandsOnRemote) {
    1128              :                     // full chiller model is not really run when called from the heat recovery side and the chiller evap loop puts demand on the heat
    1129              :                     // recovery loop, so this logic is flawed, remove if statement so evap side gets simulated again
    1130            0 :                     int OtherLoopNum = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).Connected(ConnectLoopNum).LoopNum;
    1131            0 :                     OtherLoopSide = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).Connected(ConnectLoopNum).LoopSideNum;
    1132            0 :                     state.dataPlnt->PlantLoop(OtherLoopNum).LoopSide(OtherLoopSide).SimLoopSideNeeded = true;
    1133              :                 }
    1134              :             }
    1135              :         }
    1136              : 
    1137         2988 :     } else { // nothing changed so turn off sim flag
    1138         2980 :         state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).SimLoopSideNeeded = false;
    1139              :     }
    1140         5968 : }
    1141              : 
    1142        13877 : void UpdateAbsorberChillerComponentGeneratorSide(EnergyPlusData &state,
    1143              :                                                  int const LoopNum,                                                 // component's loop index
    1144              :                                                  const DataPlant::LoopSideLocation LoopSide,                        // component's loop side number
    1145              :                                                  [[maybe_unused]] DataPlant::PlantEquipmentType const Type,         // Component's type index
    1146              :                                                  int const InletNodeNum,                                            // Component's inlet node pointer
    1147              :                                                  [[maybe_unused]] int const OutletNodeNum,                          // Component's outlet node pointer
    1148              :                                                  [[maybe_unused]] DataLoopNode::NodeFluidType const HeatSourceType, // Type of fluid in Generator loop
    1149              :                                                  Real64 const ModelGeneratorHeatRate,                               // model's generator heat rate (W)
    1150              :                                                  Real64 const ModelMassFlowRate, // model's generator mass flow rate (kg/s)
    1151              :                                                  bool const FirstHVACIteration)
    1152              : {
    1153              : 
    1154              :     // SUBROUTINE INFORMATION:
    1155              :     //       AUTHOR         Brent Griffith
    1156              :     //       DATE WRITTEN   February 2010
    1157              :     //       MODIFIED       na
    1158              :     //       RE-ENGINEERED  na
    1159              : 
    1160              :     // PURPOSE OF THIS SUBROUTINE:
    1161              :     // provides reusable update routine for absoption chiller's generator
    1162              :     // connection to plant loops
    1163              : 
    1164              :     // METHODOLOGY EMPLOYED:
    1165              :     // check if anything changed or doesn't agree and set simulation flags.
    1166              :     // update outlet conditions if needed or possible
    1167              : 
    1168              :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    1169        13877 :     bool DidAnythingChange(false);             // set to true if conditions changed
    1170              :     DataPlant::LoopSideLocation OtherLoopSide; // local loop side pointer for remote connected loop
    1171              : 
    1172              :     // check if any conditions have changed
    1173        13877 :     if (state.dataLoopNodes->Node(InletNodeNum).MassFlowRate != ModelMassFlowRate) {
    1174         8420 :         DidAnythingChange = true;
    1175              :     }
    1176              : 
    1177        13877 :     if ((state.dataLoopNodes->Node(InletNodeNum).MassFlowRate == 0.0) && (ModelGeneratorHeatRate > 0.0)) {
    1178              : 
    1179              :         //  TODO also send a request that generator loop be made available, interlock message infrastructure??
    1180              : 
    1181            0 :         DidAnythingChange = true;
    1182              :     }
    1183              : 
    1184        13877 :     if (DidAnythingChange || FirstHVACIteration) {
    1185              : 
    1186              :         // set sim flag for this loop
    1187        11149 :         state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).SimLoopSideNeeded = true;
    1188              : 
    1189              :         // set sim flag on connected loops to true because this side changed
    1190        11149 :         if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).TotalConnected > 0) {
    1191        33447 :             for (int ConnectLoopNum = 1; ConnectLoopNum <= state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).TotalConnected; ++ConnectLoopNum) {
    1192        22298 :                 if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).Connected(ConnectLoopNum).LoopDemandsOnRemote) {
    1193        11149 :                     int OtherLoopNum = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).Connected(ConnectLoopNum).LoopNum;
    1194        11149 :                     OtherLoopSide = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).Connected(ConnectLoopNum).LoopSideNum;
    1195        11149 :                     state.dataPlnt->PlantLoop(OtherLoopNum).LoopSide(OtherLoopSide).SimLoopSideNeeded = true;
    1196              :                 }
    1197              :             }
    1198              :         }
    1199              : 
    1200        11149 :     } else { // nothing changed so turn off sim flag
    1201         2728 :         state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSide).SimLoopSideNeeded = false;
    1202              :     }
    1203        13877 : }
    1204              : 
    1205           46 : void InterConnectTwoPlantLoopSides(EnergyPlusData &state,
    1206              :                                    PlantLocation const &Loop1PlantLoc,
    1207              :                                    PlantLocation const &Loop2PlantLoc,
    1208              :                                    DataPlant::PlantEquipmentType ComponentType,
    1209              :                                    bool const Loop1DemandsOnLoop2)
    1210              : {
    1211              : 
    1212              :     // SUBROUTINE INFORMATION:
    1213              :     //       AUTHOR         B. Griffith
    1214              :     //       DATE WRITTEN   February 2010
    1215              :     //       MODIFIED       na
    1216              :     //       RE-ENGINEERED  na
    1217              : 
    1218              :     // PURPOSE OF THIS SUBROUTINE:
    1219              :     // Setup PlantLoop data structure pointers to direct interacting loops
    1220              : 
    1221              :     // Using/Aliasing
    1222              :     using DataPlant::ConnectedLoopData;
    1223              : 
    1224           46 :     if (Loop1PlantLoc.loopNum == 0 || Loop1PlantLoc.loopSideNum == DataPlant::LoopSideLocation::Invalid || Loop2PlantLoc.loopNum == 0 ||
    1225           46 :         Loop2PlantLoc.loopSideNum == DataPlant::LoopSideLocation::Invalid) {
    1226            0 :         return; // Associated ScanPlantLoopsForObject couldn't find the component in the the plant loop structure...
    1227              :     } // This is a Fatal error condition
    1228              : 
    1229           46 :     bool const Loop2DemandsOnLoop1(!Loop1DemandsOnLoop2);
    1230              : 
    1231              :     int TotalConnected;
    1232              : 
    1233           46 :     auto &loop_side_1 = state.dataPlnt->PlantLoop(Loop1PlantLoc.loopNum).LoopSide(Loop1PlantLoc.loopSideNum);
    1234           46 :     auto &connected_1 = loop_side_1.Connected;
    1235           46 :     if (allocated(connected_1)) {
    1236           10 :         TotalConnected = ++loop_side_1.TotalConnected;
    1237           10 :         connected_1.redimension(TotalConnected);
    1238              :     } else {
    1239           36 :         TotalConnected = loop_side_1.TotalConnected = 1;
    1240           36 :         connected_1.allocate(1);
    1241              :     }
    1242           46 :     connected_1(TotalConnected).LoopNum = Loop2PlantLoc.loopNum;
    1243           46 :     connected_1(TotalConnected).LoopSideNum = Loop2PlantLoc.loopSideNum;
    1244           46 :     connected_1(TotalConnected).ConnectorTypeOf_Num = static_cast<int>(ComponentType);
    1245           46 :     connected_1(TotalConnected).LoopDemandsOnRemote = Loop1DemandsOnLoop2;
    1246              : 
    1247           46 :     auto &loop_side_2 = state.dataPlnt->PlantLoop(Loop2PlantLoc.loopNum).LoopSide(Loop2PlantLoc.loopSideNum);
    1248           46 :     auto &connected_2 = loop_side_2.Connected;
    1249           46 :     if (allocated(connected_2)) {
    1250            8 :         TotalConnected = ++loop_side_2.TotalConnected;
    1251            8 :         connected_2.redimension(TotalConnected);
    1252              :     } else {
    1253           38 :         TotalConnected = loop_side_2.TotalConnected = 1;
    1254           38 :         connected_2.allocate(1);
    1255              :     }
    1256           46 :     connected_2(TotalConnected).LoopNum = Loop1PlantLoc.loopNum;
    1257           46 :     connected_2(TotalConnected).LoopSideNum = Loop1PlantLoc.loopSideNum;
    1258           46 :     connected_2(TotalConnected).ConnectorTypeOf_Num = static_cast<int>(ComponentType);
    1259           46 :     connected_2(TotalConnected).LoopDemandsOnRemote = Loop2DemandsOnLoop1;
    1260              : }
    1261              : 
    1262           13 : void ShiftPlantLoopSideCallingOrder(EnergyPlusData &state, int const OldIndex, int const NewIndex)
    1263              : {
    1264              : 
    1265              :     // SUBROUTINE INFORMATION:
    1266              :     //       AUTHOR         B. Griffith
    1267              :     //       DATE WRITTEN   <April 2011
    1268              :     //       MODIFIED       na
    1269              :     //       RE-ENGINEERED  na
    1270              : 
    1271              :     // PURPOSE OF THIS SUBROUTINE:
    1272              :     // re-arrange the calling order, move one loop side from an old index to a new one
    1273              : 
    1274              :     // Using/Aliasing
    1275              :     using namespace DataPlant;
    1276              : 
    1277              :     // Object Data
    1278           13 :     PlantCallingOrderInfoStruct RecordToMoveInPlantCallingOrderInfo;
    1279              : 
    1280           13 :     if (OldIndex == 0) {
    1281            0 :         ShowSevereError(state, "ShiftPlantLoopSideCallingOrder: developer error notice of invalid index, Old Index=0");
    1282              :     }
    1283           13 :     if (NewIndex == 0) {
    1284            0 :         ShowSevereError(state, "ShiftPlantLoopSideCallingOrder: developer error notice of invalid index, New Index=1");
    1285              :     }
    1286           13 :     if ((OldIndex == 0) || (NewIndex == 0)) {
    1287            0 :         return;
    1288              :     }
    1289              : 
    1290              :     // store copy of prior structure
    1291           13 :     Array1D<PlantCallingOrderInfoStruct> TempPlantCallingOrderInfo(state.dataPlnt->PlantCallingOrderInfo);
    1292              : 
    1293           13 :     RecordToMoveInPlantCallingOrderInfo = state.dataPlnt->PlantCallingOrderInfo(OldIndex);
    1294              : 
    1295           13 :     if (OldIndex == NewIndex) {
    1296              :         // do nothing, no shift needed.
    1297           11 :     } else if ((OldIndex == 1) && (NewIndex > OldIndex) && (NewIndex < state.dataPlnt->TotNumHalfLoops)) {
    1298              :         // example was:      1  2  3  4  5  6  7  8 (with OI = 1, NI = 5)
    1299              :         // example shifted:  2  3  4  5  1  6  7  8
    1300              : 
    1301            0 :         state.dataPlnt->PlantCallingOrderInfo({1, NewIndex - 1}) = TempPlantCallingOrderInfo({2, NewIndex});
    1302            0 :         state.dataPlnt->PlantCallingOrderInfo(NewIndex) = RecordToMoveInPlantCallingOrderInfo;
    1303            0 :         state.dataPlnt->PlantCallingOrderInfo({NewIndex + 1, state.dataPlnt->TotNumHalfLoops}) =
    1304            0 :             TempPlantCallingOrderInfo({NewIndex + 1, state.dataPlnt->TotNumHalfLoops});
    1305              : 
    1306           11 :     } else if ((OldIndex == 1) && (NewIndex > OldIndex) && (NewIndex == state.dataPlnt->TotNumHalfLoops)) {
    1307              :         // example was:      1  2  3  4  5  6  7  8 (with OI = 1, NI = 8)
    1308              :         // example shifted:  2  3  4  5  6  7  8  1
    1309              : 
    1310            0 :         state.dataPlnt->PlantCallingOrderInfo({1, NewIndex - 1}) = TempPlantCallingOrderInfo({2, NewIndex});
    1311            0 :         state.dataPlnt->PlantCallingOrderInfo(NewIndex) = RecordToMoveInPlantCallingOrderInfo;
    1312           11 :     } else if ((OldIndex > 1) && (NewIndex > OldIndex) && (NewIndex < state.dataPlnt->TotNumHalfLoops)) {
    1313              :         // example was:      1  2  3  4  5  6  7  8 (with OI = 3, NI = 6)
    1314              :         // example shifted:  1  2  4  5  6  3  7  8
    1315            0 :         state.dataPlnt->PlantCallingOrderInfo({1, OldIndex - 1}) = TempPlantCallingOrderInfo({1, OldIndex - 1});
    1316            0 :         state.dataPlnt->PlantCallingOrderInfo({OldIndex, NewIndex - 1}) = TempPlantCallingOrderInfo({OldIndex + 1, NewIndex});
    1317            0 :         state.dataPlnt->PlantCallingOrderInfo(NewIndex) = RecordToMoveInPlantCallingOrderInfo;
    1318            0 :         state.dataPlnt->PlantCallingOrderInfo({NewIndex + 1, state.dataPlnt->TotNumHalfLoops}) =
    1319            0 :             TempPlantCallingOrderInfo({NewIndex + 1, state.dataPlnt->TotNumHalfLoops});
    1320           11 :     } else if ((OldIndex > 1) && (NewIndex > OldIndex) && (NewIndex == state.dataPlnt->TotNumHalfLoops)) {
    1321              :         // example was:      1  2  3  4  5  6  7  8 (with OI = 3, NI = 8)
    1322              :         // example shifted:  1  2  4  5  6  7  8  3
    1323            0 :         state.dataPlnt->PlantCallingOrderInfo({1, OldIndex - 1}) = TempPlantCallingOrderInfo({1, OldIndex - 1});
    1324            0 :         state.dataPlnt->PlantCallingOrderInfo({OldIndex, NewIndex - 1}) = TempPlantCallingOrderInfo({OldIndex + 1, NewIndex});
    1325            0 :         state.dataPlnt->PlantCallingOrderInfo(NewIndex) = RecordToMoveInPlantCallingOrderInfo;
    1326           11 :     } else if ((OldIndex > 1) && (NewIndex < OldIndex) && (NewIndex == 1)) {
    1327              :         // example was:      1  2  3  4  5  6  7  8 (with OI = 3, NI = 1)
    1328              :         // example shifted:  3  1  2  4  5  6  7  8
    1329            2 :         state.dataPlnt->PlantCallingOrderInfo(NewIndex) = RecordToMoveInPlantCallingOrderInfo;
    1330            2 :         state.dataPlnt->PlantCallingOrderInfo({NewIndex + 1, OldIndex}) = TempPlantCallingOrderInfo({1, OldIndex - 1});
    1331            4 :         state.dataPlnt->PlantCallingOrderInfo({OldIndex + 1, state.dataPlnt->TotNumHalfLoops}) =
    1332            6 :             TempPlantCallingOrderInfo({OldIndex + 1, state.dataPlnt->TotNumHalfLoops});
    1333              : 
    1334            9 :     } else if ((OldIndex > 1) && (NewIndex < OldIndex) && (NewIndex > 1)) {
    1335              :         // example was:      1  2  3  4  5  6  7  8 (with OI = 3, NI = 2)
    1336              :         // example shifted:  1  3  2  4  5  6  7  8
    1337            9 :         state.dataPlnt->PlantCallingOrderInfo({1, NewIndex - 1}) = TempPlantCallingOrderInfo({1, NewIndex - 1});
    1338            9 :         state.dataPlnt->PlantCallingOrderInfo(NewIndex) = RecordToMoveInPlantCallingOrderInfo;
    1339            9 :         state.dataPlnt->PlantCallingOrderInfo({NewIndex + 1, OldIndex}) = TempPlantCallingOrderInfo({NewIndex, NewIndex + (OldIndex - NewIndex) - 1});
    1340           18 :         state.dataPlnt->PlantCallingOrderInfo({OldIndex + 1, state.dataPlnt->TotNumHalfLoops}) =
    1341           27 :             TempPlantCallingOrderInfo({OldIndex + 1, state.dataPlnt->TotNumHalfLoops});
    1342              : 
    1343              :     } else {
    1344            0 :         ShowSevereError(state,
    1345              :                         "ShiftPlantLoopSideCallingOrder: developer error notice, caught unexpected logical case in "
    1346              :                         "ShiftPlantLoopSideCallingOrder PlantUtilities");
    1347              :     }
    1348           13 : }
    1349              : 
    1350          508 : void RegisterPlantCompDesignFlow(EnergyPlusData &state,
    1351              :                                  int const ComponentInletNodeNum, // the component's water inlet node number
    1352              :                                  Real64 const DesPlantFlow        // the component's design fluid volume flow rate [m3/s]
    1353              : )
    1354              : {
    1355              : 
    1356              :     // SUBROUTINE INFORMATION:
    1357              :     //       AUTHOR         Fred Buhl(previously SaveCompDesWaterFlow in General.cc)
    1358              :     //       DATE WRITTEN   January 2004
    1359              :     //       MODIFIED
    1360              :     //       RE-ENGINEERED  B. Griffith April 2011, allow to enter repeatedly
    1361              : 
    1362              :     // PURPOSE OF THIS SUBROUTINE:
    1363              :     // Regester the design fluid flow rates of plant components for sizing purposes
    1364              :     // in an array that can be accessed by the plant manager routines
    1365              :     // allows sizing routines to iterate by safely processing repeated calls from the same component
    1366              : 
    1367              :     // METHODOLOGY EMPLOYED:
    1368              :     // Derived from SaveCompDesWaterFlow but changed to allow re entry with the same node just update
    1369              :     // the information at the same location in the structure
    1370              :     // The design flow rate is stored in a dynamic structure array along with the plant component's inlet node number
    1371              :     // (which is used by plant as a component identifier instead if name and type).
    1372              : 
    1373              :     // Using/Aliasing
    1374              :     using namespace DataSizing;
    1375              : 
    1376              :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    1377              :     int NumPlantComps;
    1378              :     int PlantCompNum; // component do loop index
    1379              :     bool Found;
    1380              :     int thisCallNodeIndex;
    1381              : 
    1382          508 :     NumPlantComps = state.dataSize->SaveNumPlantComps;
    1383              : 
    1384          508 :     if (NumPlantComps == 0) { // first time in, fill and return
    1385          151 :         NumPlantComps = 1;
    1386          151 :         state.dataSize->CompDesWaterFlow.allocate(NumPlantComps);
    1387              :         // save the new data
    1388          151 :         state.dataSize->CompDesWaterFlow(NumPlantComps).SupNode = ComponentInletNodeNum;
    1389          151 :         state.dataSize->CompDesWaterFlow(NumPlantComps).DesVolFlowRate = DesPlantFlow;
    1390          151 :         state.dataSize->SaveNumPlantComps = NumPlantComps;
    1391          151 :         return;
    1392              :     }
    1393              : 
    1394          357 :     Found = false;
    1395              :     // find node num index in structure if any
    1396         1308 :     for (PlantCompNum = 1; PlantCompNum <= NumPlantComps; ++PlantCompNum) {
    1397         1171 :         if (ComponentInletNodeNum == state.dataSize->CompDesWaterFlow(PlantCompNum).SupNode) {
    1398          220 :             Found = true;
    1399          220 :             thisCallNodeIndex = PlantCompNum;
    1400              :         }
    1401         1171 :         if (Found) {
    1402          220 :             break;
    1403              :         }
    1404              :     }
    1405              : 
    1406          357 :     if (!Found) {        // grow structure and add new node at the end
    1407          137 :         ++NumPlantComps; // increment the number of components that use water as a source of heat or coolth
    1408          137 :         state.dataSize->CompDesWaterFlow.emplace_back(ComponentInletNodeNum, DesPlantFlow); // Append the new element
    1409          137 :         state.dataSize->SaveNumPlantComps = NumPlantComps;
    1410              :     } else {
    1411          220 :         state.dataSize->CompDesWaterFlow(thisCallNodeIndex).SupNode = ComponentInletNodeNum;
    1412          220 :         state.dataSize->CompDesWaterFlow(thisCallNodeIndex).DesVolFlowRate = DesPlantFlow;
    1413              :     }
    1414              : }
    1415              : 
    1416      1116788 : void SafeCopyPlantNode(EnergyPlusData &state,
    1417              :                        int const InletNodeNum,
    1418              :                        int const OutletNodeNum,
    1419              :                        ObjexxFCL::Optional_int_const LoopNum,
    1420              :                        [[maybe_unused]] ObjexxFCL::Optional<Real64 const> OutletTemp // set on outlet node if present and water.
    1421              : )
    1422              : {
    1423              : 
    1424              :     // SUBROUTINE INFORMATION:
    1425              :     //       AUTHOR         B.  Griffith
    1426              :     //       DATE WRITTEN   February, 2010
    1427              :     //       MODIFIED       na
    1428              :     //       RE-ENGINEERED  na
    1429              : 
    1430              :     // PURPOSE OF THIS SUBROUTINE:
    1431              :     // Provide a safer alternative for Node(outlet) = Node(inlet)
    1432              :     // Intended just for plant
    1433              : 
    1434              :     // METHODOLOGY EMPLOYED:
    1435              :     // Copy over state variables but not setpoints
    1436              :     // derived from adiabatic Pipes
    1437              : 
    1438              :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    1439      1116788 :     state.dataLoopNodes->Node(OutletNodeNum).FluidType = state.dataLoopNodes->Node(InletNodeNum).FluidType;
    1440              : 
    1441      1116788 :     state.dataLoopNodes->Node(OutletNodeNum).Temp = state.dataLoopNodes->Node(InletNodeNum).Temp;
    1442      1116788 :     state.dataLoopNodes->Node(OutletNodeNum).MassFlowRate = state.dataLoopNodes->Node(InletNodeNum).MassFlowRate;
    1443      1116788 :     state.dataLoopNodes->Node(OutletNodeNum).Quality = state.dataLoopNodes->Node(InletNodeNum).Quality;
    1444      1116788 :     state.dataLoopNodes->Node(OutletNodeNum).Enthalpy =
    1445      1116788 :         state.dataLoopNodes->Node(InletNodeNum).Enthalpy; // should have routines that keep this current with temp?
    1446              : 
    1447      1116788 :     state.dataLoopNodes->Node(OutletNodeNum).TempMin = state.dataLoopNodes->Node(InletNodeNum).TempMin;
    1448      1116788 :     state.dataLoopNodes->Node(OutletNodeNum).TempMax = state.dataLoopNodes->Node(InletNodeNum).TempMax;
    1449      1116788 :     state.dataLoopNodes->Node(OutletNodeNum).MassFlowRateMinAvail =
    1450      1116788 :         max(state.dataLoopNodes->Node(InletNodeNum).MassFlowRateMin, state.dataLoopNodes->Node(InletNodeNum).MassFlowRateMinAvail);
    1451      1116788 :     state.dataLoopNodes->Node(OutletNodeNum).MassFlowRateMaxAvail =
    1452      1116788 :         min(state.dataLoopNodes->Node(InletNodeNum).MassFlowRateMax, state.dataLoopNodes->Node(InletNodeNum).MassFlowRateMaxAvail);
    1453              : 
    1454      1116788 :     state.dataLoopNodes->Node(OutletNodeNum).HumRat = state.dataLoopNodes->Node(InletNodeNum).HumRat; // air only?
    1455              : 
    1456              :     // Only pass pressure if we aren't doing a pressure simulation
    1457      1116788 :     if (present(LoopNum)) {
    1458       836189 :         switch (state.dataPlnt->PlantLoop(LoopNum).PressureSimType) {
    1459       836189 :         case DataPlant::PressSimType::NoPressure:
    1460       836189 :             state.dataLoopNodes->Node(OutletNodeNum).Press = state.dataLoopNodes->Node(InletNodeNum).Press;
    1461       836189 :         default:
    1462              :             // Don't do anything
    1463       836189 :             break;
    1464              :         }
    1465              :     }
    1466      1116788 : }
    1467              : 
    1468       662407 : Real64 BoundValueToNodeMinMaxAvail(EnergyPlusData &state, Real64 const ValueToBound, int const NodeNumToBoundWith)
    1469              : {
    1470              : 
    1471              :     // FUNCTION INFORMATION:
    1472              :     //       AUTHOR         Edwin Lee
    1473              :     //       DATE WRITTEN   September 2010
    1474              :     //       MODIFIED       na
    1475              :     //       RE-ENGINEERED  na
    1476              : 
    1477              :     // PURPOSE OF THIS FUNCTION:
    1478              :     // Provides a clean way to quickly bound a generic value to within any node's minavail and maxavail range
    1479              : 
    1480              :     // METHODOLOGY EMPLOYED:
    1481              :     // Bound up to min avail, down to max avail
    1482              : 
    1483              :     // Return value
    1484              :     Real64 BoundedValue;
    1485              : 
    1486       662407 :     BoundedValue = ValueToBound;
    1487       662407 :     BoundedValue = max(BoundedValue, state.dataLoopNodes->Node(NodeNumToBoundWith).MassFlowRateMinAvail);
    1488       662407 :     BoundedValue = min(BoundedValue, state.dataLoopNodes->Node(NodeNumToBoundWith).MassFlowRateMaxAvail);
    1489              : 
    1490       662407 :     return BoundedValue;
    1491              : }
    1492              : 
    1493        88208 : void TightenNodeMinMaxAvails(EnergyPlusData &state, int const NodeNum, Real64 const NewMinAvail, Real64 const NewMaxAvail)
    1494              : {
    1495              : 
    1496              :     // SUBROUTINE INFORMATION:
    1497              :     //       AUTHOR         Edwin Lee
    1498              :     //       DATE WRITTEN   January, 2011
    1499              :     //       MODIFIED       na
    1500              :     //       RE-ENGINEERED  na
    1501              : 
    1502              :     // PURPOSE OF THIS SUBROUTINE:
    1503              :     // Provides a means of tightening up min/max avail on a node if possible
    1504              : 
    1505              :     // METHODOLOGY EMPLOYED:
    1506              :     // Bring up node min avail to new min avail if it doesn't violate any other node conditions
    1507              :     // Pull down node max avail to new max avail if it doesn't violate any other node conditions
    1508              :     // Assumes that current min/max avails are already honoring hardware min/max values, so they aren't checked here
    1509              : 
    1510              :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    1511              :     Real64 OldMinAvail;
    1512              :     Real64 OldMaxAvail;
    1513              : 
    1514        88208 :     OldMinAvail = state.dataLoopNodes->Node(NodeNum).MassFlowRateMinAvail;
    1515        88208 :     OldMaxAvail = state.dataLoopNodes->Node(NodeNum).MassFlowRateMaxAvail;
    1516              : 
    1517              :     // If the new min avail is higher than previous, and it isn't higher than the max avail, update MIN AVAIL
    1518        88208 :     if ((NewMinAvail > OldMinAvail) && (NewMinAvail <= OldMaxAvail)) {
    1519            0 :         state.dataLoopNodes->Node(NodeNum).MassFlowRateMinAvail = NewMinAvail;
    1520              :     }
    1521              : 
    1522              :     // If the new max avail is lower than previous, and it isn't lower than the min avail, update MAX AVAIL
    1523        88208 :     if ((NewMaxAvail < OldMaxAvail) && (NewMaxAvail >= OldMinAvail)) {
    1524          222 :         state.dataLoopNodes->Node(NodeNum).MassFlowRateMaxAvail = NewMaxAvail;
    1525              :     }
    1526        88208 : }
    1527              : 
    1528        88208 : Real64 BoundValueToWithinTwoValues(Real64 const ValueToBound, Real64 const LowerBound, Real64 const UpperBound)
    1529              : {
    1530              : 
    1531              :     // FUNCTION INFORMATION:
    1532              :     //       AUTHOR         Edwin Lee
    1533              :     //       DATE WRITTEN   September 2010
    1534              :     //       MODIFIED       na
    1535              :     //       RE-ENGINEERED  na
    1536              : 
    1537              :     // PURPOSE OF THIS FUNCTION:
    1538              :     // Provides a clean way to quickly bound a generic value to within any two other values
    1539              : 
    1540              :     // METHODOLOGY EMPLOYED:
    1541              :     // Bound up to min and down to max
    1542              : 
    1543              :     // Return value
    1544              :     Real64 BoundedValue;
    1545              : 
    1546        88208 :     BoundedValue = ValueToBound;
    1547        88208 :     BoundedValue = max(BoundedValue, LowerBound);
    1548        88208 :     BoundedValue = min(BoundedValue, UpperBound);
    1549              : 
    1550        88208 :     return BoundedValue;
    1551              : }
    1552              : 
    1553            5 : bool IntegerIsWithinTwoValues(int const ValueToCheck, int const LowerBound, int const UpperBound)
    1554              : {
    1555              : 
    1556              :     // FUNCTION INFORMATION:
    1557              :     //       AUTHOR         Edwin Lee
    1558              :     //       DATE WRITTEN   September 2010
    1559              :     //       MODIFIED       na
    1560              :     //       RE-ENGINEERED  na
    1561              : 
    1562              :     // PURPOSE OF THIS FUNCTION:
    1563              :     // Provides a clean way to quickly check if an integer is within two values
    1564              : 
    1565              :     // METHODOLOGY EMPLOYED:
    1566              :     // TRUE if ValueToCheck = [LowerBound, UpperBound]
    1567              :     // in other words, it returns true if ValueToCheck=LowerBound, or if ValueToCheck=UpperBound
    1568              : 
    1569              :     // Return value
    1570            5 :     return (ValueToCheck >= LowerBound) && (ValueToCheck <= UpperBound);
    1571              : }
    1572              : 
    1573              : // In-Place Right Shift by 1 of Array Elements
    1574       343744 : void rshift1(Array1D<Real64> &a, Real64 const a_l)
    1575              : {
    1576       343744 :     assert(a.size_bounded());
    1577      1718720 :     for (int i = a.u(), e = a.l(); i > e; --i) {
    1578      1374976 :         a(i) = a(i - 1);
    1579              :     }
    1580       343744 :     a(a.l()) = a_l;
    1581       343744 : }
    1582              : 
    1583        24886 : void LogPlantConvergencePoints(EnergyPlusData &state, bool const FirstHVACIteration)
    1584              : {
    1585              : 
    1586              :     // SUBROUTINE INFORMATION:
    1587              :     //       AUTHOR         Edwin Lee
    1588              :     //       DATE WRITTEN   Summer 2011
    1589              :     //       MODIFIED       na
    1590              :     //       RE-ENGINEERED  na
    1591              : 
    1592              :     // PURPOSE OF THIS SUBROUTINE:
    1593              :     // This routine stores the history of the plant convergence to check for stuck (max iteration) conditions
    1594              : 
    1595              :     // METHODOLOGY EMPLOYED:
    1596              :     // Loop across all loops and loopsides
    1597              :     //   On first hvac, reset the history arrays to begin anew
    1598              :     //   Pick up the LoopSide inlet and outlet temp and flow rate
    1599              :     //   Store this in the history array of each node using EOSHIFT
    1600              : 
    1601        67854 :     for (int ThisLoopNum = 1; ThisLoopNum <= isize(state.dataPlnt->PlantLoop); ++ThisLoopNum) {
    1602        42968 :         auto &loop = state.dataPlnt->PlantLoop(ThisLoopNum);
    1603       128904 :         for (DataPlant::LoopSideLocation ThisLoopSide : DataPlant::LoopSideKeys) {
    1604        85936 :             auto &loop_side = loop.LoopSide(ThisLoopSide);
    1605              : 
    1606        85936 :             if (FirstHVACIteration) {
    1607        42948 :                 loop_side.InletNode.TemperatureHistory = 0.0;
    1608        42948 :                 loop_side.InletNode.MassFlowRateHistory = 0.0;
    1609        42948 :                 loop_side.OutletNode.TemperatureHistory = 0.0;
    1610        42948 :                 loop_side.OutletNode.MassFlowRateHistory = 0.0;
    1611              :             }
    1612              : 
    1613        85936 :             int InletNodeNum = loop_side.NodeNumIn;
    1614        85936 :             Real64 InletNodeTemp = state.dataLoopNodes->Node(InletNodeNum).Temp;
    1615        85936 :             Real64 InletNodeMdot = state.dataLoopNodes->Node(InletNodeNum).MassFlowRate;
    1616              : 
    1617        85936 :             int OutletNodeNum = loop_side.NodeNumOut;
    1618        85936 :             Real64 OutletNodeTemp = state.dataLoopNodes->Node(OutletNodeNum).Temp;
    1619        85936 :             Real64 OutletNodeMdot = state.dataLoopNodes->Node(OutletNodeNum).MassFlowRate;
    1620              : 
    1621        85936 :             rshift1(loop_side.InletNode.TemperatureHistory, InletNodeTemp);
    1622        85936 :             rshift1(loop_side.InletNode.MassFlowRateHistory, InletNodeMdot);
    1623        85936 :             rshift1(loop_side.OutletNode.TemperatureHistory, OutletNodeTemp);
    1624        85936 :             rshift1(loop_side.OutletNode.MassFlowRateHistory, OutletNodeMdot);
    1625              :         }
    1626              :     }
    1627        24886 : }
    1628              : 
    1629          695 : void ScanPlantLoopsForObject(EnergyPlusData &state,
    1630              :                              std::string_view CompName,
    1631              :                              DataPlant::PlantEquipmentType CompType,
    1632              :                              PlantLocation &plantLoc,
    1633              :                              bool &errFlag,
    1634              :                              ObjexxFCL::Optional<Real64 const> LowLimitTemp,
    1635              :                              ObjexxFCL::Optional<Real64 const> HighLimitTemp,
    1636              :                              ObjexxFCL::Optional_int CountMatchPlantLoops,
    1637              :                              ObjexxFCL::Optional_int_const InletNodeNumber,
    1638              :                              ObjexxFCL::Optional_int_const SingleLoopSearch,
    1639              :                              ObjexxFCL::Optional_bool_const suppressErrors)
    1640              : {
    1641              : 
    1642              :     // SUBROUTINE INFORMATION:
    1643              :     //       AUTHOR         Edwin Lee
    1644              :     //       DATE WRITTEN   November 2009
    1645              :     //       MODIFIED       B. Griffith, changes to help with single component one multiple plant loops
    1646              :     //       RE-ENGINEERED  na
    1647              :     // PURPOSE OF THIS SUBROUTINE:
    1648              :     // This subroutine scans the plant loop structure trying to find the component by type then name.
    1649              :     // If there are more than one match, it counts them up and returns count using an optional output arg
    1650              :     // If the option input declaring the component inlet's node name, then the matching is more specific.
    1651              :     // An optional input, lowlimittemp, can be passed in to be used in the PlantCondLoopOperation routines
    1652              :     //  when distributing loads to components
    1653              :     // METHODOLOGY EMPLOYED:
    1654              :     // Standard EnergyPlus methodology.
    1655              : 
    1656              :     // Using/Aliasing
    1657              :     using BranchInputManager::AuditBranches;
    1658              : 
    1659              :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    1660              :     int LoopCtr;
    1661              :     int BranchCtr;
    1662              :     int CompCtr;
    1663              :     bool FoundComponent;
    1664              :     int FoundCount;
    1665              :     bool FoundCompName;
    1666              :     int StartingLoopNum;
    1667              :     int EndingLoopNum;
    1668              : 
    1669          695 :     FoundCount = 0;
    1670              : 
    1671          695 :     FoundComponent = false;
    1672          695 :     FoundCompName = false;
    1673          695 :     StartingLoopNum = 1;
    1674          695 :     EndingLoopNum = state.dataPlnt->TotNumLoops;
    1675          695 :     if (present(SingleLoopSearch)) {
    1676           37 :         StartingLoopNum = SingleLoopSearch;
    1677           37 :         EndingLoopNum = SingleLoopSearch;
    1678              :     }
    1679              : 
    1680         1964 :     for (LoopCtr = StartingLoopNum; LoopCtr <= EndingLoopNum; ++LoopCtr) {
    1681         1269 :         auto &this_loop = state.dataPlnt->PlantLoop(LoopCtr);
    1682         3807 :         for (DataPlant::LoopSideLocation LoopSideCtr : DataPlant::LoopSideKeys) {
    1683         2538 :             auto &this_loop_side = this_loop.LoopSide(LoopSideCtr);
    1684        10279 :             for (BranchCtr = 1; BranchCtr <= this_loop_side.TotalBranches; ++BranchCtr) {
    1685         7741 :                 auto &this_branch = this_loop_side.Branch(BranchCtr);
    1686        15539 :                 for (CompCtr = 1; CompCtr <= this_branch.TotalComponents; ++CompCtr) {
    1687         7798 :                     auto &this_component = this_branch.Comp(CompCtr);
    1688         7798 :                     if (this_component.Type == CompType) {
    1689         2932 :                         if (Util::SameString(CompName, this_component.Name)) {
    1690          804 :                             FoundCompName = true;
    1691          804 :                             if (present(InletNodeNumber)) {
    1692          231 :                                 if (InletNodeNumber > 0) {
    1693              :                                     // check if inlet nodes agree
    1694          231 :                                     if (InletNodeNumber == this_component.NodeNumIn) {
    1695          117 :                                         FoundComponent = true;
    1696          117 :                                         ++FoundCount;
    1697          117 :                                         plantLoc.loopNum = LoopCtr;
    1698          117 :                                         plantLoc.loopSideNum = LoopSideCtr;
    1699          117 :                                         plantLoc.branchNum = BranchCtr;
    1700          117 :                                         plantLoc.compNum = CompCtr;
    1701              : 
    1702          117 :                                         plantLoc.loop = &this_loop;
    1703          117 :                                         plantLoc.side = &this_loop_side;
    1704          117 :                                         plantLoc.branch = &this_branch;
    1705          117 :                                         plantLoc.comp = &this_component;
    1706              :                                     }
    1707              :                                 }
    1708              :                             } else {
    1709          573 :                                 FoundComponent = true;
    1710          573 :                                 ++FoundCount;
    1711          573 :                                 plantLoc.loopNum = LoopCtr;
    1712          573 :                                 plantLoc.loopSideNum = LoopSideCtr;
    1713          573 :                                 plantLoc.branchNum = BranchCtr;
    1714          573 :                                 plantLoc.compNum = CompCtr;
    1715              : 
    1716          573 :                                 plantLoc.loop = &this_loop;
    1717          573 :                                 plantLoc.side = &this_loop_side;
    1718          573 :                                 plantLoc.branch = &this_branch;
    1719          573 :                                 plantLoc.comp = &this_component;
    1720              :                             }
    1721          804 :                             if (present(LowLimitTemp)) {
    1722           23 :                                 this_component.MinOutletTemp = LowLimitTemp;
    1723              :                             }
    1724          804 :                             if (present(HighLimitTemp)) {
    1725            3 :                                 this_component.MaxOutletTemp = HighLimitTemp;
    1726              :                             }
    1727              :                         }
    1728              :                     }
    1729              :                 }
    1730              :             }
    1731              :         }
    1732              :     }
    1733              : 
    1734          695 :     bool skipErrors = false;
    1735          695 :     if (present(suppressErrors)) {
    1736            0 :         skipErrors = suppressErrors;
    1737              :     }
    1738              : 
    1739          695 :     if (!FoundComponent && !skipErrors) {
    1740            5 :         if (CompType != DataPlant::PlantEquipmentType::Invalid && CompType != DataPlant::PlantEquipmentType::Num) {
    1741            5 :             if (!present(SingleLoopSearch)) {
    1742           10 :                 ShowSevereError(state,
    1743           10 :                                 format("Plant Component {} called \"{}\" was not found on any plant loops.",
    1744            5 :                                        DataPlant::PlantEquipTypeNames[static_cast<int>(CompType)],
    1745           10 :                                        std::string{CompName}));
    1746            5 :                 AuditBranches(state, true, DataPlant::PlantEquipTypeNames[static_cast<int>(CompType)], CompName);
    1747              :             } else {
    1748            0 :                 ShowSevereError(state,
    1749            0 :                                 format("Plant Component {} called \"{}\" was not found on plant loop=\"{}\".",
    1750            0 :                                        DataPlant::PlantEquipTypeNames[static_cast<int>(CompType)],
    1751            0 :                                        std::string{CompName},
    1752            0 :                                        state.dataPlnt->PlantLoop(SingleLoopSearch).Name));
    1753              :             }
    1754            5 :             if (present(InletNodeNumber)) {
    1755            4 :                 if (FoundCompName) {
    1756            4 :                     ShowContinueError(state, format("Looking for matching inlet Node=\"{}\".", state.dataLoopNodes->NodeID(InletNodeNumber)));
    1757              :                 }
    1758              :             }
    1759            5 :             if (present(SingleLoopSearch)) {
    1760            0 :                 ShowContinueError(state, format("Look at Operation Scheme=\"{}\".", state.dataPlnt->PlantLoop(SingleLoopSearch).OperationScheme));
    1761            0 :                 ShowContinueError(state, "Look at Branches and Components on the Loop.");
    1762            0 :                 ShowBranchesOnLoop(state, SingleLoopSearch);
    1763              :             }
    1764            5 :             errFlag = true;
    1765              :         } else {
    1766            0 :             ShowSevereError(state, format("ScanPlantLoopsForObject: Invalid CompType passed [{}], Name={}", CompType, CompName));
    1767            0 :             ShowContinueError(state, format("Valid CompTypes are in the range [0 - {}].", static_cast<int>(DataPlant::PlantEquipmentType::Num)));
    1768            0 :             ShowFatalError(state, "Previous error causes program termination");
    1769              :         }
    1770              :     }
    1771              : 
    1772          695 :     if (present(CountMatchPlantLoops)) {
    1773          317 :         CountMatchPlantLoops = FoundCount;
    1774              :     }
    1775          695 : }
    1776              : 
    1777          189 : void ScanPlantLoopsForNodeNum(EnergyPlusData &state,
    1778              :                               std::string_view const CallerName, // really used for error messages
    1779              :                               int const NodeNum,                 // index in Node structure of node to be scanned
    1780              :                               PlantLocation &plantLoc,           // return value for location
    1781              :                               ObjexxFCL::Optional_int CompNum)
    1782              : {
    1783              : 
    1784              :     // SUBROUTINE INFORMATION:
    1785              :     //       AUTHOR         B. Griffith
    1786              :     //       DATE WRITTEN   Feb. 2010
    1787              :     //       MODIFIED       na
    1788              :     //       RE-ENGINEERED  na
    1789              : 
    1790              :     // PURPOSE OF THIS SUBROUTINE:
    1791              :     // Get routine to return plant loop index and plant loop side
    1792              :     // based on node number.  for one time init routines only.
    1793              : 
    1794              :     // METHODOLOGY EMPLOYED:
    1795              :     // Loop thru plant data structure and find matching node.
    1796              : 
    1797              :     int LoopCtr;
    1798              :     int BranchCtr;
    1799              :     int CompCtr;
    1800              :     bool FoundNode;
    1801              :     int inFoundCount;
    1802              :     int outFoundCount;
    1803              : 
    1804          189 :     inFoundCount = 0;
    1805          189 :     outFoundCount = 0;
    1806          189 :     if (present(CompNum)) {
    1807            2 :         CompNum = 0;
    1808              :     }
    1809          189 :     FoundNode = false;
    1810              : 
    1811          526 :     for (LoopCtr = 1; LoopCtr <= state.dataPlnt->TotNumLoops; ++LoopCtr) {
    1812          337 :         auto &this_loop = state.dataPlnt->PlantLoop(LoopCtr);
    1813         1011 :         for (DataPlant::LoopSideLocation LoopSideCtr : DataPlant::LoopSideKeys) {
    1814          674 :             auto &this_loop_side = this_loop.LoopSide(LoopSideCtr);
    1815         2736 :             for (BranchCtr = 1; BranchCtr <= this_loop_side.TotalBranches; ++BranchCtr) {
    1816         2062 :                 auto &this_branch = this_loop_side.Branch(BranchCtr);
    1817         4153 :                 for (CompCtr = 1; CompCtr <= this_branch.TotalComponents; ++CompCtr) {
    1818         2091 :                     auto &this_comp = this_branch.Comp(CompCtr);
    1819         2091 :                     if (NodeNum == this_comp.NodeNumIn) {
    1820          190 :                         FoundNode = true;
    1821          190 :                         ++inFoundCount;
    1822          190 :                         plantLoc.loopNum = LoopCtr;
    1823          190 :                         plantLoc.loopSideNum = LoopSideCtr;
    1824          190 :                         plantLoc.branchNum = BranchCtr;
    1825              : 
    1826          190 :                         if (present(CompNum)) {
    1827            3 :                             CompNum = CompCtr; // What is this? This is an input
    1828              :                         }
    1829          190 :                         plantLoc.loop = &this_loop;
    1830          190 :                         plantLoc.side = &this_loop_side;
    1831          190 :                         plantLoc.branch = &this_branch;
    1832          190 :                         plantLoc.comp = &this_comp;
    1833              :                     }
    1834              : 
    1835         2091 :                     if (NodeNum == this_comp.NodeNumOut) {
    1836            5 :                         ++outFoundCount;
    1837            5 :                         plantLoc.loopNum = LoopCtr;
    1838            5 :                         plantLoc.loopSideNum = LoopSideCtr;
    1839            5 :                         plantLoc.branchNum = BranchCtr;
    1840              : 
    1841            5 :                         plantLoc.loop = &this_loop;
    1842            5 :                         plantLoc.side = &this_loop_side;
    1843            5 :                         plantLoc.branch = &this_branch;
    1844            5 :                         plantLoc.comp = &this_comp;
    1845              :                     }
    1846              :                 }
    1847              :             }
    1848              :         }
    1849              :     }
    1850              : 
    1851          189 :     if (!FoundNode) {
    1852            0 :         ShowSevereError(state, "ScanPlantLoopsForNodeNum: Plant Node was not found as inlet node (for component) on any plant loops");
    1853            0 :         ShowContinueError(state, format("Node Name=\"{}\"", state.dataLoopNodes->NodeID(NodeNum)));
    1854            0 :         if (!state.dataGlobal->DoingSizing) {
    1855            0 :             ShowContinueError(state, format("called by {}", CallerName));
    1856              :         } else {
    1857            0 :             ShowContinueError(state, format("during sizing: called by {}", CallerName));
    1858              :         }
    1859            0 :         if (outFoundCount > 0) {
    1860            0 :             ShowContinueError(state, format("Node was found as outlet node (for component) {} time(s).", outFoundCount));
    1861              :         }
    1862            0 :         ShowContinueError(state, "Possible error in Branch inputs.  For more information, look for other error messages related to this node name.");
    1863              :         // fatal?
    1864              :     }
    1865          189 : }
    1866              : 
    1867              : // Utility function, mostly for unit tests.
    1868          567 : void SetPlantLocationLinks(EnergyPlusData &state, PlantLocation &plantLoc)
    1869              : {
    1870          567 :     if (plantLoc.loopNum == 0) {
    1871            1 :         return;
    1872              :     }
    1873          566 :     plantLoc.loop = &state.dataPlnt->PlantLoop(plantLoc.loopNum);
    1874          566 :     if (plantLoc.loopSideNum == DataPlant::LoopSideLocation::Invalid) {
    1875           24 :         return;
    1876              :     }
    1877          542 :     plantLoc.side = &plantLoc.loop->LoopSide(plantLoc.loopSideNum);
    1878          542 :     if (plantLoc.branchNum == 0) {
    1879            1 :         return;
    1880              :     }
    1881          541 :     plantLoc.branch = &plantLoc.side->Branch(plantLoc.branchNum);
    1882          541 :     if (plantLoc.compNum == 0) {
    1883            0 :         return;
    1884              :     }
    1885          541 :     plantLoc.comp = &plantLoc.branch->Comp(plantLoc.compNum);
    1886              : }
    1887              : 
    1888         7875 : bool AnyPlantLoopSidesNeedSim(EnergyPlusData &state)
    1889              : {
    1890              : 
    1891              :     // FUNCTION INFORMATION:
    1892              :     //       AUTHOR         Edwin Lee
    1893              :     //       DATE WRITTEN   November 2009
    1894              :     //       MODIFIED       na
    1895              :     //       RE-ENGINEERED  na
    1896              :     // PURPOSE OF THIS FUNCTION:
    1897              :     // This subroutine scans the plant LoopSide simflags and returns if any of them are still true
    1898              : 
    1899              :     // Return value
    1900              :     bool AnyPlantLoopSidesNeedSim;
    1901              : 
    1902              :     // FUNCTION LOCAL VARIABLE DECLARATIONS:
    1903              :     int LoopCtr;
    1904              : 
    1905              :     // Assume that there aren't any
    1906         7875 :     AnyPlantLoopSidesNeedSim = false;
    1907              : 
    1908              :     // Then check if there are any
    1909         7878 :     for (LoopCtr = 1; LoopCtr <= state.dataPlnt->TotNumLoops; ++LoopCtr) {
    1910           10 :         for (DataPlant::LoopSideLocation LoopSideCtr : DataPlant::LoopSideKeys) {
    1911            7 :             if (state.dataPlnt->PlantLoop(LoopCtr).LoopSide(LoopSideCtr).SimLoopSideNeeded) {
    1912            1 :                 AnyPlantLoopSidesNeedSim = true;
    1913            1 :                 return AnyPlantLoopSidesNeedSim;
    1914              :             }
    1915              :         }
    1916              :     }
    1917              : 
    1918         7874 :     return AnyPlantLoopSidesNeedSim;
    1919              : }
    1920              : 
    1921       505709 : void SetAllPlantSimFlagsToValue(EnergyPlusData &state, bool const Value)
    1922              : {
    1923              : 
    1924              :     // SUBROUTINE INFORMATION:
    1925              :     //       AUTHOR         Edwin Lee
    1926              :     //       DATE WRITTEN   November 2009
    1927              :     //       MODIFIED       na
    1928              :     //       RE-ENGINEERED  B. Griffith Feb 2009
    1929              :     // PURPOSE OF THIS SUBROUTINE:
    1930              :     // Quickly sets all sim flags of a certain type (loop type/side) to a value
    1931              : 
    1932              :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    1933              :     int LoopCtr;
    1934              : 
    1935              :     // Loop over all loops
    1936       576573 :     for (LoopCtr = 1; LoopCtr <= state.dataPlnt->TotNumLoops; ++LoopCtr) {
    1937        70864 :         auto &this_loop = state.dataPlnt->PlantLoop(LoopCtr);
    1938        70864 :         this_loop.LoopSide(DataPlant::LoopSideLocation::Demand).SimLoopSideNeeded = Value;
    1939        70864 :         this_loop.LoopSide(DataPlant::LoopSideLocation::Supply).SimLoopSideNeeded = Value;
    1940              :     }
    1941       505709 : }
    1942              : 
    1943            0 : void ShowBranchesOnLoop(EnergyPlusData &state, int const LoopNum) // Loop number of loop
    1944              : {
    1945              : 
    1946              :     // SUBROUTINE INFORMATION:
    1947              :     //       AUTHOR         Linda Lawrie
    1948              :     //       DATE WRITTEN   November 2011
    1949              :     //       MODIFIED       na
    1950              :     //       RE-ENGINEERED  na
    1951              : 
    1952              :     // PURPOSE OF THIS SUBROUTINE:
    1953              :     // This routine will display (with continue error messages) the branch/component
    1954              :     // structure of the given loop.
    1955              : 
    1956              :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    1957              :     int BrN; // Branch counter
    1958              :     int CpN; // Component (on branch) counter
    1959              : 
    1960            0 :     for (DataPlant::LoopSideLocation LSN : DataPlant::LoopSideKeys) {
    1961            0 :         ShowContinueError(state, format("{} Branches:", DataPlant::DemandSupplyNames[static_cast<int>(LSN)]));
    1962            0 :         for (BrN = 1; BrN <= state.dataPlnt->PlantLoop(LoopNum).LoopSide(LSN).TotalBranches; ++BrN) {
    1963            0 :             ShowContinueError(state, format("  {}", state.dataPlnt->PlantLoop(LoopNum).LoopSide(LSN).Branch(BrN).Name));
    1964            0 :             ShowContinueError(state, "    Components on Branch:");
    1965            0 :             for (CpN = 1; CpN <= state.dataPlnt->PlantLoop(LoopNum).LoopSide(LSN).Branch(BrN).TotalComponents; ++CpN) {
    1966            0 :                 ShowContinueError(state,
    1967            0 :                                   format("      {}:{}",
    1968            0 :                                          state.dataPlnt->PlantLoop(LoopNum).LoopSide(LSN).Branch(BrN).Comp(CpN).TypeOf,
    1969            0 :                                          state.dataPlnt->PlantLoop(LoopNum).LoopSide(LSN).Branch(BrN).Comp(CpN).Name));
    1970              :             }
    1971              :         }
    1972              :     }
    1973            0 : }
    1974              : 
    1975          175 : int MyPlantSizingIndex(EnergyPlusData &state,
    1976              :                        std::string_view const CompType,       // component description
    1977              :                        std::string_view const CompName,       // user name of component
    1978              :                        int const NodeNumIn,                   // component water inlet node
    1979              :                        [[maybe_unused]] int const NodeNumOut, // component water outlet node
    1980              :                        bool &ErrorsFound,                     // set to true if there's an error, unchanged otherwise
    1981              :                        bool const PrintErrorFlag              // used for WSHP's where condenser loop may not be on a plant loop
    1982              : )
    1983              : {
    1984              : 
    1985              :     // FUNCTION INFORMATION:
    1986              :     //       AUTHOR         Fred Buhl
    1987              :     //       DATE WRITTEN   July 2008
    1988              :     //       MODIFIED       na
    1989              :     //       RE-ENGINEERED  na
    1990              : 
    1991              :     // PURPOSE OF THIS FUNCTION:
    1992              :     // Identify the correct Plant Sizing object for demand-side components such as heating and
    1993              :     // cooling coils.
    1994              : 
    1995              :     // METHODOLOGY EMPLOYED:
    1996              :     // This function searches all plant loops for a component whose input and
    1997              :     // output nodes match the desired input & output nodes. This plant loop index is then used
    1998              :     // to search the Plant Sizing array for the matching Plant Sizing object.
    1999              : 
    2000              :     // Using/Aliasing
    2001              :     using DataSizing::PlantSizingData;
    2002              : 
    2003              :     // Return value
    2004              :     int MyPltSizNum; // returned plant sizing index
    2005              : 
    2006          175 :     int MyPltLoopNum{};
    2007          175 :     PlantLocation DummyPlantLoc{};
    2008              : 
    2009          175 :     MyPltSizNum = 0;
    2010              : 
    2011          175 :     ScanPlantLoopsForNodeNum(state, "MyPlantSizingIndex", NodeNumIn, DummyPlantLoc);
    2012              : 
    2013          175 :     if (DummyPlantLoc.loopNum > 0) {
    2014          175 :         MyPltLoopNum = DummyPlantLoc.loopNum;
    2015              :     } else {
    2016            0 :         MyPltLoopNum = 0;
    2017              :     }
    2018              : 
    2019          175 :     if (MyPltLoopNum > 0) {
    2020          175 :         if (state.dataSize->NumPltSizInput > 0) {
    2021              :             MyPltSizNum =
    2022          173 :                 Util::FindItemInList(state.dataPlnt->PlantLoop(MyPltLoopNum).Name, state.dataSize->PlantSizData, &PlantSizingData::PlantLoopName);
    2023              :         }
    2024          175 :         if (MyPltSizNum == 0) {
    2025            4 :             if (PrintErrorFlag) {
    2026            4 :                 ShowSevereError(
    2027            4 :                     state, format("MyPlantSizingIndex: Could not find {} in Sizing:Plant objects.", state.dataPlnt->PlantLoop(MyPltLoopNum).Name));
    2028            2 :                 ShowContinueError(state, format("...reference Component Type=\"{}\", Name=\"{}\".", CompType, CompName));
    2029              :             }
    2030            4 :             ErrorsFound = true;
    2031              :         }
    2032              :     } else {
    2033            0 :         if (PrintErrorFlag) {
    2034            0 :             ShowWarningError(state, format("MyPlantSizingIndex: Could not find {} with name {} on any plant loop", CompType, CompName));
    2035              :         }
    2036            0 :         ErrorsFound = true;
    2037              :     }
    2038              : 
    2039          175 :     return MyPltSizNum;
    2040              : }
    2041              : 
    2042            5 : bool verifyTwoNodeNumsOnSamePlantLoop(EnergyPlusData &state, int const nodeIndexA, int const nodeIndexB)
    2043              : {
    2044              :     // this function simply searches across plant loops looking for node numbers
    2045              :     // it returns true if the two nodes are found to be on the same loop
    2046              :     // it returns false otherwise
    2047              :     // because this is a nested loop, there's no reason it should be called except in one-time fashion
    2048            5 :     int matchedIndexA = 0;
    2049            5 :     int matchedIndexB = 0;
    2050           12 :     for (int loopNum = 1; loopNum <= state.dataPlnt->TotNumLoops; loopNum++) {
    2051           21 :         for (auto const &loopSide : state.dataPlnt->PlantLoop(loopNum).LoopSide) {
    2052           28 :             for (auto const &branch : loopSide.Branch) {
    2053           28 :                 for (auto const &comp : branch.Comp) {
    2054           14 :                     if (comp.NodeNumIn == nodeIndexA || comp.NodeNumOut == nodeIndexA) {
    2055            5 :                         matchedIndexA = loopNum;
    2056              :                     }
    2057           14 :                     if (comp.NodeNumIn == nodeIndexB || comp.NodeNumOut == nodeIndexB) {
    2058            5 :                         matchedIndexB = loopNum;
    2059              :                     }
    2060              :                 }
    2061              :             }
    2062              :         }
    2063              :     }
    2064            5 :     return (matchedIndexA == matchedIndexB) && (matchedIndexA != 0); // only return true if both are equal and non-zero
    2065              : }
    2066              : 
    2067              : Real64
    2068            8 : MinFlowIfBranchHasVSPump(EnergyPlusData &state, PlantLocation const &plantLoc, bool &foundBranchPump, bool &foundLoopPump, bool const setFlowStatus)
    2069              : {
    2070            8 :     Real64 branchPumpMinFlowLimit = 0.0;
    2071              : 
    2072            8 :     int NumCompsOnThisBranch = plantLoc.branch->TotalComponents;
    2073           16 :     for (int CompCounter = 1; CompCounter <= NumCompsOnThisBranch; ++CompCounter) {
    2074            8 :         auto &component = plantLoc.branch->Comp(CompCounter);
    2075            8 :         if (component.Type == DataPlant::PlantEquipmentType::PumpVariableSpeed ||
    2076            8 :             component.Type == DataPlant::PlantEquipmentType::PumpBankVariableSpeed) {
    2077            0 :             foundBranchPump = true;
    2078            0 :             if (component.CompNum > 0) {
    2079            0 :                 branchPumpMinFlowLimit = state.dataPumps->PumpEquip(component.CompNum).MassFlowRateMin;
    2080              :             }
    2081            0 :             break;
    2082              :         }
    2083              :     }
    2084              : 
    2085            8 :     if (!foundBranchPump) {
    2086              :         // second, if no branch pump, search for variable speed pump on inlet branch of supply side of this loop
    2087            8 :         if (plantLoc.loop->LoopSide(DataPlant::LoopSideLocation::Supply).TotalBranches > 1) {
    2088            0 :             int NumCompsOnInletBranch = plantLoc.loop->LoopSide(DataPlant::LoopSideLocation::Supply).Branch(1).TotalComponents;
    2089            0 :             for (int CompCounter = 1; CompCounter <= NumCompsOnInletBranch; ++CompCounter) {
    2090            0 :                 auto &component = plantLoc.loop->LoopSide(DataPlant::LoopSideLocation::Supply).Branch(1).Comp(CompCounter);
    2091            0 :                 if (component.Type == DataPlant::PlantEquipmentType::PumpVariableSpeed ||
    2092            0 :                     component.Type == DataPlant::PlantEquipmentType::PumpBankVariableSpeed) {
    2093            0 :                     foundLoopPump = true;
    2094            0 :                     if (component.CompNum > 0) {
    2095            0 :                         branchPumpMinFlowLimit = state.dataPumps->PumpEquip(component.CompNum).MassFlowRateMin;
    2096              :                     }
    2097            0 :                     break;
    2098              :                 }
    2099              :             }
    2100              :         }
    2101              :     }
    2102              : 
    2103            8 :     if (setFlowStatus) {
    2104            0 :         if (branchPumpMinFlowLimit > 0.0 && foundBranchPump) {
    2105            0 :             plantLoc.comp->FlowPriority = DataPlant::LoopFlowStatus::NeedyIfLoopOn;
    2106              :         } else {
    2107            0 :             plantLoc.comp->FlowPriority = DataPlant::LoopFlowStatus::TakesWhatGets;
    2108              :         }
    2109              :     }
    2110              : 
    2111            8 :     return branchPumpMinFlowLimit;
    2112              : }
    2113              : 
    2114              : } // namespace EnergyPlus::PlantUtilities
        

Generated by: LCOV version 2.0-1