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