LCOV - code coverage report
Current view: top level - EnergyPlus - HVACInterfaceManager.cc (source / functions) Hit Total Coverage
Test: lcov.output.filtered Lines: 519 563 92.2 %
Date: 2023-01-17 19:17:23 Functions: 9 9 100.0 %

          Line data    Source code
       1             : // EnergyPlus, Copyright (c) 1996-2023, The Board of Trustees of the University of Illinois,
       2             : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
       3             : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
       4             : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
       5             : // contributors. All rights reserved.
       6             : //
       7             : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
       8             : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
       9             : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
      10             : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
      11             : // derivative works, and perform publicly and display publicly, and to permit others to do so.
      12             : //
      13             : // Redistribution and use in source and binary forms, with or without modification, are permitted
      14             : // provided that the following conditions are met:
      15             : //
      16             : // (1) Redistributions of source code must retain the above copyright notice, this list of
      17             : //     conditions and the following disclaimer.
      18             : //
      19             : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
      20             : //     conditions and the following disclaimer in the documentation and/or other materials
      21             : //     provided with the distribution.
      22             : //
      23             : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
      24             : //     the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
      25             : //     used to endorse or promote products derived from this software without specific prior
      26             : //     written permission.
      27             : //
      28             : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
      29             : //     without changes from the version obtained under this License, or (ii) Licensee makes a
      30             : //     reference solely to the software portion of its product, Licensee must refer to the
      31             : //     software as "EnergyPlus version X" software, where "X" is the version number Licensee
      32             : //     obtained under this License and may not use a different name for the software. Except as
      33             : //     specifically required in this Section (4), Licensee shall not use in a company name, a
      34             : //     product name, in advertising, publicity, or other promotional activities any name, trade
      35             : //     name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
      36             : //     similar designation, without the U.S. Department of Energy's prior written consent.
      37             : //
      38             : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
      39             : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
      40             : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
      41             : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
      42             : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
      43             : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
      44             : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
      45             : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
      46             : // POSSIBILITY OF SUCH DAMAGE.
      47             : 
      48             : // C++ Headers
      49             : #include <cmath>
      50             : 
      51             : // ObjexxFCL Headers
      52             : #include <ObjexxFCL/Fmath.hh>
      53             : 
      54             : // EnergyPlus Headers
      55             : #include <EnergyPlus/Data/EnergyPlusData.hh>
      56             : #include <EnergyPlus/DataAirLoop.hh>
      57             : #include <EnergyPlus/DataBranchAirLoopPlant.hh>
      58             : #include <EnergyPlus/DataContaminantBalance.hh>
      59             : #include <EnergyPlus/DataConvergParams.hh>
      60             : #include <EnergyPlus/DataHVACGlobals.hh>
      61             : #include <EnergyPlus/FluidProperties.hh>
      62             : #include <EnergyPlus/HVACInterfaceManager.hh>
      63             : #include <EnergyPlus/OutputProcessor.hh>
      64             : #include <EnergyPlus/Plant/DataPlant.hh>
      65             : #include <EnergyPlus/PlantUtilities.hh>
      66             : #include <EnergyPlus/UtilityRoutines.hh>
      67             : 
      68             : namespace EnergyPlus::HVACInterfaceManager {
      69             : 
      70             : // MODULE INFORMATION:
      71             : //       AUTHOR         Rick Strand
      72             : //       DATE WRITTEN   October 1998
      73             : //       MODIFIED       na
      74             : //       RE-ENGINEERED  na
      75             : 
      76             : // PURPOSE OF THIS MODULE:
      77             : // This module contains one or more routines for checking the convergence
      78             : // of the various HVAC loops and passing information across interface
      79             : // boundaries.
      80             : 
      81             : // METHODOLOGY EMPLOYED:
      82             : // The upper level HVAC managers call the routine(s) contained in this
      83             : // module as a last step.  The node information is passed across the
      84             : // interface boundary and the logical flag is set if the nodes across
      85             : // from each other are not within tolerance.
      86             : 
      87    23146589 : void UpdateHVACInterface(EnergyPlusData &state,
      88             :                          int const AirLoopNum, // airloop number for which air loop this is
      89             :                          DataConvergParams::CalledFrom const CalledFrom,
      90             :                          int const OutletNode,    // Node number for the outlet of the side of the loop just simulated
      91             :                          int const InletNode,     // Node number for the inlet of the side that needs the outlet node data
      92             :                          bool &OutOfToleranceFlag // True when the other side of the loop need to be (re)simulated
      93             : )
      94             : {
      95             : 
      96             :     // SUBROUTINE INFORMATION:
      97             :     //       AUTHOR         Rick Strand
      98             :     //       DATE WRITTEN   October 1998
      99             : 
     100             :     // PURPOSE OF THIS SUBROUTINE:
     101             :     // This subroutine manages any generic HVAC loop interface.
     102             : 
     103             :     // METHODOLOGY EMPLOYED:
     104             :     // This is a simple "forward" interface where all of the properties
     105             :     // from the outlet of one side of the loop get transferred directly
     106             :     // to the inlet node of the corresponding other side of the loop.
     107             : 
     108    23146589 :     auto &TmpRealARR = state.dataHVACInterfaceMgr->TmpRealARR;
     109    23146589 :     auto &airLoopConv = state.dataConvergeParams->AirLoopConvergence(AirLoopNum);
     110    23146589 :     auto &thisInletNode = state.dataLoopNodes->Node(InletNode);
     111             : 
     112    23146589 :     if ((CalledFrom == DataConvergParams::CalledFrom::AirSystemDemandSide) && (OutletNode == 0)) {
     113             :         // Air loop has no return path - only check mass flow and then set return inlet node mass flow to sum of demand side inlet nodes
     114             : 
     115       15046 :         airLoopConv.HVACMassFlowNotConverged[0] = false;
     116       15046 :         airLoopConv.HVACHumRatNotConverged[0] = false;
     117       15046 :         airLoopConv.HVACTempNotConverged[0] = false;
     118       15046 :         airLoopConv.HVACEnergyNotConverged[0] = false;
     119             : 
     120       15046 :         Real64 totDemandSideMassFlow = 0.0;
     121       15046 :         Real64 totDemandSideMinAvail = 0.0;
     122       15046 :         Real64 totDemandSideMaxAvail = 0.0;
     123       30092 :         for (int demIn = 1; demIn <= state.dataAirLoop->AirToZoneNodeInfo(AirLoopNum).NumSupplyNodes; ++demIn) {
     124       15046 :             int demInNode = state.dataAirLoop->AirToZoneNodeInfo(AirLoopNum).ZoneEquipSupplyNodeNum(demIn);
     125       15046 :             totDemandSideMassFlow += state.dataLoopNodes->Node(demInNode).MassFlowRate;
     126       15046 :             totDemandSideMinAvail += state.dataLoopNodes->Node(demInNode).MassFlowRateMinAvail;
     127       15046 :             totDemandSideMaxAvail += state.dataLoopNodes->Node(demInNode).MassFlowRateMaxAvail;
     128             :         }
     129       15046 :         TmpRealARR = airLoopConv.HVACFlowDemandToSupplyTolValue;
     130       15046 :         airLoopConv.HVACFlowDemandToSupplyTolValue[0] = std::abs(totDemandSideMassFlow - thisInletNode.MassFlowRate);
     131      150460 :         for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
     132      135414 :             airLoopConv.HVACFlowDemandToSupplyTolValue[logIndex] = TmpRealARR[logIndex - 1];
     133             :         }
     134       15046 :         if (airLoopConv.HVACFlowDemandToSupplyTolValue[0] > DataConvergParams::HVACFlowRateToler) {
     135        5240 :             airLoopConv.HVACMassFlowNotConverged[0] = true;
     136        5240 :             OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
     137             :         }
     138             : 
     139       15046 :         thisInletNode.MassFlowRate = totDemandSideMassFlow;
     140       15046 :         thisInletNode.MassFlowRateMinAvail = totDemandSideMinAvail;
     141       15046 :         thisInletNode.MassFlowRateMaxAvail = totDemandSideMaxAvail;
     142       15046 :         return;
     143             :     }
     144             : 
     145             :     // Calculate the approximate energy difference across interface for comparison
     146             :     Real64 DeltaEnergy =
     147    46263086 :         DataConvergParams::HVACCpApprox * ((state.dataLoopNodes->Node(OutletNode).MassFlowRate * state.dataLoopNodes->Node(OutletNode).Temp) -
     148    46263086 :                                            (thisInletNode.MassFlowRate * thisInletNode.Temp));
     149             : 
     150    23131543 :     if ((CalledFrom == DataConvergParams::CalledFrom::AirSystemDemandSide) && (OutletNode > 0)) {
     151             : 
     152    10975788 :         airLoopConv.HVACMassFlowNotConverged[0] = false;
     153    10975788 :         airLoopConv.HVACHumRatNotConverged[0] = false;
     154    10975788 :         airLoopConv.HVACTempNotConverged[0] = false;
     155    10975788 :         airLoopConv.HVACEnergyNotConverged[0] = false;
     156             : 
     157    10975788 :         TmpRealARR = airLoopConv.HVACFlowDemandToSupplyTolValue;
     158    10975788 :         airLoopConv.HVACFlowDemandToSupplyTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).MassFlowRate - thisInletNode.MassFlowRate);
     159   109757880 :         for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
     160    98782092 :             airLoopConv.HVACFlowDemandToSupplyTolValue[logIndex] = TmpRealARR[logIndex - 1];
     161             :         }
     162    10975788 :         if (airLoopConv.HVACFlowDemandToSupplyTolValue[0] > DataConvergParams::HVACFlowRateToler) {
     163     2901659 :             airLoopConv.HVACMassFlowNotConverged[0] = true;
     164     2901659 :             OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
     165             :         }
     166             : 
     167    10975788 :         TmpRealARR = airLoopConv.HVACHumDemandToSupplyTolValue;
     168    10975788 :         airLoopConv.HVACHumDemandToSupplyTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).HumRat - thisInletNode.HumRat);
     169   109757880 :         for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
     170    98782092 :             airLoopConv.HVACHumDemandToSupplyTolValue[logIndex] = TmpRealARR[logIndex - 1];
     171             :         }
     172    10975788 :         if (airLoopConv.HVACHumDemandToSupplyTolValue[0] > DataConvergParams::HVACHumRatToler) {
     173      416757 :             airLoopConv.HVACHumRatNotConverged[0] = true;
     174      416757 :             OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
     175             :         }
     176             : 
     177    10975788 :         TmpRealARR = airLoopConv.HVACTempDemandToSupplyTolValue;
     178    10975788 :         airLoopConv.HVACTempDemandToSupplyTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).Temp - thisInletNode.Temp);
     179   109757880 :         for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
     180    98782092 :             airLoopConv.HVACTempDemandToSupplyTolValue[logIndex] = TmpRealARR[logIndex - 1];
     181             :         }
     182    10975788 :         if (airLoopConv.HVACTempDemandToSupplyTolValue[0] > DataConvergParams::HVACTemperatureToler) {
     183     2439037 :             airLoopConv.HVACTempNotConverged[0] = true;
     184     2439037 :             OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
     185             :         }
     186             : 
     187    10975788 :         TmpRealARR = airLoopConv.HVACEnergyDemandToSupplyTolValue;
     188    10975788 :         airLoopConv.HVACEnergyDemandToSupplyTolValue[0] = std::abs(DeltaEnergy);
     189   109757880 :         for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
     190    98782092 :             airLoopConv.HVACEnergyDemandToSupplyTolValue[logIndex] = TmpRealARR[logIndex - 1];
     191             :         }
     192    10975788 :         if (std::abs(DeltaEnergy) > DataConvergParams::HVACEnergyToler) {
     193     4038732 :             airLoopConv.HVACEnergyNotConverged[0] = true;
     194     4038732 :             OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
     195             :         }
     196             : 
     197    10975788 :         TmpRealARR = airLoopConv.HVACEnthalpyDemandToSupplyTolValue;
     198    10975788 :         airLoopConv.HVACEnthalpyDemandToSupplyTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).Enthalpy - thisInletNode.Enthalpy);
     199   109757880 :         for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
     200    98782092 :             airLoopConv.HVACEnthalpyDemandToSupplyTolValue[logIndex] = TmpRealARR[logIndex - 1];
     201             :         }
     202    10975788 :         if (airLoopConv.HVACEnthalpyDemandToSupplyTolValue[0] > DataConvergParams::HVACEnthalpyToler) {
     203     1110714 :             OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
     204             :         }
     205             : 
     206    10975788 :         TmpRealARR = airLoopConv.HVACPressureDemandToSupplyTolValue;
     207    10975788 :         airLoopConv.HVACPressureDemandToSupplyTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).Press - thisInletNode.Press);
     208   109757880 :         for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
     209    98782092 :             airLoopConv.HVACPressureDemandToSupplyTolValue[logIndex] = TmpRealARR[logIndex - 1];
     210             :         }
     211    10975788 :         if (airLoopConv.HVACPressureDemandToSupplyTolValue[0] > DataConvergParams::HVACPressToler) {
     212       11798 :             OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
     213    10975788 :         }
     214             : 
     215    12155755 :     } else if (CalledFrom == DataConvergParams::CalledFrom::AirSystemSupplySideDeck1) {
     216             : 
     217    12100880 :         airLoopConv.HVACMassFlowNotConverged[1] = false;
     218    12100880 :         airLoopConv.HVACHumRatNotConverged[1] = false;
     219    12100880 :         airLoopConv.HVACTempNotConverged[1] = false;
     220    12100880 :         airLoopConv.HVACEnergyNotConverged[1] = false;
     221             : 
     222    12100880 :         TmpRealARR = airLoopConv.HVACFlowSupplyDeck1ToDemandTolValue;
     223    12100880 :         airLoopConv.HVACFlowSupplyDeck1ToDemandTolValue[0] =
     224    12100880 :             std::abs(state.dataLoopNodes->Node(OutletNode).MassFlowRate - thisInletNode.MassFlowRate);
     225   121008800 :         for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
     226   108907920 :             airLoopConv.HVACFlowSupplyDeck1ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
     227             :         }
     228    12100880 :         if (airLoopConv.HVACFlowSupplyDeck1ToDemandTolValue[0] > DataConvergParams::HVACFlowRateToler) {
     229     2952850 :             airLoopConv.HVACMassFlowNotConverged[1] = true;
     230     2952850 :             OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
     231             :         }
     232             : 
     233    12100880 :         TmpRealARR = airLoopConv.HVACHumSupplyDeck1ToDemandTolValue;
     234    12100880 :         airLoopConv.HVACHumSupplyDeck1ToDemandTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).HumRat - thisInletNode.HumRat);
     235   121008800 :         for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
     236   108907920 :             airLoopConv.HVACHumSupplyDeck1ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
     237             :         }
     238    12100880 :         if (airLoopConv.HVACHumSupplyDeck1ToDemandTolValue[0] > DataConvergParams::HVACHumRatToler) {
     239     1686468 :             airLoopConv.HVACHumRatNotConverged[1] = true;
     240     1686468 :             OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
     241             :         }
     242             : 
     243    12100880 :         TmpRealARR = airLoopConv.HVACTempSupplyDeck1ToDemandTolValue;
     244    12100880 :         airLoopConv.HVACTempSupplyDeck1ToDemandTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).Temp - thisInletNode.Temp);
     245   121008800 :         for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
     246   108907920 :             airLoopConv.HVACTempSupplyDeck1ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
     247             :         }
     248    12100880 :         if (airLoopConv.HVACTempSupplyDeck1ToDemandTolValue[0] > DataConvergParams::HVACTemperatureToler) {
     249     5952485 :             airLoopConv.HVACTempNotConverged[1] = true;
     250     5952485 :             OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
     251             :         }
     252             : 
     253    12100880 :         TmpRealARR = airLoopConv.HVACEnergySupplyDeck1ToDemandTolValue;
     254    12100880 :         airLoopConv.HVACEnergySupplyDeck1ToDemandTolValue[0] = DeltaEnergy;
     255   121008800 :         for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
     256   108907920 :             airLoopConv.HVACEnergySupplyDeck1ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
     257             :         }
     258    12100880 :         if (std::abs(DeltaEnergy) > DataConvergParams::HVACEnergyToler) {
     259     6329279 :             airLoopConv.HVACEnergyNotConverged[1] = true;
     260     6329279 :             OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
     261             :         }
     262             : 
     263    12100880 :         TmpRealARR = airLoopConv.HVACEnthalpySupplyDeck1ToDemandTolValue;
     264    12100880 :         airLoopConv.HVACEnthalpySupplyDeck1ToDemandTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).Enthalpy - thisInletNode.Enthalpy);
     265   121008800 :         for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
     266   108907920 :             airLoopConv.HVACEnthalpySupplyDeck1ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
     267             :         }
     268    12100880 :         if (airLoopConv.HVACEnthalpySupplyDeck1ToDemandTolValue[0] > DataConvergParams::HVACEnthalpyToler) {
     269     4193012 :             OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
     270             :         }
     271             : 
     272    12100880 :         TmpRealARR = airLoopConv.HVACPressureSupplyDeck1ToDemandTolValue;
     273    12100880 :         airLoopConv.HVACPressureSupplyDeck1ToDemandTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).Press - thisInletNode.Press);
     274   121008800 :         for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
     275   108907920 :             airLoopConv.HVACPressureSupplyDeck1ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
     276             :         }
     277    12100880 :         if (airLoopConv.HVACPressureSupplyDeck1ToDemandTolValue[0] > DataConvergParams::HVACPressToler) {
     278      114359 :             OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
     279             :         }
     280             : 
     281       54875 :     } else if (CalledFrom == DataConvergParams::CalledFrom::AirSystemSupplySideDeck2) {
     282             : 
     283       54875 :         airLoopConv.HVACMassFlowNotConverged[2] = false;
     284       54875 :         airLoopConv.HVACHumRatNotConverged[2] = false;
     285       54875 :         airLoopConv.HVACTempNotConverged[2] = false;
     286       54875 :         airLoopConv.HVACEnergyNotConverged[2] = false;
     287             : 
     288       54875 :         TmpRealARR = airLoopConv.HVACFlowSupplyDeck2ToDemandTolValue;
     289       54875 :         airLoopConv.HVACFlowSupplyDeck2ToDemandTolValue[0] =
     290       54875 :             std::abs(state.dataLoopNodes->Node(OutletNode).MassFlowRate - thisInletNode.MassFlowRate);
     291      548750 :         for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
     292      493875 :             airLoopConv.HVACFlowSupplyDeck2ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
     293             :         }
     294       54875 :         if (airLoopConv.HVACFlowSupplyDeck2ToDemandTolValue[0] > DataConvergParams::HVACFlowRateToler) {
     295       18396 :             airLoopConv.HVACMassFlowNotConverged[2] = true;
     296       18396 :             OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
     297             :         }
     298             : 
     299       54875 :         TmpRealARR = airLoopConv.HVACHumSupplyDeck2ToDemandTolValue;
     300       54875 :         airLoopConv.HVACHumSupplyDeck2ToDemandTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).HumRat - thisInletNode.HumRat);
     301      548750 :         for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
     302      493875 :             airLoopConv.HVACHumSupplyDeck2ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
     303             :         }
     304       54875 :         if (airLoopConv.HVACHumSupplyDeck2ToDemandTolValue[0] > DataConvergParams::HVACHumRatToler) {
     305        6704 :             airLoopConv.HVACHumRatNotConverged[2] = true;
     306        6704 :             OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
     307             :         }
     308             : 
     309       54875 :         TmpRealARR = airLoopConv.HVACTempSupplyDeck2ToDemandTolValue;
     310       54875 :         airLoopConv.HVACTempSupplyDeck2ToDemandTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).Temp - thisInletNode.Temp);
     311      548750 :         for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
     312      493875 :             airLoopConv.HVACTempSupplyDeck2ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
     313             :         }
     314       54875 :         if (airLoopConv.HVACTempSupplyDeck2ToDemandTolValue[0] > DataConvergParams::HVACTemperatureToler) {
     315       11409 :             airLoopConv.HVACTempNotConverged[2] = true;
     316       11409 :             OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
     317             :         }
     318             : 
     319       54875 :         TmpRealARR = airLoopConv.HVACEnergySupplyDeck2ToDemandTolValue;
     320       54875 :         airLoopConv.HVACEnergySupplyDeck2ToDemandTolValue[0] = DeltaEnergy;
     321      548750 :         for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
     322      493875 :             airLoopConv.HVACEnergySupplyDeck2ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
     323             :         }
     324       54875 :         if (std::abs(DeltaEnergy) > DataConvergParams::HVACEnergyToler) {
     325       20349 :             airLoopConv.HVACEnergyNotConverged[2] = true;
     326       20349 :             OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
     327             :         }
     328             : 
     329       54875 :         TmpRealARR = airLoopConv.HVACEnthalpySupplyDeck2ToDemandTolValue;
     330       54875 :         airLoopConv.HVACEnthalpySupplyDeck2ToDemandTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).Enthalpy - thisInletNode.Enthalpy);
     331      548750 :         for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
     332      493875 :             airLoopConv.HVACEnthalpySupplyDeck2ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
     333             :         }
     334       54875 :         if (airLoopConv.HVACEnthalpySupplyDeck2ToDemandTolValue[0] > DataConvergParams::HVACEnthalpyToler) {
     335       12311 :             OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
     336             :         }
     337             : 
     338       54875 :         TmpRealARR = airLoopConv.HVACPressueSupplyDeck2ToDemandTolValue;
     339       54875 :         airLoopConv.HVACPressueSupplyDeck2ToDemandTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).Press - thisInletNode.Press);
     340      548750 :         for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
     341      493875 :             airLoopConv.HVACPressueSupplyDeck2ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
     342             :         }
     343       54875 :         if (airLoopConv.HVACPressueSupplyDeck2ToDemandTolValue[0] > DataConvergParams::HVACPressToler) {
     344         108 :             OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
     345             :         }
     346             :     }
     347             : 
     348             :     // Always update the new inlet conditions
     349    23131543 :     thisInletNode.Temp = state.dataLoopNodes->Node(OutletNode).Temp;
     350    23131543 :     thisInletNode.MassFlowRate = state.dataLoopNodes->Node(OutletNode).MassFlowRate;
     351    23131543 :     thisInletNode.MassFlowRateMinAvail = state.dataLoopNodes->Node(OutletNode).MassFlowRateMinAvail;
     352    23131543 :     thisInletNode.MassFlowRateMaxAvail = state.dataLoopNodes->Node(OutletNode).MassFlowRateMaxAvail;
     353    23131543 :     thisInletNode.Quality = state.dataLoopNodes->Node(OutletNode).Quality;
     354    23131543 :     thisInletNode.Press = state.dataLoopNodes->Node(OutletNode).Press;
     355    23131543 :     thisInletNode.Enthalpy = state.dataLoopNodes->Node(OutletNode).Enthalpy;
     356    23131543 :     thisInletNode.HumRat = state.dataLoopNodes->Node(OutletNode).HumRat;
     357             : 
     358    23131543 :     if (state.dataContaminantBalance->Contaminant.CO2Simulation) {
     359       71296 :         thisInletNode.CO2 = state.dataLoopNodes->Node(OutletNode).CO2;
     360             :     }
     361             : 
     362    23131543 :     if (state.dataContaminantBalance->Contaminant.GenericContamSimulation) {
     363       23301 :         thisInletNode.GenContam = state.dataLoopNodes->Node(OutletNode).GenContam;
     364             :     }
     365             : }
     366             : 
     367    34416411 : void UpdatePlantLoopInterface(EnergyPlusData &state,
     368             :                               PlantLocation const &plantLoc,    // The 'outlet node' Location
     369             :                               int const ThisLoopSideOutletNode, // Node number for the inlet of the side that needs the outlet node data
     370             :                               int const OtherLoopSideInletNode, // Node number for the outlet of the side of the loop just simulated
     371             :                               bool &OutOfToleranceFlag,         // True when the other side of the loop need to be (re)simulated
     372             :                               DataPlant::CommonPipeType const CommonPipeType)
     373             : {
     374             : 
     375             :     // SUBROUTINE INFORMATION:
     376             :     //       AUTHOR         Rick Strand
     377             :     //       DATE WRITTEN   October 1998
     378             :     //       MODIFIED       na
     379             :     //       RE-ENGINEERED  Brent Griffith, Sept. 2010
     380             :     //       RE-ENGINEERED  Dan Fisher,     Sept. 2010
     381             : 
     382             :     // PURPOSE OF THIS SUBROUTINE:
     383             :     // This subroutine manages any generic HVAC loop interface.
     384             : 
     385             :     // METHODOLOGY EMPLOYED:
     386             :     // This is a simple "forward" interface where all of the properties
     387             :     // from the outlet of one side of the loop get transfered
     388             :     // to the inlet node of the corresponding other side of the loop.
     389             :     // Temperatures are 'lagged' by loop capacitance (i.e. a 'tank')
     390             :     // between the outlet and inlet nodes.
     391             :     // the update from the demand side to the supply side always triggers
     392             :     // resimulation of the supply side if any state variable (or energy) is
     393             :     // out of tolerance.  Remsimulation of the demand side is only triggered if
     394             :     // flow or energy are out of tolerance.  This in effect checks flow and
     395             :     // ~.25C temperature difference.
     396             : 
     397             :     // SUBROUTINE PARAMETER DEFINITIONS:
     398             :     static constexpr std::string_view RoutineName("UpdatePlantLoopInterface");
     399             : 
     400    34416411 :     int LoopNum = plantLoc.loopNum;
     401    34416411 :     DataPlant::LoopSideLocation ThisLoopSideNum = plantLoc.loopSideNum;
     402    34416411 :     auto &convergence(state.dataConvergeParams->PlantConvergence(LoopNum));
     403             : 
     404             :     // reset out of tolerance flags
     405    34416411 :     convergence.PlantMassFlowNotConverged = false;
     406    34416411 :     convergence.PlantTempNotConverged = false;
     407             : 
     408             :     // set the LoopSide inlet node
     409    34416411 :     int ThisLoopSideInletNode = state.dataPlnt->PlantLoop(LoopNum).LoopSide(ThisLoopSideNum).NodeNumIn;
     410             : 
     411             :     // save the inlet node temp for DeltaEnergy check
     412    34416411 :     Real64 OldOtherLoopSideInletMdot = state.dataLoopNodes->Node(OtherLoopSideInletNode).MassFlowRate;
     413    34416411 :     Real64 OldTankOutletTemp = state.dataLoopNodes->Node(OtherLoopSideInletNode).Temp;
     414             : 
     415             :     // calculate the specific heat
     416    68832822 :     Real64 Cp = FluidProperties::GetSpecificHeatGlycol(
     417   103249233 :         state, state.dataPlnt->PlantLoop(LoopNum).FluidName, OldTankOutletTemp, state.dataPlnt->PlantLoop(LoopNum).FluidIndex, RoutineName);
     418             : 
     419             :     // update the enthalpy
     420    34416411 :     state.dataLoopNodes->Node(OtherLoopSideInletNode).Enthalpy = Cp * state.dataLoopNodes->Node(OtherLoopSideInletNode).Temp;
     421             : 
     422             :     // update the temperatures and flow rates
     423    34416411 :     auto &flow_demand_to_supply_tol(convergence.PlantFlowDemandToSupplyTolValue);
     424    34416411 :     auto &flow_supply_to_demand_tol(convergence.PlantFlowSupplyToDemandTolValue);
     425             :     Real64 MixedOutletTemp;
     426             :     Real64 TankOutletTemp;
     427    34416411 :     if (CommonPipeType == DataPlant::CommonPipeType::Single || CommonPipeType == DataPlant::CommonPipeType::TwoWay) {
     428             :         // update the temperature
     429      520030 :         UpdateCommonPipe(state, plantLoc, CommonPipeType, MixedOutletTemp);
     430      520030 :         state.dataLoopNodes->Node(OtherLoopSideInletNode).Temp = MixedOutletTemp;
     431      520030 :         TankOutletTemp = MixedOutletTemp;
     432      520030 :         if (ThisLoopSideNum == DataPlant::LoopSideLocation::Demand) {
     433      260015 :             rshift1(flow_demand_to_supply_tol);
     434      260015 :             flow_demand_to_supply_tol[0] = std::abs(OldOtherLoopSideInletMdot - state.dataLoopNodes->Node(OtherLoopSideInletNode).MassFlowRate);
     435      260015 :             if (flow_demand_to_supply_tol[0] > DataConvergParams::PlantFlowRateToler) {
     436           0 :                 convergence.PlantMassFlowNotConverged = true;
     437             :             }
     438             :         } else {
     439      260015 :             rshift1(flow_supply_to_demand_tol);
     440      260015 :             flow_supply_to_demand_tol[0] = std::abs(OldOtherLoopSideInletMdot - state.dataLoopNodes->Node(OtherLoopSideInletNode).MassFlowRate);
     441      260015 :             if (flow_supply_to_demand_tol[0] > DataConvergParams::PlantFlowRateToler) {
     442           0 :                 convergence.PlantMassFlowNotConverged = true;
     443             :             }
     444             :         }
     445             :         // Set the flow rate.  Continuity requires that the flow rates at the half loop inlet and outlet match
     446      520030 :         state.dataLoopNodes->Node(ThisLoopSideInletNode).MassFlowRate = state.dataLoopNodes->Node(ThisLoopSideOutletNode).MassFlowRate;
     447             :         // Update this LoopSide inlet node Min/MaxAvail to this LoopSide outlet node Min/MaxAvail
     448      520030 :         state.dataLoopNodes->Node(ThisLoopSideInletNode).MassFlowRateMinAvail =
     449      520030 :             state.dataLoopNodes->Node(ThisLoopSideOutletNode).MassFlowRateMinAvail;
     450      520030 :         state.dataLoopNodes->Node(ThisLoopSideInletNode).MassFlowRateMaxAvail =
     451      520030 :             state.dataLoopNodes->Node(ThisLoopSideOutletNode).MassFlowRateMaxAvail;
     452             : 
     453             :     } else { // no common pipe
     454    33896381 :         UpdateHalfLoopInletTemp(state, LoopNum, ThisLoopSideNum, TankOutletTemp);
     455             :         // update the temperature
     456    33896381 :         state.dataLoopNodes->Node(OtherLoopSideInletNode).Temp = TankOutletTemp;
     457             :         // Set the flow tolerance array
     458    33896381 :         if (ThisLoopSideNum == DataPlant::LoopSideLocation::Demand) {
     459    16951963 :             rshift1(flow_demand_to_supply_tol);
     460    33903926 :             flow_demand_to_supply_tol[0] = std::abs(state.dataLoopNodes->Node(ThisLoopSideOutletNode).MassFlowRate -
     461    16951963 :                                                     state.dataLoopNodes->Node(OtherLoopSideInletNode).MassFlowRate);
     462    16951963 :             if (flow_demand_to_supply_tol[0] > DataConvergParams::PlantFlowRateToler) {
     463     1627941 :                 convergence.PlantMassFlowNotConverged = true;
     464             :             }
     465             :         } else {
     466    16944418 :             rshift1(flow_supply_to_demand_tol);
     467    33888836 :             flow_supply_to_demand_tol[0] = std::abs(state.dataLoopNodes->Node(ThisLoopSideOutletNode).MassFlowRate -
     468    16944418 :                                                     state.dataLoopNodes->Node(OtherLoopSideInletNode).MassFlowRate);
     469    16944418 :             if (flow_supply_to_demand_tol[0] > DataConvergParams::PlantFlowRateToler) {
     470      538212 :                 convergence.PlantMassFlowNotConverged = true;
     471             :             }
     472             :         }
     473             :         //    PlantFlowTolValue(PlantQuePtr)  = ABS(Node(ThisLoopSideOutletNode)%MassFlowRate-Node(OtherLoopSideInletNode)%MassFlowRate)
     474             :         // Set the flow rate
     475    33896381 :         state.dataLoopNodes->Node(OtherLoopSideInletNode).MassFlowRate = state.dataLoopNodes->Node(ThisLoopSideOutletNode).MassFlowRate;
     476             :         // update the MIN/MAX available flow rates
     477    33896381 :         state.dataLoopNodes->Node(OtherLoopSideInletNode).MassFlowRateMinAvail =
     478    33896381 :             state.dataLoopNodes->Node(ThisLoopSideOutletNode).MassFlowRateMinAvail;
     479    33896381 :         state.dataLoopNodes->Node(OtherLoopSideInletNode).MassFlowRateMaxAvail =
     480    33896381 :             state.dataLoopNodes->Node(ThisLoopSideOutletNode).MassFlowRateMaxAvail;
     481             :         // update Quality.  Note: This update assumes that STEAM cannot be used with common pipes.
     482    33896381 :         state.dataLoopNodes->Node(OtherLoopSideInletNode).Quality = state.dataLoopNodes->Node(ThisLoopSideOutletNode).Quality;
     483             :         // pressure update  Note: This update assumes that PRESSURE SIMULATION cannot be used with common pipes.
     484    33896381 :         if (state.dataPlnt->PlantLoop(LoopNum).HasPressureComponents) {
     485             :             // Don't update pressure, let the pressure simulation handle pressures
     486             :         } else {
     487             :             // Do update pressure!
     488    33875321 :             state.dataLoopNodes->Node(OtherLoopSideInletNode).Press = state.dataLoopNodes->Node(ThisLoopSideOutletNode).Press;
     489             :         }
     490             :     }
     491             : 
     492             :     // temperature
     493    34416411 :     if (ThisLoopSideNum == DataPlant::LoopSideLocation::Demand) {
     494    17211978 :         auto &temp_demand_to_supply_tol(convergence.PlantTempDemandToSupplyTolValue);
     495    17211978 :         rshift1(temp_demand_to_supply_tol);
     496    17211978 :         temp_demand_to_supply_tol[0] = std::abs(OldTankOutletTemp - state.dataLoopNodes->Node(OtherLoopSideInletNode).Temp);
     497    17211978 :         if (temp_demand_to_supply_tol[0] > DataConvergParams::PlantTemperatureToler) {
     498     4178236 :             convergence.PlantTempNotConverged = true;
     499             :         }
     500             :     } else {
     501    17204433 :         auto &temp_supply_to_demand_tol(convergence.PlantTempSupplyToDemandTolValue);
     502    17204433 :         rshift1(temp_supply_to_demand_tol);
     503    17204433 :         temp_supply_to_demand_tol[0] = std::abs(OldTankOutletTemp - state.dataLoopNodes->Node(OtherLoopSideInletNode).Temp);
     504    17204433 :         if (temp_supply_to_demand_tol[0] > DataConvergParams::PlantTemperatureToler) {
     505     1889298 :             convergence.PlantTempNotConverged = true;
     506             :         }
     507             :     }
     508             : 
     509             :     // Set out of tolerance flags
     510    34416411 :     if (ThisLoopSideNum == DataPlant::LoopSideLocation::Demand) {
     511    17211978 :         if (convergence.PlantMassFlowNotConverged || convergence.PlantTempNotConverged) {
     512     4503344 :             OutOfToleranceFlag = true;
     513             :         }
     514             :     } else {
     515    17204433 :         if (convergence.PlantMassFlowNotConverged) {
     516      538212 :             OutOfToleranceFlag = true;
     517             :         }
     518             :     }
     519    34416411 : }
     520             : 
     521    33896381 : void UpdateHalfLoopInletTemp(EnergyPlusData &state, int const LoopNum, const DataPlant::LoopSideLocation TankInletLoopSide, Real64 &TankOutletTemp)
     522             : {
     523             : 
     524             :     // SUBROUTINE INFORMATION:
     525             :     //       AUTHOR         Rick Strand
     526             :     //       DATE WRITTEN   September 2001
     527             :     //       MODIFIED       Simon Rees, July 2007
     528             :     //                      Brent Griffith, Feb. 2010, add LoopNum arg
     529             :     //       RE-ENGINEERED  Brent Griffith, Sept 2010, generalize for both loop sides
     530             :     //                                           add pump heat from other loop
     531             :     //                      B.Griffith and L.Gu, Oct 2011, solve via analytical soln, use average over timestep
     532             : 
     533             :     // PURPOSE OF THIS SUBROUTINE:
     534             :     // This subroutine calculates the new loop side inlet temperature
     535             :     // based on the previous temperature of the mixed tank, mass flow rate and the new
     536             :     // outlet temperature on the supply side.  The temperature does not
     537             :     // pass directly across because the loop has some capacitance. It is
     538             :     // called separately but used for both supply-to-demand, and demand-to-supply
     539             : 
     540             :     // METHODOLOGY EMPLOYED:
     541             :     // This uses a analytical solution for changes in the
     542             :     // fluid loop temperature.  The user defines some volume of fluid
     543             :     // for the loop which gets converted to a fixed amount of mass.
     544             :     // The loop side inlet node is modeled as the outlet of a fully mixed
     545             :     // tank. Note that this routine is called repeatedly to re calculate
     546             :     // loop capacitance based on current plant conditions
     547             : 
     548    33896381 :     auto &SysTimeElapsed = state.dataHVACGlobal->SysTimeElapsed;
     549    33896381 :     auto &TimeStepSys = state.dataHVACGlobal->TimeStepSys;
     550             : 
     551             :     // SUBROUTINE PARAMETER DEFINITIONS:
     552    33896381 :     Real64 constexpr FracTotLoopMass(0.5); // Fraction of total loop mass assigned to the half loop
     553             :     static constexpr std::string_view RoutineName("UpdateHalfLoopInletTemp");
     554             : 
     555             :     // find tank inlet and outlet nodes
     556    33896381 :     DataPlant::LoopSideLocation TankOutletLoopSide = DataPlant::LoopSideOther[static_cast<int>(TankInletLoopSide)];
     557    33896381 :     int TankInletNode = state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankInletLoopSide).NodeNumOut;
     558    33896381 :     Real64 TankInletTemp = state.dataLoopNodes->Node(TankInletNode).Temp;
     559             : 
     560             :     // This needs to be based on time to deal with system downstepping and repeated timesteps
     561    33896381 :     Real64 TimeElapsed = (state.dataGlobal->HourOfDay - 1) + state.dataGlobal->TimeStep * state.dataGlobal->TimeStepZone + SysTimeElapsed;
     562    33896381 :     if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).TimeElapsed != TimeElapsed) {
     563     6562670 :         state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).LastTempInterfaceTankOutlet =
     564     6562670 :             state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).TempInterfaceTankOutlet;
     565     6562670 :         state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).TimeElapsed = TimeElapsed;
     566             :     }
     567             : 
     568    33896381 :     Real64 LastTankOutletTemp = state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).LastTempInterfaceTankOutlet;
     569             : 
     570             :     // calculate the specific heat for the capacitance calculation
     571    67792762 :     Real64 Cp = FluidProperties::GetSpecificHeatGlycol(
     572   101689143 :         state, state.dataPlnt->PlantLoop(LoopNum).FluidName, LastTankOutletTemp, state.dataPlnt->PlantLoop(LoopNum).FluidIndex, RoutineName);
     573             :     // set the fraction of loop mass assigned to each half loop outlet capacitance ('tank') calculation
     574             : 
     575             :     // calculate new loop inlet temperature.  The calculation is a simple 'tank' (thermal capacitance) calculation that includes:
     576             :     //--half of loop mass.  The other half is accounted for at the other half loop interface
     577             :     //--pump heat.  Pump heat for a single loop setpoint with pumps only on the supply side is added at the supply side inlet.
     578             :     //   Pump heat for a dual setpoint loop is added to each loop side inlet
     579             :     //  The previous tank temperature value is used to prevent accumulation of pump heat during iterations while recalculating
     580             :     // tank conditions each call.
     581             :     // Analytical solution for ODE, formulated for both final tank temp and average tank temp.
     582             : 
     583    33896381 :     Real64 TimeStepSeconds = TimeStepSys * DataGlobalConstants::SecInHour;
     584    33896381 :     Real64 MassFlowRate = state.dataLoopNodes->Node(TankInletNode).MassFlowRate;
     585    33896381 :     Real64 PumpHeat = state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).TotalPumpHeat;
     586    33896381 :     Real64 ThisTankMass = FracTotLoopMass * state.dataPlnt->PlantLoop(LoopNum).Mass;
     587             :     Real64 TankFinalTemp;
     588             :     Real64 TankAverageTemp;
     589    33896381 :     if (ThisTankMass <= 0.0) { // no mass, no plant loop volume
     590           0 :         if (MassFlowRate > 0.0) {
     591           0 :             TankFinalTemp = TankInletTemp + PumpHeat / (MassFlowRate * Cp);
     592           0 :             TankAverageTemp = (TankFinalTemp + LastTankOutletTemp) / 2.0;
     593             :         } else {
     594           0 :             TankFinalTemp = LastTankOutletTemp;
     595           0 :             TankAverageTemp = LastTankOutletTemp;
     596             :         }
     597             : 
     598             :     } else { // tank has mass
     599    33896381 :         if (MassFlowRate > 0.0) {
     600    17946850 :             Real64 const mdotCp = MassFlowRate * Cp;
     601    17946850 :             Real64 const mdotCpTempIn = mdotCp * TankInletTemp;
     602    17946850 :             Real64 const tankMassCp = ThisTankMass * Cp;
     603    17946850 :             Real64 const ExponentTerm = mdotCp / tankMassCp * TimeStepSeconds;
     604    17946850 :             if (ExponentTerm >= 700.0) {
     605           0 :                 TankFinalTemp = (mdotCp * TankInletTemp + PumpHeat) / mdotCp;
     606             : 
     607           0 :                 TankAverageTemp = (tankMassCp / mdotCp * (LastTankOutletTemp - (mdotCpTempIn + PumpHeat) / mdotCp) / TimeStepSeconds +
     608           0 :                                    (mdotCpTempIn + PumpHeat) / mdotCp);
     609             :             } else {
     610    35893700 :                 TankFinalTemp = (LastTankOutletTemp - (mdotCpTempIn + PumpHeat) / mdotCp) * std::exp(-ExponentTerm) +
     611    17946850 :                                 (mdotCpTempIn + PumpHeat) / (MassFlowRate * Cp);
     612             : 
     613    35893700 :                 TankAverageTemp = (tankMassCp / mdotCp * (LastTankOutletTemp - (mdotCpTempIn + PumpHeat) / mdotCp) * (1.0 - std::exp(-ExponentTerm)) /
     614             :                                        TimeStepSeconds +
     615    17946850 :                                    (mdotCpTempIn + PumpHeat) / mdotCp);
     616             :             }
     617             :         } else {
     618    15949531 :             TankFinalTemp = PumpHeat / (ThisTankMass * Cp) * TimeStepSeconds + LastTankOutletTemp;
     619    15949531 :             TankAverageTemp = (TankFinalTemp + LastTankOutletTemp) / 2.0;
     620             :         }
     621             :     }
     622             : 
     623             :     // update last tank outlet temperature
     624    33896381 :     state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).TempInterfaceTankOutlet = TankFinalTemp;
     625             : 
     626             :     // update heat transport and heat storage rates
     627    33896381 :     state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).LoopSideInlet_MdotCpDeltaT =
     628    33896381 :         (TankInletTemp - TankAverageTemp) * Cp * MassFlowRate;
     629    33896381 :     state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).LoopSideInlet_McpDTdt =
     630    33896381 :         (ThisTankMass * Cp * (TankFinalTemp - LastTankOutletTemp)) / TimeStepSeconds;
     631             : 
     632             :     // update report variable
     633    33896381 :     state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).LoopSideInlet_TankTemp = TankAverageTemp;
     634             : 
     635    33896381 :     TankOutletTemp = TankAverageTemp;
     636    33896381 : }
     637             : 
     638      520030 : void UpdateCommonPipe(EnergyPlusData &state,
     639             :                       PlantLocation const &TankInletPlantLoc,
     640             :                       DataPlant::CommonPipeType const CommonPipeType,
     641             :                       Real64 &MixedOutletTemp)
     642             : {
     643             : 
     644             :     // SUBROUTINE INFORMATION:
     645             :     //       AUTHOR         Rick Strand
     646             :     //       DATE WRITTEN   September 2001
     647             :     //       MODIFIED       Simon Rees, July 2007
     648             :     //                      Brent Griffith, Feb. 2010, add LoopNum arg
     649             :     //       RE-ENGINEERED  Brent Griffith, Sept 2010, generalize for both loop sides
     650             :     //                                           add pump heat from other loop
     651             :     //                      B.Griffith and L.Gu, Oct 2011, solve via analytical soln, use average over timestep
     652             : 
     653             :     // PURPOSE OF THIS SUBROUTINE:
     654             :     // This subroutine calculates the new loop side inlet temperature
     655             :     // based on the previous temperature of the mixed tank, mass flow rate and the new
     656             :     // outlet temperature on the supply side.  The temperature does not
     657             :     // pass directly across because the loop has some capacitance. It is
     658             :     // called separately but used for both supply-to-demand, and demand-to-supply
     659             : 
     660             :     // METHODOLOGY EMPLOYED:
     661             :     // This uses a analytical solution for changes in the
     662             :     // fluid loop temperature.  The user defines some volume of fluid
     663             :     // for the loop which gets converted to a fixed amount of mass.
     664             :     // The loop side inlet node is modeled as the outlet of a fully mixed
     665             :     // tank. Note that this routine is called repeatedly to re calculate
     666             :     // loop capacitance based on current plant conditions
     667             : 
     668             :     // Using/Aliasing
     669      520030 :     auto &SysTimeElapsed = state.dataHVACGlobal->SysTimeElapsed;
     670      520030 :     auto &TimeStepSys = state.dataHVACGlobal->TimeStepSys;
     671             : 
     672             :     // SUBROUTINE PARAMETER DEFINITIONS:
     673             :     static constexpr std::string_view RoutineName("UpdateCommonPipe");
     674             : 
     675             :     // find tank inlet and outlet nodes
     676      520030 :     int LoopNum = TankInletPlantLoc.loopNum;
     677      520030 :     DataPlant::LoopSideLocation TankInletLoopSide = TankInletPlantLoc.loopSideNum;
     678      520030 :     DataPlant::LoopSideLocation TankOutletLoopSide = DataPlant::LoopSideOther[static_cast<int>(TankInletPlantLoc.loopSideNum)]; // Outlet loopside
     679      520030 :     int TankInletNode = state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankInletLoopSide).NodeNumOut;
     680      520030 :     int TankOutletNode = state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).NodeNumIn;
     681             : 
     682      520030 :     Real64 TankInletTemp = state.dataLoopNodes->Node(TankInletNode).Temp;
     683             : 
     684             :     Real64 FracTotLoopMass; // Fraction of total loop mass assigned to the half loop
     685      520030 :     if (TankInletLoopSide == DataPlant::LoopSideLocation::Demand) {
     686             :         // for common pipe loops, assume 75% of plant loop volume is on the demand side
     687      260015 :         FracTotLoopMass = 0.25;
     688             :     } else {
     689      260015 :         FracTotLoopMass = 0.75;
     690             :     }
     691             : 
     692             :     // This needs to be based on time to deal with system downstepping and repeated timesteps
     693      520030 :     Real64 TimeElapsed = (state.dataGlobal->HourOfDay - 1) + state.dataGlobal->TimeStep * state.dataGlobal->TimeStepZone + SysTimeElapsed;
     694      520030 :     if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).TimeElapsed != TimeElapsed) {
     695       33636 :         state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).LastTempInterfaceTankOutlet =
     696       33636 :             state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).TempInterfaceTankOutlet;
     697       33636 :         state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).TimeElapsed = TimeElapsed;
     698             :     }
     699             : 
     700      520030 :     Real64 LastTankOutletTemp = state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).LastTempInterfaceTankOutlet;
     701             : 
     702             :     // calculate the specific heat for the capacitance calculation
     703     1040060 :     Real64 Cp = FluidProperties::GetSpecificHeatGlycol(
     704     1560090 :         state, state.dataPlnt->PlantLoop(LoopNum).FluidName, LastTankOutletTemp, state.dataPlnt->PlantLoop(LoopNum).FluidIndex, RoutineName);
     705             : 
     706             :     // set the fraction of loop mass assigned to each half loop outlet capacitance ('tank') calculation
     707             : 
     708             :     // calculate new loop inlet temperature.  The calculation is a simple 'tank' (thermal capacitance) calculation that includes:
     709             :     //--half of loop mass.  The other half is accounted for at the other half loop interface
     710             :     //--pump heat.  Pump heat for a single loop setpoint with pumps only on the supply side is added at the supply side inlet.
     711             :     // Pump heat for a dual setpoint loop is added to each loop side inlet
     712             :     // The previous inlet side temp,'ThisLoopSideTankOutletTemp' is used to prevent accumulation of pump heat during iterations.
     713             :     // The placement of the 'tank' for common pipes is *after* the outlet node and *before* the flow split or flow mixing.
     714             :     // This requires no logical check in the code since for purposes of temperature calculations, it is identical to the
     715             :     // no common pipe case.
     716             :     // calculation is separated because for common pipe, a different split for mass fraction is applied
     717             :     // The pump heat source is swapped around here compared to no common pipe (so pump heat sort stays on its own side).
     718      520030 :     Real64 TimeStepSeconds = TimeStepSys * DataGlobalConstants::SecInHour;
     719      520030 :     Real64 MassFlowRate = state.dataLoopNodes->Node(TankInletNode).MassFlowRate;
     720      520030 :     Real64 PumpHeat = state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankInletLoopSide).TotalPumpHeat;
     721      520030 :     Real64 ThisTankMass = FracTotLoopMass * state.dataPlnt->PlantLoop(LoopNum).Mass;
     722             : 
     723             :     Real64 TankFinalTemp;
     724             :     Real64 TankAverageTemp;
     725      520030 :     if (ThisTankMass <= 0.0) { // no mass, no plant loop volume
     726           0 :         if (MassFlowRate > 0.0) {
     727           0 :             TankFinalTemp = TankInletTemp + PumpHeat / (MassFlowRate * Cp);
     728           0 :             TankAverageTemp = (TankFinalTemp + LastTankOutletTemp) / 2.0;
     729             :         } else {
     730           0 :             TankFinalTemp = LastTankOutletTemp;
     731           0 :             TankAverageTemp = LastTankOutletTemp;
     732             :         }
     733             : 
     734             :     } else { // tank has mass
     735      520030 :         if (MassFlowRate > 0.0) {
     736      762783 :             TankFinalTemp = (LastTankOutletTemp - (MassFlowRate * Cp * TankInletTemp + PumpHeat) / (MassFlowRate * Cp)) *
     737      254261 :                                 std::exp(-(MassFlowRate * Cp) / (ThisTankMass * Cp) * TimeStepSeconds) +
     738      254261 :                             (MassFlowRate * Cp * TankInletTemp + PumpHeat) / (MassFlowRate * Cp);
     739      762783 :             TankAverageTemp = ((ThisTankMass * Cp) / (MassFlowRate * Cp) *
     740      508522 :                                    (LastTankOutletTemp - (MassFlowRate * Cp * TankInletTemp + PumpHeat) / (MassFlowRate * Cp)) *
     741      508522 :                                    (1.0 - std::exp(-(MassFlowRate * Cp) / (ThisTankMass * Cp) * TimeStepSeconds)) / TimeStepSeconds +
     742      254261 :                                (MassFlowRate * Cp * TankInletTemp + PumpHeat) / (MassFlowRate * Cp));
     743             :         } else {
     744             : 
     745      265769 :             TankFinalTemp = PumpHeat / (ThisTankMass * Cp) * TimeStepSeconds + LastTankOutletTemp;
     746      265769 :             TankAverageTemp = (TankFinalTemp + LastTankOutletTemp) / 2.0;
     747             :         }
     748             :     }
     749             :     // Common Pipe Simulation
     750      520030 :     if (CommonPipeType == DataPlant::CommonPipeType::Single) {
     751      137284 :         ManageSingleCommonPipe(state, LoopNum, TankOutletLoopSide, TankAverageTemp, MixedOutletTemp);
     752             :         // 2-way (controlled) common pipe simulation
     753      382746 :     } else if (CommonPipeType == DataPlant::CommonPipeType::TwoWay) {
     754      382746 :         PlantLocation TankOutletPlantLoc = {LoopNum, TankOutletLoopSide, 0, 0};
     755             : 
     756      382746 :         ManageTwoWayCommonPipe(state, TankOutletPlantLoc, TankAverageTemp);
     757      382746 :         MixedOutletTemp = state.dataLoopNodes->Node(TankOutletNode).Temp;
     758             :     }
     759             : 
     760      520030 :     state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).TempInterfaceTankOutlet = TankFinalTemp;
     761             : 
     762      520030 :     state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).LoopSideInlet_TankTemp = TankAverageTemp;
     763      520030 : }
     764             : 
     765      137284 : void ManageSingleCommonPipe(EnergyPlusData &state,
     766             :                             int const LoopNum,                          // plant loop number
     767             :                             DataPlant::LoopSideLocation const LoopSide, // plant loop side number
     768             :                             Real64 const TankOutletTemp, // inlet temperature to the common pipe passed in from the capacitance calculation
     769             :                             Real64 &MixedOutletTemp      // inlet temperature to the common pipe passed in from the capacitance calculation
     770             : )
     771             : {
     772             : 
     773             :     // SUBROUTINE INFORMATION:
     774             :     //       AUTHOR         Sankaranarayanan K P
     775             :     //       DATE WRITTEN   November 2006
     776             :     //       MODIFIED       B. Griffith, Jan 2010 clean up setup to allow mixing common pipe modes
     777             :     //                      B. Griffith, Mar 2010 add LoopNum arg and simplify
     778             :     //       RE-ENGINEERED  D. Fisher, Sept. 2010
     779             :     //                      B. Griffith, Oct 2011, major rewrite for plant upgrade
     780             : 
     781             :     // PURPOSE OF THIS SUBROUTINE:
     782             :     // To determine the conditions in common pipe viz., the flow flow temperature and direction of flow.
     783             : 
     784             :     // METHODOLOGY EMPLOYED:
     785             :     // Determine the flow on both sides of the common pipe. Decide if flow is coming into common pipe
     786             :     // or going out of common pipe. After that determine which interface calls the subroutine, i.e. if
     787             :     // called from "Demand to Supply" interface or "Supply to Demand" interface. Update the node temperatures
     788             :     // accordingly.
     789             : 
     790      137284 :     auto &PlantCommonPipe(state.dataHVACInterfaceMgr->PlantCommonPipe);
     791             : 
     792             :     // One time call to set up report variables and set common pipe 'type' flag
     793      137284 :     if (!state.dataHVACInterfaceMgr->CommonPipeSetupFinished) SetupCommonPipes(state);
     794             : 
     795             :     // fill local node indexes
     796      137284 :     int NodeNumPriIn = state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Supply).NodeNumIn;
     797      137284 :     int NodeNumPriOut = state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Supply).NodeNumOut;
     798      137284 :     int NodeNumSecIn = state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Demand).NodeNumIn;
     799      137284 :     int NodeNumSecOut = state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Demand).NodeNumOut;
     800             : 
     801      137284 :     auto &MyEnvrnFlag(PlantCommonPipe(LoopNum).MyEnvrnFlag);
     802      137284 :     if (MyEnvrnFlag && state.dataGlobal->BeginEnvrnFlag) {
     803          13 :         PlantCommonPipe(LoopNum).Flow = 0.0;
     804          13 :         PlantCommonPipe(LoopNum).Temp = 0.0;
     805          13 :         PlantCommonPipe(LoopNum).FlowDir = NoRecircFlow;
     806          13 :         MyEnvrnFlag = false;
     807             :     }
     808      137284 :     if (!state.dataGlobal->BeginEnvrnFlag) {
     809      136444 :         MyEnvrnFlag = true;
     810             :     }
     811             : 
     812             :     // every time inits
     813      137284 :     Real64 MdotSec = state.dataLoopNodes->Node(NodeNumSecOut).MassFlowRate;
     814      137284 :     Real64 MdotPri = state.dataLoopNodes->Node(NodeNumPriOut).MassFlowRate;
     815             : 
     816             :     Real64 TempSecOutTankOut;
     817             :     Real64 TempPriOutTankOut;
     818      137284 :     if (LoopSide == DataPlant::LoopSideLocation::Supply) {
     819       68642 :         TempSecOutTankOut = TankOutletTemp;
     820       68642 :         TempPriOutTankOut = state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Demand).LoopSideInlet_TankTemp;
     821             :     } else {
     822       68642 :         TempPriOutTankOut = TankOutletTemp;
     823       68642 :         TempSecOutTankOut = state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Supply).LoopSideInlet_TankTemp;
     824             :     }
     825             : 
     826             :     // first do mass balances and find common pipe flow rate and direction
     827             :     Real64 MdotPriRCLeg; // flow rate of primary recirculation thru common pipe kg/s
     828             :     Real64 MdotSecRCLeg; // flow rate of secondary recirculation thru common pipe kg/s
     829             :     Real64 TempSecInlet; // temperature at secondary inlet deg C
     830             :     Real64 TempPriInlet; // temperature at primary inlet deg C
     831             :     int CPFlowDir;       // flow direction in single common pipe
     832             :     Real64 CommonPipeTemp;
     833      137284 :     if (MdotPri > MdotSec) {
     834       49446 :         MdotPriRCLeg = MdotPri - MdotSec;
     835       49446 :         if (MdotPriRCLeg < DataBranchAirLoopPlant::MassFlowTolerance) {
     836           0 :             MdotPriRCLeg = 0.0;
     837           0 :             CPFlowDir = NoRecircFlow;
     838             :         } else {
     839       49446 :             CPFlowDir = PrimaryRecirc;
     840             :         }
     841       49446 :         MdotSecRCLeg = 0.0;
     842       49446 :         CommonPipeTemp = TempPriOutTankOut;
     843       87838 :     } else if (MdotPri < MdotSec) {
     844        5159 :         MdotSecRCLeg = MdotSec - MdotPri;
     845        5159 :         if (MdotSecRCLeg < DataBranchAirLoopPlant::MassFlowTolerance) {
     846           0 :             MdotSecRCLeg = 0.0;
     847           0 :             CPFlowDir = NoRecircFlow;
     848             :         } else {
     849        5159 :             CPFlowDir = SecondaryRecirc;
     850             :         }
     851        5159 :         MdotPriRCLeg = 0.0;
     852        5159 :         CommonPipeTemp = TempSecOutTankOut;
     853             :     } else { // equal
     854       82679 :         MdotPriRCLeg = 0.0;
     855       82679 :         MdotSecRCLeg = 0.0;
     856       82679 :         CPFlowDir = NoRecircFlow;
     857       82679 :         CommonPipeTemp = (TempPriOutTankOut + TempSecOutTankOut) / 2.0;
     858             :     }
     859             : 
     860             :     // now calculate inlet temps
     861             : 
     862      137284 :     if (MdotSec > 0.0) {
     863       54486 :         TempSecInlet = (MdotPri * TempPriOutTankOut + MdotSecRCLeg * TempSecOutTankOut - MdotPriRCLeg * TempPriOutTankOut) / (MdotSec);
     864             :     } else {
     865       82798 :         TempSecInlet = TempPriOutTankOut;
     866             :     }
     867      137284 :     if (MdotPri > 0.0) {
     868       52186 :         TempPriInlet = (MdotSec * TempSecOutTankOut + MdotPriRCLeg * TempPriOutTankOut - MdotSecRCLeg * TempSecOutTankOut) / (MdotPri);
     869             :     } else {
     870       85098 :         TempPriInlet = TempSecOutTankOut;
     871             :     }
     872             : 
     873             :     // Update the Common Pipe Data structure for reporting purposes.
     874      137284 :     PlantCommonPipe(LoopNum).Flow = max(MdotPriRCLeg, MdotSecRCLeg);
     875      137284 :     PlantCommonPipe(LoopNum).Temp = CommonPipeTemp;
     876      137284 :     PlantCommonPipe(LoopNum).FlowDir = CPFlowDir;
     877      137284 :     state.dataLoopNodes->Node(NodeNumSecIn).Temp = TempSecInlet;
     878      137284 :     state.dataLoopNodes->Node(NodeNumPriIn).Temp = TempPriInlet;
     879             : 
     880      137284 :     if (LoopSide == DataPlant::LoopSideLocation::Supply) {
     881       68642 :         MixedOutletTemp = TempPriInlet;
     882             :     } else {
     883       68642 :         MixedOutletTemp = TempSecInlet;
     884             :     }
     885      137284 : }
     886             : 
     887      382746 : void ManageTwoWayCommonPipe(EnergyPlusData &state, PlantLocation const &plantLoc, Real64 const TankOutletTemp)
     888             : {
     889             : 
     890             :     // SUBROUTINE INFORMATION:
     891             :     //       AUTHOR         B. Griffith
     892             :     //       DATE WRITTEN   June 2011
     893             :     //       MODIFIED       na
     894             :     //       RE-ENGINEERED  B. Griffith, Oct 2011.  rewrite
     895             : 
     896             :     // PURPOSE OF THIS SUBROUTINE:
     897             :     // manage two-way common pipe modeling at half-loop interface
     898             : 
     899             :     // METHODOLOGY EMPLOYED:
     900             :     // calculate mixed temperatures and various flow rates
     901             :     // sequential substitution of system of equations
     902             : 
     903             :     // REFERENCES:
     904             :     // reimplementation of CheckTwoWayCommonPipeConditions by Sankaranarayanan K P Jan 2007
     905             : 
     906             :     // SUBROUTINE PARAMETER DEFINITIONS:
     907             :     enum class UpdateType
     908             :     {
     909             :         DemandLedPrimaryInlet,
     910             :         DemandLedSecondaryInlet,
     911             :         SupplyLedPrimaryInlet,
     912             :         SupplyLedSecondaryInlet
     913      382746 :     } curCallingCase = UpdateType::SupplyLedPrimaryInlet;
     914      382746 :     constexpr int MaxIterLimitCaseA(8);
     915      382746 :     constexpr int MaxIterLimitCaseB(4);
     916             : 
     917             :     // one time setups
     918      382746 :     if (!state.dataHVACInterfaceMgr->CommonPipeSetupFinished) SetupCommonPipes(state);
     919             : 
     920      382746 :     auto &plantCommonPipe(state.dataHVACInterfaceMgr->PlantCommonPipe(plantLoc.loopNum));
     921      382746 :     auto &thisPlantLoop = state.dataPlnt->PlantLoop(plantLoc.loopNum);
     922             : 
     923             :     // fill local node indexes
     924      382746 :     int const NodeNumPriIn = thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Supply).NodeNumIn;
     925      382746 :     int const NodeNumPriOut = thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Supply).NodeNumOut;
     926      382746 :     int const NodeNumSecIn = thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Demand).NodeNumIn;
     927      382746 :     int const NodeNumSecOut = thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Demand).NodeNumOut;
     928             : 
     929             :     // begin environment inits
     930      382746 :     auto &MyEnvrnFlag(plantCommonPipe.MyEnvrnFlag);
     931      382746 :     if (MyEnvrnFlag && state.dataGlobal->BeginEnvrnFlag) {
     932          54 :         plantCommonPipe.PriToSecFlow = 0.0;
     933          54 :         plantCommonPipe.SecToPriFlow = 0.0;
     934          54 :         plantCommonPipe.PriCPLegFlow = 0.0;
     935          54 :         plantCommonPipe.SecCPLegFlow = 0.0;
     936          54 :         MyEnvrnFlag = false;
     937             :     }
     938             : 
     939      382746 :     if (!state.dataGlobal->BeginEnvrnFlag) {
     940      379554 :         MyEnvrnFlag = true;
     941             :     }
     942             : 
     943             :     // every time inits
     944      382746 :     Real64 MdotSec = state.dataLoopNodes->Node(NodeNumSecOut).MassFlowRate; // assume known and fixed by demand side operation
     945      382746 :     Real64 TempCPPrimaryCntrlSetPoint = state.dataLoopNodes->Node(NodeNumPriIn).TempSetPoint;
     946      382746 :     Real64 TempCPSecondaryCntrlSetPoint = state.dataLoopNodes->Node(NodeNumSecIn).TempSetPoint;
     947             : 
     948             :     // 6 unknowns follow, fill with current values
     949      382746 :     Real64 MdotPriToSec = plantCommonPipe.PriToSecFlow;
     950      382746 :     Real64 MdotPriRCLeg = plantCommonPipe.PriCPLegFlow;
     951      382746 :     Real64 MdotSecRCLeg = plantCommonPipe.SecCPLegFlow;
     952      382746 :     Real64 TempSecInlet = state.dataLoopNodes->Node(NodeNumSecIn).Temp;
     953      382746 :     Real64 TempPriInlet = state.dataLoopNodes->Node(NodeNumPriIn).Temp;
     954             :     Real64 MdotPri =
     955      382746 :         state.dataLoopNodes->Node(NodeNumPriOut).MassFlowRate; // may or may not be an unknown, If variable speed primary side, then unknown
     956             : 
     957             :     Real64 TempPriOutTankOut;
     958             :     Real64 TempSecOutTankOut;
     959      382746 :     if (plantLoc.loopSideNum == DataPlant::LoopSideLocation::Supply) {
     960      191373 :         TempSecOutTankOut = TankOutletTemp;
     961      191373 :         TempPriOutTankOut = thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Demand).LoopSideInlet_TankTemp;
     962             :     } else {
     963      191373 :         TempPriOutTankOut = TankOutletTemp;
     964      191373 :         TempSecOutTankOut = thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Supply).LoopSideInlet_TankTemp;
     965             :     }
     966             : 
     967             :     // determine current case
     968             :     // which side is being updated
     969             :     // commonpipe control point is the inlet of one of the half loops
     970      382746 :     if (plantLoc.loopSideNum == DataPlant::LoopSideLocation::Supply) { // update primary inlet
     971      223377 :         if (thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Supply).InletNodeSetPt &&
     972       32004 :             !thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Demand).InletNodeSetPt) {
     973       32004 :             curCallingCase = UpdateType::SupplyLedPrimaryInlet;
     974             : 
     975      318738 :         } else if (!thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Supply).InletNodeSetPt &&
     976      159369 :                    thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Demand).InletNodeSetPt) {
     977      159369 :             curCallingCase = UpdateType::DemandLedPrimaryInlet;
     978             :         }
     979             :     } else { // update secondary inlet
     980      223377 :         if (thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Supply).InletNodeSetPt &&
     981       32004 :             !thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Demand).InletNodeSetPt) {
     982       32004 :             curCallingCase = UpdateType::SupplyLedSecondaryInlet;
     983             : 
     984      318738 :         } else if (!thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Supply).InletNodeSetPt &&
     985      159369 :                    thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Demand).InletNodeSetPt) {
     986      159369 :             curCallingCase = UpdateType::DemandLedSecondaryInlet;
     987             :         }
     988             :     }
     989             : 
     990      382746 :     switch (curCallingCase) {
     991       64008 :     case UpdateType::SupplyLedPrimaryInlet:
     992             :     case UpdateType::SupplyLedSecondaryInlet:
     993             :         // CASE A, Primary/Supply Led
     994             :         // six equations and six unknowns (although one has a setpoint)
     995      576072 :         for (int loop = 1; loop <= MaxIterLimitCaseA; ++loop) {
     996             : 
     997             :             // eq 1
     998      512064 :             if (std::abs(TempSecOutTankOut - TempCPPrimaryCntrlSetPoint) > DataPlant::DeltaTempTol) {
     999      512064 :                 MdotPriToSec = MdotPriRCLeg * (TempCPPrimaryCntrlSetPoint - TempPriOutTankOut) / (TempSecOutTankOut - TempCPPrimaryCntrlSetPoint);
    1000      512064 :                 if (MdotPriToSec < DataBranchAirLoopPlant::MassFlowTolerance) MdotPriToSec = 0.0;
    1001      512064 :                 if (MdotPriToSec > MdotSec) MdotPriToSec = MdotSec;
    1002             :             } else {
    1003           0 :                 MdotPriToSec = MdotSec; //  what to do (?)
    1004             :             }
    1005             :             // eq. 5
    1006      512064 :             MdotPriRCLeg = MdotPri - MdotPriToSec;
    1007      512064 :             if (MdotPriRCLeg < DataBranchAirLoopPlant::MassFlowTolerance) MdotPriRCLeg = 0.0;
    1008             : 
    1009             :             // eq. 4
    1010      512064 :             MdotSecRCLeg = MdotSec - MdotPriToSec;
    1011      512064 :             if (MdotSecRCLeg < DataBranchAirLoopPlant::MassFlowTolerance) MdotSecRCLeg = 0.0;
    1012             : 
    1013             :             // eq  6
    1014      512064 :             if ((MdotPriToSec + MdotSecRCLeg) > DataBranchAirLoopPlant::MassFlowTolerance) {
    1015      345744 :                 TempSecInlet = (MdotPriToSec * TempPriOutTankOut + MdotSecRCLeg * TempSecOutTankOut) / (MdotPriToSec + MdotSecRCLeg);
    1016             :             } else {
    1017      166320 :                 TempSecInlet = TempPriOutTankOut;
    1018             :             }
    1019             : 
    1020             :             // eq. 3
    1021      512064 :             if ((plantCommonPipe.SupplySideInletPumpType == FlowType::Variable) && (curCallingCase == UpdateType::SupplyLedPrimaryInlet)) {
    1022             :                 // MdotPri is a variable to be calculated and flow request needs to be made
    1023           0 :                 if (std::abs(TempCPPrimaryCntrlSetPoint) > DataPlant::DeltaTempTol) {
    1024             : 
    1025           0 :                     MdotPri = (MdotPriRCLeg * TempPriOutTankOut + MdotPriToSec * TempSecOutTankOut) / (TempCPPrimaryCntrlSetPoint);
    1026             : 
    1027           0 :                     if (MdotPri < DataBranchAirLoopPlant::MassFlowTolerance) MdotPri = 0.0;
    1028             :                 } else {
    1029           0 :                     MdotPri = MdotSec;
    1030             :                 }
    1031           0 :                 PlantLocation thisPlantLoc = {plantLoc.loopNum, DataPlant::LoopSideLocation::Supply, 1, 0};
    1032           0 :                 PlantUtilities::SetActuatedBranchFlowRate(state, MdotPri, NodeNumPriIn, thisPlantLoc, false);
    1033             :             }
    1034             : 
    1035             :             // eq. 2
    1036      512064 :             if ((MdotPriToSec + MdotPriRCLeg) > DataBranchAirLoopPlant::MassFlowTolerance) {
    1037      345680 :                 TempPriInlet = (MdotPriToSec * TempSecOutTankOut + MdotPriRCLeg * TempPriOutTankOut) / (MdotPriToSec + MdotPriRCLeg);
    1038             :             } else {
    1039      166384 :                 TempPriInlet = TempSecOutTankOut;
    1040             :             }
    1041       64008 :         }
    1042       64008 :         break;
    1043      318738 :     case UpdateType::DemandLedPrimaryInlet:
    1044             :     case UpdateType::DemandLedSecondaryInlet:
    1045             :         // case B. Secondary/demand led
    1046             : 
    1047             :         // six equations and six unknowns (although one has a setpoint)
    1048     1593690 :         for (int loop = 1; loop <= MaxIterLimitCaseB; ++loop) {
    1049             :             // eq 1,
    1050     1274952 :             if (std::abs(TempPriOutTankOut - TempSecOutTankOut) > DataPlant::DeltaTempTol) {
    1051      890720 :                 MdotPriToSec = MdotSec * (TempCPSecondaryCntrlSetPoint - TempSecOutTankOut) / (TempPriOutTankOut - TempSecOutTankOut);
    1052      890720 :                 if (MdotPriToSec < DataBranchAirLoopPlant::MassFlowTolerance) MdotPriToSec = 0.0;
    1053      890720 :                 if (MdotPriToSec > MdotSec) MdotPriToSec = MdotSec;
    1054             :             } else {
    1055      384232 :                 MdotPriToSec = MdotSec;
    1056             :             }
    1057             : 
    1058             :             // eq. 2,
    1059     1274952 :             if ((MdotPriToSec + MdotPriRCLeg) > DataBranchAirLoopPlant::MassFlowTolerance) {
    1060      631545 :                 TempPriInlet = (MdotPriToSec * TempSecOutTankOut + MdotPriRCLeg * TempPriOutTankOut) / (MdotPriToSec + MdotPriRCLeg);
    1061             :             } else {
    1062      643407 :                 TempPriInlet = TempSecOutTankOut;
    1063             :             }
    1064             : 
    1065             :             // eq. 3
    1066     1274952 :             if ((plantCommonPipe.SupplySideInletPumpType == FlowType::Variable) && (curCallingCase == UpdateType::DemandLedPrimaryInlet)) {
    1067             :                 // MdotPri is a variable to be calculated and flow request made
    1068           0 :                 if (std::abs(TempPriOutTankOut - TempPriInlet) > DataPlant::DeltaTempTol) {
    1069           0 :                     MdotPri = MdotSec * (TempCPSecondaryCntrlSetPoint - TempSecOutTankOut) / (TempPriOutTankOut - TempPriInlet);
    1070           0 :                     if (MdotPri < DataBranchAirLoopPlant::MassFlowTolerance) MdotPri = 0.0;
    1071             :                 } else {
    1072           0 :                     MdotPri = MdotSec;
    1073             :                 }
    1074           0 :                 PlantLocation thisPlantLoc = {plantLoc.loopNum, DataPlant::LoopSideLocation::Supply, 1, 0};
    1075           0 :                 PlantUtilities::SetActuatedBranchFlowRate(state, MdotPri, NodeNumPriIn, thisPlantLoc, false);
    1076             :             }
    1077             : 
    1078             :             // eq. 4
    1079     1274952 :             MdotSecRCLeg = MdotSec - MdotPriToSec;
    1080     1274952 :             if (MdotSecRCLeg < DataBranchAirLoopPlant::MassFlowTolerance) MdotSecRCLeg = 0.0;
    1081             : 
    1082             :             // eq. 5
    1083     1274952 :             MdotPriRCLeg = MdotPri - MdotPriToSec;
    1084     1274952 :             if (MdotPriRCLeg < DataBranchAirLoopPlant::MassFlowTolerance) MdotPriRCLeg = 0.0;
    1085             : 
    1086             :             // eq  6
    1087     1274952 :             if ((MdotPriToSec + MdotSecRCLeg) > DataBranchAirLoopPlant::MassFlowTolerance) {
    1088      630624 :                 TempSecInlet = (MdotPriToSec * TempPriOutTankOut + MdotSecRCLeg * TempSecOutTankOut) / (MdotPriToSec + MdotSecRCLeg);
    1089             :             } else {
    1090      644328 :                 TempSecInlet = TempPriOutTankOut;
    1091             :             }
    1092      318738 :         }
    1093             :     }
    1094             : 
    1095             :     // update
    1096      382746 :     plantCommonPipe.PriToSecFlow = MdotPriToSec;
    1097      382746 :     plantCommonPipe.SecToPriFlow = MdotPriToSec;
    1098      382746 :     plantCommonPipe.PriCPLegFlow = MdotPriRCLeg;
    1099      382746 :     plantCommonPipe.SecCPLegFlow = MdotSecRCLeg;
    1100      382746 :     state.dataLoopNodes->Node(NodeNumSecIn).Temp = TempSecInlet;
    1101      382746 :     state.dataLoopNodes->Node(NodeNumPriIn).Temp = TempPriInlet;
    1102      382746 : }
    1103             : 
    1104          12 : void SetupCommonPipes(EnergyPlusData &state)
    1105             : {
    1106             : 
    1107             :     // SUBROUTINE INFORMATION:
    1108             :     //       AUTHOR         B. Griffith
    1109             :     //       DATE WRITTEN   Jan. 2010
    1110             :     //       MODIFIED       B. Griffith Oct. 2011
    1111             :     //       RE-ENGINEERED  na
    1112             : 
    1113             :     // PURPOSE OF THIS SUBROUTINE:
    1114             :     // collect allocation, outputs, and other set up for common pipes
    1115             : 
    1116          12 :     state.dataHVACInterfaceMgr->PlantCommonPipe.allocate(state.dataPlnt->TotNumLoops);
    1117             : 
    1118          44 :     for (int CurLoopNum = 1; CurLoopNum <= state.dataPlnt->TotNumLoops; ++CurLoopNum) {
    1119             : 
    1120             :         // reference to easily lookup the first item once
    1121          32 :         auto &thisPlantLoop = state.dataPlnt->PlantLoop(CurLoopNum);
    1122          32 :         auto &thisCommonPipe = state.dataHVACInterfaceMgr->PlantCommonPipe(CurLoopNum);
    1123          32 :         auto &first_demand_component_type(thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Demand).Branch(1).Comp(1).Type);
    1124          32 :         auto &first_supply_component_type(thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Supply).Branch(1).Comp(1).Type);
    1125             : 
    1126          32 :         switch (thisPlantLoop.CommonPipeType) {
    1127          20 :         case DataPlant::CommonPipeType::No:
    1128          20 :             thisCommonPipe.CommonPipeType = DataPlant::CommonPipeType::No;
    1129          20 :             break;
    1130           3 :         case DataPlant::CommonPipeType::Single: // Uncontrolled ('single') common pipe
    1131           3 :             thisCommonPipe.CommonPipeType = DataPlant::CommonPipeType::Single;
    1132           6 :             SetupOutputVariable(state,
    1133             :                                 "Plant Common Pipe Mass Flow Rate",
    1134             :                                 OutputProcessor::Unit::kg_s,
    1135             :                                 thisCommonPipe.Flow,
    1136             :                                 OutputProcessor::SOVTimeStepType::System,
    1137             :                                 OutputProcessor::SOVStoreType::Average,
    1138           3 :                                 thisPlantLoop.Name);
    1139           6 :             SetupOutputVariable(state,
    1140             :                                 "Plant Common Pipe Temperature",
    1141             :                                 OutputProcessor::Unit::C,
    1142             :                                 thisCommonPipe.Temp,
    1143             :                                 OutputProcessor::SOVTimeStepType::System,
    1144             :                                 OutputProcessor::SOVStoreType::Average,
    1145           3 :                                 thisPlantLoop.Name);
    1146           6 :             SetupOutputVariable(state,
    1147             :                                 "Plant Common Pipe Flow Direction Status",
    1148             :                                 OutputProcessor::Unit::None,
    1149             :                                 thisCommonPipe.FlowDir,
    1150             :                                 OutputProcessor::SOVTimeStepType::System,
    1151             :                                 OutputProcessor::SOVStoreType::Average,
    1152           3 :                                 thisPlantLoop.Name);
    1153             : 
    1154           3 :             if (first_supply_component_type == DataPlant::PlantEquipmentType::PumpVariableSpeed) {
    1155             :                 // If/when the model supports variable-pumping primary, this can be removed.
    1156           0 :                 ShowWarningError(state, "SetupCommonPipes: detected variable speed pump on supply inlet of CommonPipe plant loop");
    1157           0 :                 ShowContinueError(state, "Occurs on plant loop name = " + thisPlantLoop.Name);
    1158           0 :                 ShowContinueError(state, "The common pipe model does not support varying the flow rate on the primary/supply side");
    1159           0 :                 ShowContinueError(state, "The primary/supply side will operate as if constant speed, and the simulation continues");
    1160             :             }
    1161           3 :             break;
    1162           9 :         case DataPlant::CommonPipeType::TwoWay: // Controlled ('two-way') common pipe
    1163           9 :             thisCommonPipe.CommonPipeType = DataPlant::CommonPipeType::TwoWay;
    1164          18 :             SetupOutputVariable(state,
    1165             :                                 "Plant Common Pipe Primary Mass Flow Rate",
    1166             :                                 OutputProcessor::Unit::kg_s,
    1167             :                                 thisCommonPipe.PriCPLegFlow,
    1168             :                                 OutputProcessor::SOVTimeStepType::System,
    1169             :                                 OutputProcessor::SOVStoreType::Average,
    1170           9 :                                 thisPlantLoop.Name);
    1171          18 :             SetupOutputVariable(state,
    1172             :                                 "Plant Common Pipe Secondary Mass Flow Rate",
    1173             :                                 OutputProcessor::Unit::kg_s,
    1174             :                                 thisCommonPipe.SecCPLegFlow,
    1175             :                                 OutputProcessor::SOVTimeStepType::System,
    1176             :                                 OutputProcessor::SOVStoreType::Average,
    1177           9 :                                 thisPlantLoop.Name);
    1178          18 :             SetupOutputVariable(state,
    1179             :                                 "Plant Common Pipe Primary to Secondary Mass Flow Rate",
    1180             :                                 OutputProcessor::Unit::kg_s,
    1181             :                                 thisCommonPipe.PriToSecFlow,
    1182             :                                 OutputProcessor::SOVTimeStepType::System,
    1183             :                                 OutputProcessor::SOVStoreType::Average,
    1184           9 :                                 thisPlantLoop.Name);
    1185          18 :             SetupOutputVariable(state,
    1186             :                                 "Plant Common Pipe Secondary to Primary Mass Flow Rate",
    1187             :                                 OutputProcessor::Unit::kg_s,
    1188             :                                 thisCommonPipe.SecToPriFlow,
    1189             :                                 OutputProcessor::SOVTimeStepType::System,
    1190             :                                 OutputProcessor::SOVStoreType::Average,
    1191           9 :                                 thisPlantLoop.Name);
    1192             : 
    1193             :             // check type of pump on supply side inlet
    1194           9 :             if (first_supply_component_type == DataPlant::PlantEquipmentType::PumpConstantSpeed) {
    1195           8 :                 thisCommonPipe.SupplySideInletPumpType = FlowType::Constant;
    1196           1 :             } else if (first_supply_component_type == DataPlant::PlantEquipmentType::PumpVariableSpeed) {
    1197           0 :                 thisCommonPipe.SupplySideInletPumpType = FlowType::Variable;
    1198             :                 // If/when the model supports variable-pumping primary, this can be removed.
    1199           0 :                 ShowWarningError(state, "SetupCommonPipes: detected variable speed pump on supply inlet of TwoWayCommonPipe plant loop");
    1200           0 :                 ShowContinueError(state, "Occurs on plant loop name = " + thisPlantLoop.Name);
    1201           0 :                 ShowContinueError(state, "The common pipe model does not support varying the flow rate on the primary/supply side");
    1202           0 :                 ShowContinueError(state, "The primary/supply side will operate as if constant speed, and the simulation continues");
    1203             :             }
    1204             :             // check type of pump on demand side inlet
    1205           9 :             if (first_demand_component_type == DataPlant::PlantEquipmentType::PumpConstantSpeed) {
    1206           0 :                 thisCommonPipe.DemandSideInletPumpType = FlowType::Constant;
    1207           9 :             } else if (first_demand_component_type == DataPlant::PlantEquipmentType::PumpVariableSpeed) {
    1208           9 :                 thisCommonPipe.DemandSideInletPumpType = FlowType::Variable;
    1209             :             }
    1210           9 :             break;
    1211           0 :         default:
    1212           0 :             assert(false);
    1213             :         }
    1214             :     }
    1215             : 
    1216          12 :     state.dataHVACInterfaceMgr->CommonPipeSetupFinished = true;
    1217          12 : }
    1218             : 
    1219        2313 : } // namespace EnergyPlus::HVACInterfaceManager

Generated by: LCOV version 1.13