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 152726 : 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 152726 : auto &TmpRealARR = state.dataHVACInterfaceMgr->TmpRealARR;
109 152726 : auto &airLoopConv = state.dataConvergeParams->AirLoopConvergence(AirLoopNum);
110 152726 : auto &thisInletNode = state.dataLoopNodes->Node(InletNode);
111 152726 : int const iCall = (int)CalledFrom;
112 :
113 152726 : 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 0 : airLoopConv.HVACMassFlowNotConverged[iCall] = false;
117 0 : airLoopConv.HVACHumRatNotConverged[iCall] = false;
118 0 : airLoopConv.HVACTempNotConverged[iCall] = false;
119 0 : airLoopConv.HVACEnergyNotConverged[iCall] = false;
120 :
121 0 : Real64 totDemandSideMassFlow = 0.0;
122 0 : Real64 totDemandSideMinAvail = 0.0;
123 0 : Real64 totDemandSideMaxAvail = 0.0;
124 0 : for (int demIn = 1; demIn <= state.dataAirLoop->AirToZoneNodeInfo(AirLoopNum).NumSupplyNodes; ++demIn) {
125 0 : int demInNode = state.dataAirLoop->AirToZoneNodeInfo(AirLoopNum).ZoneEquipSupplyNodeNum(demIn);
126 0 : auto const &node = state.dataLoopNodes->Node(demInNode);
127 0 : totDemandSideMassFlow += node.MassFlowRate;
128 0 : totDemandSideMinAvail += node.MassFlowRateMinAvail;
129 0 : totDemandSideMaxAvail += node.MassFlowRateMaxAvail;
130 : }
131 0 : TmpRealARR = airLoopConv.HVACFlowDemandToSupplyTolValue;
132 0 : airLoopConv.HVACFlowDemandToSupplyTolValue[0] = std::abs(totDemandSideMassFlow - thisInletNode.MassFlowRate);
133 0 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
134 0 : airLoopConv.HVACFlowDemandToSupplyTolValue[logIndex] = TmpRealARR[logIndex - 1];
135 : }
136 0 : if (airLoopConv.HVACFlowDemandToSupplyTolValue[0] > DataConvergParams::HVACFlowRateToler) {
137 0 : airLoopConv.HVACMassFlowNotConverged[iCall] = true;
138 0 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
139 : }
140 :
141 0 : thisInletNode.MassFlowRate = totDemandSideMassFlow;
142 0 : thisInletNode.MassFlowRateMinAvail = totDemandSideMinAvail;
143 0 : thisInletNode.MassFlowRateMaxAvail = totDemandSideMaxAvail;
144 0 : return;
145 : }
146 :
147 : // Calculate the approximate energy difference across interface for comparison
148 : Real64 DeltaEnergy =
149 152726 : DataConvergParams::HVACCpApprox * ((state.dataLoopNodes->Node(OutletNode).MassFlowRate * state.dataLoopNodes->Node(OutletNode).Temp) -
150 152726 : (thisInletNode.MassFlowRate * thisInletNode.Temp));
151 :
152 152726 : if ((CalledFrom == DataConvergParams::CalledFrom::AirSystemDemandSide) && (OutletNode > 0)) {
153 :
154 75815 : airLoopConv.HVACMassFlowNotConverged[iCall] = false;
155 75815 : airLoopConv.HVACHumRatNotConverged[iCall] = false;
156 75815 : airLoopConv.HVACTempNotConverged[iCall] = false;
157 75815 : airLoopConv.HVACEnergyNotConverged[iCall] = false;
158 75815 : if (state.dataContaminantBalance->Contaminant.CO2Simulation) {
159 2 : airLoopConv.HVACCO2NotConverged[iCall] = false;
160 : }
161 75815 : if (state.dataContaminantBalance->Contaminant.GenericContamSimulation) {
162 2 : airLoopConv.HVACGenContamNotConverged[iCall] = false;
163 : }
164 :
165 75815 : TmpRealARR = airLoopConv.HVACFlowDemandToSupplyTolValue;
166 75815 : airLoopConv.HVACFlowDemandToSupplyTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).MassFlowRate - thisInletNode.MassFlowRate);
167 758150 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
168 682335 : airLoopConv.HVACFlowDemandToSupplyTolValue[logIndex] = TmpRealARR[logIndex - 1];
169 : }
170 75815 : if (airLoopConv.HVACFlowDemandToSupplyTolValue[0] > DataConvergParams::HVACFlowRateToler) {
171 13631 : airLoopConv.HVACMassFlowNotConverged[iCall] = true;
172 13631 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
173 : }
174 :
175 75815 : TmpRealARR = airLoopConv.HVACHumDemandToSupplyTolValue;
176 75815 : airLoopConv.HVACHumDemandToSupplyTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).HumRat - thisInletNode.HumRat);
177 758150 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
178 682335 : airLoopConv.HVACHumDemandToSupplyTolValue[logIndex] = TmpRealARR[logIndex - 1];
179 : }
180 75815 : if (airLoopConv.HVACHumDemandToSupplyTolValue[0] > DataConvergParams::HVACHumRatToler) {
181 2347 : airLoopConv.HVACHumRatNotConverged[iCall] = true;
182 2347 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
183 : }
184 :
185 75815 : TmpRealARR = airLoopConv.HVACTempDemandToSupplyTolValue;
186 75815 : airLoopConv.HVACTempDemandToSupplyTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).Temp - thisInletNode.Temp);
187 758150 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
188 682335 : airLoopConv.HVACTempDemandToSupplyTolValue[logIndex] = TmpRealARR[logIndex - 1];
189 : }
190 75815 : if (airLoopConv.HVACTempDemandToSupplyTolValue[0] > DataConvergParams::HVACTemperatureToler) {
191 19157 : airLoopConv.HVACTempNotConverged[iCall] = true;
192 19157 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
193 : }
194 :
195 75815 : TmpRealARR = airLoopConv.HVACEnergyDemandToSupplyTolValue;
196 75815 : airLoopConv.HVACEnergyDemandToSupplyTolValue[0] = std::abs(DeltaEnergy);
197 758150 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
198 682335 : airLoopConv.HVACEnergyDemandToSupplyTolValue[logIndex] = TmpRealARR[logIndex - 1];
199 : }
200 75815 : if (std::abs(DeltaEnergy) > DataConvergParams::HVACEnergyToler) {
201 25961 : airLoopConv.HVACEnergyNotConverged[iCall] = true;
202 25961 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
203 : }
204 :
205 75815 : TmpRealARR = airLoopConv.HVACEnthalpyDemandToSupplyTolValue;
206 75815 : airLoopConv.HVACEnthalpyDemandToSupplyTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).Enthalpy - thisInletNode.Enthalpy);
207 758150 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
208 682335 : airLoopConv.HVACEnthalpyDemandToSupplyTolValue[logIndex] = TmpRealARR[logIndex - 1];
209 : }
210 75815 : if (airLoopConv.HVACEnthalpyDemandToSupplyTolValue[0] > DataConvergParams::HVACEnthalpyToler) {
211 8614 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
212 : }
213 :
214 75815 : TmpRealARR = airLoopConv.HVACPressureDemandToSupplyTolValue;
215 75815 : airLoopConv.HVACPressureDemandToSupplyTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).Press - thisInletNode.Press);
216 758150 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
217 682335 : airLoopConv.HVACPressureDemandToSupplyTolValue[logIndex] = TmpRealARR[logIndex - 1];
218 : }
219 75815 : if (airLoopConv.HVACPressureDemandToSupplyTolValue[0] > DataConvergParams::HVACPressToler) {
220 92 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
221 : }
222 :
223 75815 : if (state.dataContaminantBalance->Contaminant.CO2Simulation) {
224 2 : TmpRealARR = airLoopConv.HVACCO2DemandToSupplyTolValue;
225 2 : airLoopConv.HVACCO2DemandToSupplyTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).CO2 - thisInletNode.CO2);
226 20 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
227 18 : airLoopConv.HVACCO2DemandToSupplyTolValue[logIndex] = TmpRealARR[logIndex - 1];
228 : }
229 2 : if (airLoopConv.HVACCO2DemandToSupplyTolValue[0] > DataConvergParams::HVACCO2Toler) {
230 1 : airLoopConv.HVACCO2NotConverged[iCall] = true;
231 1 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
232 : }
233 : }
234 :
235 75815 : if (state.dataContaminantBalance->Contaminant.GenericContamSimulation) {
236 2 : TmpRealARR = airLoopConv.HVACGenContamDemandToSupplyTolValue;
237 2 : airLoopConv.HVACGenContamDemandToSupplyTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).GenContam - thisInletNode.GenContam);
238 20 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
239 18 : airLoopConv.HVACGenContamDemandToSupplyTolValue[logIndex] = TmpRealARR[logIndex - 1];
240 : }
241 2 : if (airLoopConv.HVACGenContamDemandToSupplyTolValue[0] > DataConvergParams::HVACGenContamToler) {
242 1 : airLoopConv.HVACGenContamNotConverged[iCall] = true;
243 1 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
244 : }
245 : }
246 :
247 152726 : } else if (CalledFrom == DataConvergParams::CalledFrom::AirSystemSupplySideDeck1) {
248 :
249 76909 : airLoopConv.HVACMassFlowNotConverged[iCall] = false;
250 76909 : airLoopConv.HVACHumRatNotConverged[iCall] = false;
251 76909 : airLoopConv.HVACTempNotConverged[iCall] = false;
252 76909 : airLoopConv.HVACEnergyNotConverged[iCall] = false;
253 76909 : if (state.dataContaminantBalance->Contaminant.CO2Simulation) {
254 2 : airLoopConv.HVACCO2NotConverged[iCall] = false;
255 : }
256 76909 : if (state.dataContaminantBalance->Contaminant.GenericContamSimulation) {
257 2 : airLoopConv.HVACGenContamNotConverged[iCall] = false;
258 : }
259 :
260 76909 : TmpRealARR = airLoopConv.HVACFlowSupplyDeck1ToDemandTolValue;
261 76909 : airLoopConv.HVACFlowSupplyDeck1ToDemandTolValue[0] =
262 76909 : std::abs(state.dataLoopNodes->Node(OutletNode).MassFlowRate - thisInletNode.MassFlowRate);
263 769090 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
264 692181 : airLoopConv.HVACFlowSupplyDeck1ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
265 : }
266 76909 : if (airLoopConv.HVACFlowSupplyDeck1ToDemandTolValue[0] > DataConvergParams::HVACFlowRateToler) {
267 14909 : airLoopConv.HVACMassFlowNotConverged[iCall] = true;
268 14909 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
269 : }
270 :
271 76909 : TmpRealARR = airLoopConv.HVACHumSupplyDeck1ToDemandTolValue;
272 76909 : airLoopConv.HVACHumSupplyDeck1ToDemandTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).HumRat - thisInletNode.HumRat);
273 769090 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
274 692181 : airLoopConv.HVACHumSupplyDeck1ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
275 : }
276 76909 : if (airLoopConv.HVACHumSupplyDeck1ToDemandTolValue[0] > DataConvergParams::HVACHumRatToler) {
277 12553 : airLoopConv.HVACHumRatNotConverged[iCall] = true;
278 12553 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
279 : }
280 :
281 76909 : TmpRealARR = airLoopConv.HVACTempSupplyDeck1ToDemandTolValue;
282 76909 : airLoopConv.HVACTempSupplyDeck1ToDemandTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).Temp - thisInletNode.Temp);
283 769090 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
284 692181 : airLoopConv.HVACTempSupplyDeck1ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
285 : }
286 76909 : if (airLoopConv.HVACTempSupplyDeck1ToDemandTolValue[0] > DataConvergParams::HVACTemperatureToler) {
287 38616 : airLoopConv.HVACTempNotConverged[iCall] = true;
288 38616 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
289 : }
290 :
291 76909 : TmpRealARR = airLoopConv.HVACEnergySupplyDeck1ToDemandTolValue;
292 76909 : airLoopConv.HVACEnergySupplyDeck1ToDemandTolValue[0] = DeltaEnergy;
293 769090 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
294 692181 : airLoopConv.HVACEnergySupplyDeck1ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
295 : }
296 76909 : if (std::abs(DeltaEnergy) > DataConvergParams::HVACEnergyToler) {
297 32477 : airLoopConv.HVACEnergyNotConverged[iCall] = true;
298 32477 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
299 : }
300 :
301 76909 : TmpRealARR = airLoopConv.HVACEnthalpySupplyDeck1ToDemandTolValue;
302 76909 : airLoopConv.HVACEnthalpySupplyDeck1ToDemandTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).Enthalpy - thisInletNode.Enthalpy);
303 769090 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
304 692181 : airLoopConv.HVACEnthalpySupplyDeck1ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
305 : }
306 76909 : if (airLoopConv.HVACEnthalpySupplyDeck1ToDemandTolValue[0] > DataConvergParams::HVACEnthalpyToler) {
307 28375 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
308 : }
309 :
310 76909 : TmpRealARR = airLoopConv.HVACPressureSupplyDeck1ToDemandTolValue;
311 76909 : airLoopConv.HVACPressureSupplyDeck1ToDemandTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).Press - thisInletNode.Press);
312 769090 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
313 692181 : airLoopConv.HVACPressureSupplyDeck1ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
314 : }
315 76909 : if (airLoopConv.HVACPressureSupplyDeck1ToDemandTolValue[0] > DataConvergParams::HVACPressToler) {
316 16 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
317 : }
318 : // CO2 check
319 76909 : if (state.dataContaminantBalance->Contaminant.CO2Simulation) {
320 2 : TmpRealARR = airLoopConv.HVACCO2SupplyDeck1ToDemandTolValue;
321 2 : airLoopConv.HVACCO2SupplyDeck1ToDemandTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).CO2 - thisInletNode.CO2);
322 20 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
323 18 : airLoopConv.HVACCO2SupplyDeck1ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
324 : }
325 2 : if (airLoopConv.HVACCO2SupplyDeck1ToDemandTolValue[0] > DataConvergParams::HVACCO2Toler) {
326 1 : airLoopConv.HVACCO2NotConverged[iCall] = true;
327 1 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
328 : }
329 : }
330 :
331 76909 : if (state.dataContaminantBalance->Contaminant.GenericContamSimulation) {
332 2 : TmpRealARR = airLoopConv.HVACGenContamSupplyDeck1ToDemandTolValue;
333 2 : airLoopConv.HVACGenContamSupplyDeck1ToDemandTolValue[0] =
334 2 : std::abs(state.dataLoopNodes->Node(OutletNode).GenContam - thisInletNode.GenContam);
335 20 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
336 18 : airLoopConv.HVACGenContamSupplyDeck1ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
337 : }
338 2 : if (airLoopConv.HVACGenContamSupplyDeck1ToDemandTolValue[0] > DataConvergParams::HVACGenContamToler) {
339 1 : airLoopConv.HVACGenContamNotConverged[iCall] = true;
340 1 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
341 : }
342 : }
343 :
344 2 : } else if (CalledFrom == DataConvergParams::CalledFrom::AirSystemSupplySideDeck2) {
345 :
346 2 : airLoopConv.HVACMassFlowNotConverged[iCall] = false;
347 2 : airLoopConv.HVACHumRatNotConverged[iCall] = false;
348 2 : airLoopConv.HVACTempNotConverged[iCall] = false;
349 2 : airLoopConv.HVACEnergyNotConverged[iCall] = false;
350 2 : if (state.dataContaminantBalance->Contaminant.CO2Simulation) {
351 2 : airLoopConv.HVACCO2NotConverged[iCall] = false;
352 : }
353 2 : if (state.dataContaminantBalance->Contaminant.GenericContamSimulation) {
354 2 : airLoopConv.HVACGenContamNotConverged[iCall] = false;
355 : }
356 :
357 2 : TmpRealARR = airLoopConv.HVACFlowSupplyDeck2ToDemandTolValue;
358 2 : airLoopConv.HVACFlowSupplyDeck2ToDemandTolValue[0] =
359 2 : std::abs(state.dataLoopNodes->Node(OutletNode).MassFlowRate - thisInletNode.MassFlowRate);
360 20 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
361 18 : airLoopConv.HVACFlowSupplyDeck2ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
362 : }
363 2 : if (airLoopConv.HVACFlowSupplyDeck2ToDemandTolValue[0] > DataConvergParams::HVACFlowRateToler) {
364 0 : airLoopConv.HVACMassFlowNotConverged[iCall] = true;
365 0 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
366 : }
367 :
368 2 : TmpRealARR = airLoopConv.HVACHumSupplyDeck2ToDemandTolValue;
369 2 : airLoopConv.HVACHumSupplyDeck2ToDemandTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).HumRat - thisInletNode.HumRat);
370 20 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
371 18 : airLoopConv.HVACHumSupplyDeck2ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
372 : }
373 2 : if (airLoopConv.HVACHumSupplyDeck2ToDemandTolValue[0] > DataConvergParams::HVACHumRatToler) {
374 0 : airLoopConv.HVACHumRatNotConverged[iCall] = true;
375 0 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
376 : }
377 :
378 2 : TmpRealARR = airLoopConv.HVACTempSupplyDeck2ToDemandTolValue;
379 2 : airLoopConv.HVACTempSupplyDeck2ToDemandTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).Temp - thisInletNode.Temp);
380 20 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
381 18 : airLoopConv.HVACTempSupplyDeck2ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
382 : }
383 2 : if (airLoopConv.HVACTempSupplyDeck2ToDemandTolValue[0] > DataConvergParams::HVACTemperatureToler) {
384 0 : airLoopConv.HVACTempNotConverged[iCall] = true;
385 0 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
386 : }
387 :
388 2 : TmpRealARR = airLoopConv.HVACEnergySupplyDeck2ToDemandTolValue;
389 2 : airLoopConv.HVACEnergySupplyDeck2ToDemandTolValue[0] = DeltaEnergy;
390 20 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
391 18 : airLoopConv.HVACEnergySupplyDeck2ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
392 : }
393 2 : if (std::abs(DeltaEnergy) > DataConvergParams::HVACEnergyToler) {
394 0 : airLoopConv.HVACEnergyNotConverged[iCall] = true;
395 0 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
396 : }
397 :
398 2 : TmpRealARR = airLoopConv.HVACEnthalpySupplyDeck2ToDemandTolValue;
399 2 : airLoopConv.HVACEnthalpySupplyDeck2ToDemandTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).Enthalpy - thisInletNode.Enthalpy);
400 20 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
401 18 : airLoopConv.HVACEnthalpySupplyDeck2ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
402 : }
403 2 : if (airLoopConv.HVACEnthalpySupplyDeck2ToDemandTolValue[0] > DataConvergParams::HVACEnthalpyToler) {
404 0 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
405 : }
406 :
407 2 : TmpRealARR = airLoopConv.HVACPressueSupplyDeck2ToDemandTolValue;
408 2 : airLoopConv.HVACPressueSupplyDeck2ToDemandTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).Press - thisInletNode.Press);
409 20 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
410 18 : airLoopConv.HVACPressueSupplyDeck2ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
411 : }
412 2 : if (airLoopConv.HVACPressueSupplyDeck2ToDemandTolValue[0] > DataConvergParams::HVACPressToler) {
413 0 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
414 : }
415 :
416 2 : if (state.dataContaminantBalance->Contaminant.CO2Simulation) {
417 2 : TmpRealARR = airLoopConv.HVACCO2SupplyDeck2ToDemandTolValue;
418 2 : airLoopConv.HVACCO2SupplyDeck2ToDemandTolValue[0] = std::abs(state.dataLoopNodes->Node(OutletNode).CO2 - thisInletNode.CO2);
419 20 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
420 18 : airLoopConv.HVACCO2SupplyDeck2ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
421 : }
422 2 : if (airLoopConv.HVACCO2SupplyDeck2ToDemandTolValue[0] > DataConvergParams::HVACCO2Toler) {
423 1 : airLoopConv.HVACCO2NotConverged[iCall] = true;
424 1 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
425 : }
426 : }
427 2 : if (state.dataContaminantBalance->Contaminant.GenericContamSimulation) {
428 2 : TmpRealARR = airLoopConv.HVACGenContamSupplyDeck2ToDemandTolValue;
429 2 : airLoopConv.HVACGenContamSupplyDeck2ToDemandTolValue[0] =
430 2 : std::abs(state.dataLoopNodes->Node(OutletNode).GenContam - thisInletNode.GenContam);
431 20 : for (int logIndex = 1; logIndex < DataConvergParams::ConvergLogStackDepth; logIndex++) {
432 18 : airLoopConv.HVACGenContamSupplyDeck2ToDemandTolValue[logIndex] = TmpRealARR[logIndex - 1];
433 : }
434 2 : if (airLoopConv.HVACGenContamSupplyDeck2ToDemandTolValue[0] > DataConvergParams::HVACGenContamToler) {
435 1 : airLoopConv.HVACGenContamNotConverged[iCall] = true;
436 1 : OutOfToleranceFlag = true; // Something has changed--resimulate the other side of the loop
437 : }
438 : }
439 : }
440 :
441 : // Always update the new inlet conditions
442 152726 : thisInletNode.Temp = state.dataLoopNodes->Node(OutletNode).Temp;
443 152726 : thisInletNode.MassFlowRate = state.dataLoopNodes->Node(OutletNode).MassFlowRate;
444 152726 : thisInletNode.MassFlowRateMinAvail = state.dataLoopNodes->Node(OutletNode).MassFlowRateMinAvail;
445 152726 : thisInletNode.MassFlowRateMaxAvail = state.dataLoopNodes->Node(OutletNode).MassFlowRateMaxAvail;
446 152726 : thisInletNode.Quality = state.dataLoopNodes->Node(OutletNode).Quality;
447 152726 : thisInletNode.Press = state.dataLoopNodes->Node(OutletNode).Press;
448 152726 : thisInletNode.Enthalpy = state.dataLoopNodes->Node(OutletNode).Enthalpy;
449 152726 : thisInletNode.HumRat = state.dataLoopNodes->Node(OutletNode).HumRat;
450 :
451 152726 : if (state.dataContaminantBalance->Contaminant.CO2Simulation) {
452 6 : thisInletNode.CO2 = state.dataLoopNodes->Node(OutletNode).CO2;
453 : }
454 :
455 152726 : if (state.dataContaminantBalance->Contaminant.GenericContamSimulation) {
456 6 : thisInletNode.GenContam = state.dataLoopNodes->Node(OutletNode).GenContam;
457 : }
458 : }
459 :
460 176418 : 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 176418 : int LoopNum = plantLoc.loopNum;
494 176418 : DataPlant::LoopSideLocation ThisLoopSideNum = plantLoc.loopSideNum;
495 176418 : auto &convergence(state.dataConvergeParams->PlantConvergence(LoopNum));
496 :
497 : // reset out of tolerance flags
498 176418 : convergence.PlantMassFlowNotConverged = false;
499 176418 : convergence.PlantTempNotConverged = false;
500 :
501 : // set the LoopSide inlet node
502 176418 : int ThisLoopSideInletNode = state.dataPlnt->PlantLoop(LoopNum).LoopSide(ThisLoopSideNum).NodeNumIn;
503 :
504 : // save the inlet node temp for DeltaEnergy check
505 176418 : Real64 OldOtherLoopSideInletMdot = state.dataLoopNodes->Node(OtherLoopSideInletNode).MassFlowRate;
506 176418 : Real64 OldTankOutletTemp = state.dataLoopNodes->Node(OtherLoopSideInletNode).Temp;
507 :
508 : // calculate the specific heat
509 176418 : Real64 Cp = state.dataPlnt->PlantLoop(LoopNum).glycol->getSpecificHeat(state, OldTankOutletTemp, RoutineName);
510 :
511 : // update the enthalpy
512 176418 : state.dataLoopNodes->Node(OtherLoopSideInletNode).Enthalpy = Cp * state.dataLoopNodes->Node(OtherLoopSideInletNode).Temp;
513 :
514 : // update the temperatures and flow rates
515 176418 : auto &flow_demand_to_supply_tol(convergence.PlantFlowDemandToSupplyTolValue);
516 176418 : auto &flow_supply_to_demand_tol(convergence.PlantFlowSupplyToDemandTolValue);
517 : Real64 MixedOutletTemp;
518 : Real64 TankOutletTemp;
519 176418 : if (CommonPipeType == DataPlant::CommonPipeType::Single || CommonPipeType == DataPlant::CommonPipeType::TwoWay) {
520 : // update the temperature
521 0 : UpdateCommonPipe(state, plantLoc, CommonPipeType, MixedOutletTemp);
522 0 : state.dataLoopNodes->Node(OtherLoopSideInletNode).Temp = MixedOutletTemp;
523 0 : TankOutletTemp = MixedOutletTemp;
524 0 : if (ThisLoopSideNum == DataPlant::LoopSideLocation::Demand) {
525 0 : rshift1(flow_demand_to_supply_tol);
526 0 : flow_demand_to_supply_tol[0] = std::abs(OldOtherLoopSideInletMdot - state.dataLoopNodes->Node(OtherLoopSideInletNode).MassFlowRate);
527 0 : if (flow_demand_to_supply_tol[0] > DataConvergParams::PlantFlowRateToler) {
528 0 : convergence.PlantMassFlowNotConverged = true;
529 : }
530 : } else {
531 0 : rshift1(flow_supply_to_demand_tol);
532 0 : flow_supply_to_demand_tol[0] = std::abs(OldOtherLoopSideInletMdot - state.dataLoopNodes->Node(OtherLoopSideInletNode).MassFlowRate);
533 0 : 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 0 : 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 0 : state.dataLoopNodes->Node(ThisLoopSideInletNode).MassFlowRateMinAvail =
541 0 : state.dataLoopNodes->Node(ThisLoopSideOutletNode).MassFlowRateMinAvail;
542 0 : state.dataLoopNodes->Node(ThisLoopSideInletNode).MassFlowRateMaxAvail =
543 0 : state.dataLoopNodes->Node(ThisLoopSideOutletNode).MassFlowRateMaxAvail;
544 :
545 : } else { // no common pipe
546 176418 : UpdateHalfLoopInletTemp(state, LoopNum, ThisLoopSideNum, TankOutletTemp);
547 : // update the temperature
548 176418 : state.dataLoopNodes->Node(OtherLoopSideInletNode).Temp = TankOutletTemp;
549 : // Set the flow tolerance array
550 176418 : if (ThisLoopSideNum == DataPlant::LoopSideLocation::Demand) {
551 88210 : rshift1(flow_demand_to_supply_tol);
552 176420 : flow_demand_to_supply_tol[0] = std::abs(state.dataLoopNodes->Node(ThisLoopSideOutletNode).MassFlowRate -
553 88210 : state.dataLoopNodes->Node(OtherLoopSideInletNode).MassFlowRate);
554 88210 : if (flow_demand_to_supply_tol[0] > DataConvergParams::PlantFlowRateToler) {
555 9420 : convergence.PlantMassFlowNotConverged = true;
556 : }
557 : } else {
558 88208 : rshift1(flow_supply_to_demand_tol);
559 176416 : flow_supply_to_demand_tol[0] = std::abs(state.dataLoopNodes->Node(ThisLoopSideOutletNode).MassFlowRate -
560 88208 : state.dataLoopNodes->Node(OtherLoopSideInletNode).MassFlowRate);
561 88208 : if (flow_supply_to_demand_tol[0] > DataConvergParams::PlantFlowRateToler) {
562 2720 : convergence.PlantMassFlowNotConverged = true;
563 : }
564 : }
565 : // PlantFlowTolValue(PlantQuePtr) = ABS(Node(ThisLoopSideOutletNode)%MassFlowRate-Node(OtherLoopSideInletNode)%MassFlowRate)
566 : // Set the flow rate
567 176418 : state.dataLoopNodes->Node(OtherLoopSideInletNode).MassFlowRate = state.dataLoopNodes->Node(ThisLoopSideOutletNode).MassFlowRate;
568 : // update the MIN/MAX available flow rates
569 176418 : state.dataLoopNodes->Node(OtherLoopSideInletNode).MassFlowRateMinAvail =
570 176418 : state.dataLoopNodes->Node(ThisLoopSideOutletNode).MassFlowRateMinAvail;
571 176418 : state.dataLoopNodes->Node(OtherLoopSideInletNode).MassFlowRateMaxAvail =
572 176418 : state.dataLoopNodes->Node(ThisLoopSideOutletNode).MassFlowRateMaxAvail;
573 : // update Quality. Note: This update assumes that STEAM cannot be used with common pipes.
574 176418 : 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 176418 : if (state.dataPlnt->PlantLoop(LoopNum).HasPressureComponents) {
577 : // Don't update pressure, let the pressure simulation handle pressures
578 : } else {
579 : // Do update pressure!
580 176418 : state.dataLoopNodes->Node(OtherLoopSideInletNode).Press = state.dataLoopNodes->Node(ThisLoopSideOutletNode).Press;
581 : }
582 : }
583 :
584 : // temperature
585 176418 : if (ThisLoopSideNum == DataPlant::LoopSideLocation::Demand) {
586 88210 : auto &temp_demand_to_supply_tol(convergence.PlantTempDemandToSupplyTolValue);
587 88210 : rshift1(temp_demand_to_supply_tol);
588 88210 : temp_demand_to_supply_tol[0] = std::abs(OldTankOutletTemp - state.dataLoopNodes->Node(OtherLoopSideInletNode).Temp);
589 88210 : if (temp_demand_to_supply_tol[0] > DataConvergParams::PlantTemperatureToler) {
590 25049 : convergence.PlantTempNotConverged = true;
591 : }
592 : } else {
593 88208 : auto &temp_supply_to_demand_tol(convergence.PlantTempSupplyToDemandTolValue);
594 88208 : rshift1(temp_supply_to_demand_tol);
595 88208 : temp_supply_to_demand_tol[0] = std::abs(OldTankOutletTemp - state.dataLoopNodes->Node(OtherLoopSideInletNode).Temp);
596 88208 : if (temp_supply_to_demand_tol[0] > DataConvergParams::PlantTemperatureToler) {
597 11052 : convergence.PlantTempNotConverged = true;
598 : }
599 : }
600 :
601 : // Set out of tolerance flags
602 176418 : if (ThisLoopSideNum == DataPlant::LoopSideLocation::Demand) {
603 88210 : if (convergence.PlantMassFlowNotConverged || convergence.PlantTempNotConverged) {
604 25717 : OutOfToleranceFlag = true;
605 : }
606 : } else {
607 88208 : if (convergence.PlantMassFlowNotConverged) {
608 2720 : OutOfToleranceFlag = true;
609 : }
610 : }
611 176418 : }
612 :
613 176420 : 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 176420 : Real64 SysTimeElapsed = state.dataHVACGlobal->SysTimeElapsed;
641 :
642 : // SUBROUTINE PARAMETER DEFINITIONS:
643 176420 : 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 176420 : DataPlant::LoopSideLocation TankOutletLoopSide = DataPlant::LoopSideOther[static_cast<int>(TankInletLoopSide)];
648 176420 : int TankInletNode = state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankInletLoopSide).NodeNumOut;
649 176420 : 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 176420 : Real64 TimeElapsed = (state.dataGlobal->HourOfDay - 1) + state.dataGlobal->TimeStep * state.dataGlobal->TimeStepZone + SysTimeElapsed;
653 176420 : if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).TimeElapsed != TimeElapsed) {
654 39653 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).LastTempInterfaceTankOutlet =
655 39653 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).TempInterfaceTankOutlet;
656 39653 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).TimeElapsed = TimeElapsed;
657 : }
658 :
659 176420 : Real64 LastTankOutletTemp = state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).LastTempInterfaceTankOutlet;
660 :
661 : // calculate the specific heat for the capacitance calculation
662 176420 : 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 176420 : Real64 TimeStepSeconds = state.dataHVACGlobal->TimeStepSysSec;
674 176420 : Real64 MassFlowRate = state.dataLoopNodes->Node(TankInletNode).MassFlowRate;
675 176420 : Real64 PumpHeat = state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).TotalPumpHeat;
676 176420 : Real64 ThisTankMass = FracTotLoopMass * state.dataPlnt->PlantLoop(LoopNum).Mass;
677 : Real64 TankFinalTemp;
678 : Real64 TankAverageTemp;
679 176420 : 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 176420 : if (MassFlowRate > 0.0) {
690 93852 : Real64 const mdotCp = MassFlowRate * Cp;
691 93852 : Real64 const mdotCpTempIn = mdotCp * TankInletTemp;
692 93852 : Real64 const tankMassCp = ThisTankMass * Cp;
693 93852 : Real64 const ExponentTerm = mdotCp / tankMassCp * TimeStepSeconds;
694 93852 : if (ExponentTerm >= 700.0) {
695 2 : TankFinalTemp = (mdotCp * TankInletTemp + PumpHeat) / mdotCp;
696 :
697 2 : TankAverageTemp = (tankMassCp / mdotCp * (LastTankOutletTemp - (mdotCpTempIn + PumpHeat) / mdotCp) / TimeStepSeconds +
698 2 : (mdotCpTempIn + PumpHeat) / mdotCp);
699 : } else {
700 93850 : TankFinalTemp = (LastTankOutletTemp - (mdotCpTempIn + PumpHeat) / mdotCp) * std::exp(-ExponentTerm) +
701 93850 : (mdotCpTempIn + PumpHeat) / (MassFlowRate * Cp);
702 :
703 93850 : TankAverageTemp = (tankMassCp / mdotCp * (LastTankOutletTemp - (mdotCpTempIn + PumpHeat) / mdotCp) * (1.0 - std::exp(-ExponentTerm)) /
704 : TimeStepSeconds +
705 93850 : (mdotCpTempIn + PumpHeat) / mdotCp);
706 : }
707 : } else {
708 82568 : TankFinalTemp = PumpHeat / (ThisTankMass * Cp) * TimeStepSeconds + LastTankOutletTemp;
709 82568 : TankAverageTemp = (TankFinalTemp + LastTankOutletTemp) / 2.0;
710 : }
711 : }
712 :
713 : // update last tank outlet temperature
714 176420 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).TempInterfaceTankOutlet = TankFinalTemp;
715 :
716 : // update heat transport and heat storage rates
717 176420 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).LoopSideInlet_MdotCpDeltaT =
718 176420 : (TankInletTemp - TankAverageTemp) * Cp * MassFlowRate;
719 176420 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).LoopSideInlet_McpDTdt =
720 176420 : (ThisTankMass * Cp * (TankFinalTemp - LastTankOutletTemp)) / TimeStepSeconds;
721 :
722 : // update report variable
723 176420 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).LoopSideInlet_TankTemp = TankAverageTemp;
724 :
725 176420 : TankOutletTemp = TankAverageTemp;
726 176420 : }
727 :
728 0 : 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 0 : 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 0 : int LoopNum = TankInletPlantLoc.loopNum;
766 0 : DataPlant::LoopSideLocation TankInletLoopSide = TankInletPlantLoc.loopSideNum;
767 0 : DataPlant::LoopSideLocation TankOutletLoopSide = DataPlant::LoopSideOther[static_cast<int>(TankInletPlantLoc.loopSideNum)]; // Outlet loopside
768 0 : int TankInletNode = state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankInletLoopSide).NodeNumOut;
769 0 : int TankOutletNode = state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).NodeNumIn;
770 :
771 0 : Real64 TankInletTemp = state.dataLoopNodes->Node(TankInletNode).Temp;
772 :
773 : Real64 FracTotLoopMass; // Fraction of total loop mass assigned to the half loop
774 0 : if (TankInletLoopSide == DataPlant::LoopSideLocation::Demand) {
775 : // for common pipe loops, assume 75% of plant loop volume is on the demand side
776 0 : FracTotLoopMass = 0.25;
777 : } else {
778 0 : FracTotLoopMass = 0.75;
779 : }
780 :
781 : // This needs to be based on time to deal with system downstepping and repeated timesteps
782 0 : Real64 TimeElapsed = (state.dataGlobal->HourOfDay - 1) + state.dataGlobal->TimeStep * state.dataGlobal->TimeStepZone + SysTimeElapsed;
783 0 : if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).TimeElapsed != TimeElapsed) {
784 0 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).LastTempInterfaceTankOutlet =
785 0 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).TempInterfaceTankOutlet;
786 0 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).TimeElapsed = TimeElapsed;
787 : }
788 :
789 0 : Real64 LastTankOutletTemp = state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).LastTempInterfaceTankOutlet;
790 :
791 : // calculate the specific heat for the capacitance calculation
792 0 : 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 0 : Real64 TimeStepSeconds = state.dataHVACGlobal->TimeStepSysSec;
807 0 : Real64 MassFlowRate = state.dataLoopNodes->Node(TankInletNode).MassFlowRate;
808 0 : Real64 PumpHeat = state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankInletLoopSide).TotalPumpHeat;
809 0 : Real64 ThisTankMass = FracTotLoopMass * state.dataPlnt->PlantLoop(LoopNum).Mass;
810 :
811 : Real64 TankFinalTemp;
812 : Real64 TankAverageTemp;
813 0 : 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 0 : if (MassFlowRate > 0.0) {
824 0 : TankFinalTemp = (LastTankOutletTemp - (MassFlowRate * Cp * TankInletTemp + PumpHeat) / (MassFlowRate * Cp)) *
825 0 : std::exp(-(MassFlowRate * Cp) / (ThisTankMass * Cp) * TimeStepSeconds) +
826 0 : (MassFlowRate * Cp * TankInletTemp + PumpHeat) / (MassFlowRate * Cp);
827 0 : TankAverageTemp = ((ThisTankMass * Cp) / (MassFlowRate * Cp) *
828 0 : (LastTankOutletTemp - (MassFlowRate * Cp * TankInletTemp + PumpHeat) / (MassFlowRate * Cp)) *
829 0 : (1.0 - std::exp(-(MassFlowRate * Cp) / (ThisTankMass * Cp) * TimeStepSeconds)) / TimeStepSeconds +
830 0 : (MassFlowRate * Cp * TankInletTemp + PumpHeat) / (MassFlowRate * Cp));
831 : } else {
832 :
833 0 : TankFinalTemp = PumpHeat / (ThisTankMass * Cp) * TimeStepSeconds + LastTankOutletTemp;
834 0 : TankAverageTemp = (TankFinalTemp + LastTankOutletTemp) / 2.0;
835 : }
836 : }
837 : // Common Pipe Simulation
838 0 : if (CommonPipeType == DataPlant::CommonPipeType::Single) {
839 0 : ManageSingleCommonPipe(state, LoopNum, TankOutletLoopSide, TankAverageTemp, MixedOutletTemp);
840 : // 2-way (controlled) common pipe simulation
841 0 : } else if (CommonPipeType == DataPlant::CommonPipeType::TwoWay) {
842 0 : PlantLocation TankOutletPlantLoc = {LoopNum, TankOutletLoopSide, 0, 0};
843 :
844 0 : ManageTwoWayCommonPipe(state, TankOutletPlantLoc, TankAverageTemp);
845 0 : MixedOutletTemp = state.dataLoopNodes->Node(TankOutletNode).Temp;
846 : }
847 :
848 0 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).TempInterfaceTankOutlet = TankFinalTemp;
849 :
850 0 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(TankOutletLoopSide).LoopSideInlet_TankTemp = TankAverageTemp;
851 0 : }
852 :
853 0 : 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 0 : if (!state.dataHVACInterfaceMgr->CommonPipeSetupFinished) SetupCommonPipes(state);
880 :
881 0 : auto &plantCommonPipe = state.dataHVACInterfaceMgr->PlantCommonPipe(LoopNum);
882 :
883 : // fill local node indexes
884 0 : int NodeNumPriIn = state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Supply).NodeNumIn;
885 0 : int NodeNumPriOut = state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Supply).NodeNumOut;
886 0 : int NodeNumSecIn = state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Demand).NodeNumIn;
887 0 : int NodeNumSecOut = state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Demand).NodeNumOut;
888 :
889 0 : if (plantCommonPipe.MyEnvrnFlag && state.dataGlobal->BeginEnvrnFlag) {
890 0 : plantCommonPipe.Flow = 0.0;
891 0 : plantCommonPipe.Temp = 0.0;
892 0 : plantCommonPipe.FlowDir = NoRecircFlow;
893 0 : plantCommonPipe.MyEnvrnFlag = false;
894 : }
895 0 : if (!state.dataGlobal->BeginEnvrnFlag) {
896 0 : plantCommonPipe.MyEnvrnFlag = true;
897 : }
898 :
899 : // every time inits
900 0 : Real64 MdotSec = state.dataLoopNodes->Node(NodeNumSecOut).MassFlowRate;
901 0 : Real64 MdotPri = state.dataLoopNodes->Node(NodeNumPriOut).MassFlowRate;
902 :
903 : Real64 TempSecOutTankOut;
904 : Real64 TempPriOutTankOut;
905 0 : if (LoopSide == DataPlant::LoopSideLocation::Supply) {
906 0 : TempSecOutTankOut = TankOutletTemp;
907 0 : TempPriOutTankOut = state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Demand).LoopSideInlet_TankTemp;
908 : } else {
909 0 : TempPriOutTankOut = TankOutletTemp;
910 0 : TempSecOutTankOut = state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Supply).LoopSideInlet_TankTemp;
911 : }
912 :
913 : // first do mass balances and find common pipe flow rate and direction
914 : Real64 MdotPriRCLeg; // flow rate of primary recirculation thru common pipe kg/s
915 : Real64 MdotSecRCLeg; // flow rate of secondary recirculation thru common pipe kg/s
916 : Real64 TempSecInlet; // temperature at secondary inlet deg C
917 : Real64 TempPriInlet; // temperature at primary inlet deg C
918 : int CPFlowDir; // flow direction in single common pipe
919 : Real64 CommonPipeTemp;
920 0 : if (MdotPri > MdotSec) {
921 0 : MdotPriRCLeg = MdotPri - MdotSec;
922 0 : if (MdotPriRCLeg < DataBranchAirLoopPlant::MassFlowTolerance) {
923 0 : MdotPriRCLeg = 0.0;
924 0 : CPFlowDir = NoRecircFlow;
925 : } else {
926 0 : CPFlowDir = PrimaryRecirc;
927 : }
928 0 : MdotSecRCLeg = 0.0;
929 0 : CommonPipeTemp = TempPriOutTankOut;
930 0 : } else if (MdotPri < MdotSec) {
931 0 : MdotSecRCLeg = MdotSec - MdotPri;
932 0 : if (MdotSecRCLeg < DataBranchAirLoopPlant::MassFlowTolerance) {
933 0 : MdotSecRCLeg = 0.0;
934 0 : CPFlowDir = NoRecircFlow;
935 : } else {
936 0 : CPFlowDir = SecondaryRecirc;
937 : }
938 0 : MdotPriRCLeg = 0.0;
939 0 : CommonPipeTemp = TempSecOutTankOut;
940 : } else { // equal
941 0 : MdotPriRCLeg = 0.0;
942 0 : MdotSecRCLeg = 0.0;
943 0 : CPFlowDir = NoRecircFlow;
944 0 : CommonPipeTemp = (TempPriOutTankOut + TempSecOutTankOut) / 2.0;
945 : }
946 :
947 : // now calculate inlet temps
948 :
949 0 : if (MdotSec > 0.0) {
950 0 : TempSecInlet = (MdotPri * TempPriOutTankOut + MdotSecRCLeg * TempSecOutTankOut - MdotPriRCLeg * TempPriOutTankOut) / (MdotSec);
951 : } else {
952 0 : TempSecInlet = TempPriOutTankOut;
953 : }
954 0 : if (MdotPri > 0.0) {
955 0 : TempPriInlet = (MdotSec * TempSecOutTankOut + MdotPriRCLeg * TempPriOutTankOut - MdotSecRCLeg * TempSecOutTankOut) / (MdotPri);
956 : } else {
957 0 : TempPriInlet = TempSecOutTankOut;
958 : }
959 :
960 : // Update the Common Pipe Data structure for reporting purposes.
961 0 : plantCommonPipe.Flow = max(MdotPriRCLeg, MdotSecRCLeg);
962 0 : plantCommonPipe.Temp = CommonPipeTemp;
963 0 : plantCommonPipe.FlowDir = CPFlowDir;
964 0 : state.dataLoopNodes->Node(NodeNumSecIn).Temp = TempSecInlet;
965 0 : state.dataLoopNodes->Node(NodeNumPriIn).Temp = TempPriInlet;
966 :
967 0 : if (LoopSide == DataPlant::LoopSideLocation::Supply) {
968 0 : MixedOutletTemp = TempPriInlet;
969 : } else {
970 0 : MixedOutletTemp = TempSecInlet;
971 : }
972 0 : }
973 :
974 0 : void ManageTwoWayCommonPipe(EnergyPlusData &state, PlantLocation const &plantLoc, Real64 const TankOutletTemp)
975 : {
976 :
977 : // SUBROUTINE INFORMATION:
978 : // AUTHOR B. Griffith
979 : // DATE WRITTEN June 2011
980 : // MODIFIED na
981 : // RE-ENGINEERED B. Griffith, Oct 2011. rewrite
982 :
983 : // PURPOSE OF THIS SUBROUTINE:
984 : // manage two-way common pipe modeling at half-loop interface
985 :
986 : // METHODOLOGY EMPLOYED:
987 : // calculate mixed temperatures and various flow rates
988 : // sequential substitution of system of equations
989 :
990 : // REFERENCES:
991 : // reimplementation of CheckTwoWayCommonPipeConditions by Sankaranarayanan K P Jan 2007
992 :
993 : // SUBROUTINE PARAMETER DEFINITIONS:
994 : enum class UpdateType
995 : {
996 : DemandLedPrimaryInlet,
997 : DemandLedSecondaryInlet,
998 : SupplyLedPrimaryInlet,
999 : SupplyLedSecondaryInlet
1000 0 : } curCallingCase = UpdateType::SupplyLedPrimaryInlet;
1001 0 : constexpr int MaxIterLimitCaseA(8);
1002 0 : constexpr int MaxIterLimitCaseB(4);
1003 :
1004 : // one time setups
1005 0 : if (!state.dataHVACInterfaceMgr->CommonPipeSetupFinished) SetupCommonPipes(state);
1006 :
1007 0 : auto &plantCommonPipe(state.dataHVACInterfaceMgr->PlantCommonPipe(plantLoc.loopNum));
1008 0 : auto &thisPlantLoop = state.dataPlnt->PlantLoop(plantLoc.loopNum);
1009 :
1010 : // fill local node indexes
1011 0 : int const NodeNumPriIn = thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Supply).NodeNumIn;
1012 0 : int const NodeNumPriOut = thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Supply).NodeNumOut;
1013 0 : int const NodeNumSecIn = thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Demand).NodeNumIn;
1014 0 : int const NodeNumSecOut = thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Demand).NodeNumOut;
1015 :
1016 : // begin environment inits
1017 0 : if (plantCommonPipe.MyEnvrnFlag && state.dataGlobal->BeginEnvrnFlag) {
1018 0 : plantCommonPipe.PriToSecFlow = 0.0;
1019 0 : plantCommonPipe.SecToPriFlow = 0.0;
1020 0 : plantCommonPipe.PriCPLegFlow = 0.0;
1021 0 : plantCommonPipe.SecCPLegFlow = 0.0;
1022 0 : plantCommonPipe.MyEnvrnFlag = false;
1023 : }
1024 :
1025 0 : if (!state.dataGlobal->BeginEnvrnFlag) {
1026 0 : plantCommonPipe.MyEnvrnFlag = true;
1027 : }
1028 :
1029 : // every time inits
1030 0 : Real64 MdotSec = state.dataLoopNodes->Node(NodeNumSecOut).MassFlowRate; // assume known and fixed by demand side operation
1031 0 : Real64 TempCPPrimaryCntrlSetPoint = state.dataLoopNodes->Node(NodeNumPriIn).TempSetPoint;
1032 0 : Real64 TempCPSecondaryCntrlSetPoint = state.dataLoopNodes->Node(NodeNumSecIn).TempSetPoint;
1033 :
1034 : // 6 unknowns follow, fill with current values
1035 0 : Real64 MdotPriToSec = plantCommonPipe.PriToSecFlow;
1036 0 : Real64 MdotPriRCLeg = plantCommonPipe.PriCPLegFlow;
1037 0 : Real64 MdotSecRCLeg = plantCommonPipe.SecCPLegFlow;
1038 0 : Real64 TempSecInlet = state.dataLoopNodes->Node(NodeNumSecIn).Temp;
1039 0 : Real64 TempPriInlet = state.dataLoopNodes->Node(NodeNumPriIn).Temp;
1040 : Real64 MdotPri =
1041 0 : state.dataLoopNodes->Node(NodeNumPriOut).MassFlowRate; // may or may not be an unknown, If variable speed primary side, then unknown
1042 :
1043 : Real64 TempPriOutTankOut;
1044 : Real64 TempSecOutTankOut;
1045 0 : if (plantLoc.loopSideNum == DataPlant::LoopSideLocation::Supply) {
1046 0 : TempSecOutTankOut = TankOutletTemp;
1047 0 : TempPriOutTankOut = thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Demand).LoopSideInlet_TankTemp;
1048 : } else {
1049 0 : TempPriOutTankOut = TankOutletTemp;
1050 0 : TempSecOutTankOut = thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Supply).LoopSideInlet_TankTemp;
1051 : }
1052 :
1053 : // determine current case
1054 : // which side is being updated
1055 : // commonpipe control point is the inlet of one of the half loops
1056 0 : if (plantLoc.loopSideNum == DataPlant::LoopSideLocation::Supply) { // update primary inlet
1057 0 : if (thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Supply).InletNodeSetPt &&
1058 0 : !thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Demand).InletNodeSetPt) {
1059 0 : curCallingCase = UpdateType::SupplyLedPrimaryInlet;
1060 :
1061 0 : } else if (!thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Supply).InletNodeSetPt &&
1062 0 : thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Demand).InletNodeSetPt) {
1063 0 : curCallingCase = UpdateType::DemandLedPrimaryInlet;
1064 : }
1065 : } else { // update secondary inlet
1066 0 : if (thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Supply).InletNodeSetPt &&
1067 0 : !thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Demand).InletNodeSetPt) {
1068 0 : curCallingCase = UpdateType::SupplyLedSecondaryInlet;
1069 :
1070 0 : } else if (!thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Supply).InletNodeSetPt &&
1071 0 : thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Demand).InletNodeSetPt) {
1072 0 : curCallingCase = UpdateType::DemandLedSecondaryInlet;
1073 : }
1074 : }
1075 :
1076 0 : switch (curCallingCase) {
1077 0 : case UpdateType::SupplyLedPrimaryInlet:
1078 : case UpdateType::SupplyLedSecondaryInlet:
1079 : // CASE A, Primary/Supply Led
1080 : // six equations and six unknowns (although one has a setpoint)
1081 0 : for (int loop = 1; loop <= MaxIterLimitCaseA; ++loop) {
1082 :
1083 : // eq 1
1084 0 : if (std::abs(TempSecOutTankOut - TempCPPrimaryCntrlSetPoint) > DataPlant::DeltaTempTol) {
1085 0 : MdotPriToSec = MdotPriRCLeg * (TempCPPrimaryCntrlSetPoint - TempPriOutTankOut) / (TempSecOutTankOut - TempCPPrimaryCntrlSetPoint);
1086 0 : if (MdotPriToSec < DataBranchAirLoopPlant::MassFlowTolerance) MdotPriToSec = 0.0;
1087 0 : if (MdotPriToSec > MdotSec) MdotPriToSec = MdotSec;
1088 : } else {
1089 0 : MdotPriToSec = MdotSec; // what to do (?)
1090 : }
1091 : // eq. 5
1092 0 : MdotPriRCLeg = MdotPri - MdotPriToSec;
1093 0 : if (MdotPriRCLeg < DataBranchAirLoopPlant::MassFlowTolerance) MdotPriRCLeg = 0.0;
1094 :
1095 : // eq. 4
1096 0 : MdotSecRCLeg = MdotSec - MdotPriToSec;
1097 0 : if (MdotSecRCLeg < DataBranchAirLoopPlant::MassFlowTolerance) MdotSecRCLeg = 0.0;
1098 :
1099 : // eq 6
1100 0 : if ((MdotPriToSec + MdotSecRCLeg) > DataBranchAirLoopPlant::MassFlowTolerance) {
1101 0 : TempSecInlet = (MdotPriToSec * TempPriOutTankOut + MdotSecRCLeg * TempSecOutTankOut) / (MdotPriToSec + MdotSecRCLeg);
1102 : } else {
1103 0 : TempSecInlet = TempPriOutTankOut;
1104 : }
1105 :
1106 : // eq. 3
1107 0 : if ((plantCommonPipe.SupplySideInletPumpType == FlowType::Variable) && (curCallingCase == UpdateType::SupplyLedPrimaryInlet)) {
1108 : // MdotPri is a variable to be calculated and flow request needs to be made
1109 0 : if (std::abs(TempCPPrimaryCntrlSetPoint) > DataPlant::DeltaTempTol) {
1110 :
1111 0 : MdotPri = (MdotPriRCLeg * TempPriOutTankOut + MdotPriToSec * TempSecOutTankOut) / (TempCPPrimaryCntrlSetPoint);
1112 :
1113 0 : if (MdotPri < DataBranchAirLoopPlant::MassFlowTolerance) MdotPri = 0.0;
1114 : } else {
1115 0 : MdotPri = MdotSec;
1116 : }
1117 0 : PlantLocation thisPlantLoc = {plantLoc.loopNum, DataPlant::LoopSideLocation::Supply, 1, 0};
1118 0 : PlantUtilities::SetActuatedBranchFlowRate(state, MdotPri, NodeNumPriIn, thisPlantLoc, false);
1119 : }
1120 :
1121 : // eq. 2
1122 0 : if ((MdotPriToSec + MdotPriRCLeg) > DataBranchAirLoopPlant::MassFlowTolerance) {
1123 0 : TempPriInlet = (MdotPriToSec * TempSecOutTankOut + MdotPriRCLeg * TempPriOutTankOut) / (MdotPriToSec + MdotPriRCLeg);
1124 : } else {
1125 0 : TempPriInlet = TempSecOutTankOut;
1126 : }
1127 : }
1128 0 : break;
1129 0 : case UpdateType::DemandLedPrimaryInlet:
1130 : case UpdateType::DemandLedSecondaryInlet:
1131 : // case B. Secondary/demand led
1132 :
1133 : // six equations and six unknowns (although one has a setpoint)
1134 0 : for (int loop = 1; loop <= MaxIterLimitCaseB; ++loop) {
1135 : // eq 1,
1136 0 : if (std::abs(TempPriOutTankOut - TempSecOutTankOut) > DataPlant::DeltaTempTol) {
1137 0 : MdotPriToSec = MdotSec * (TempCPSecondaryCntrlSetPoint - TempSecOutTankOut) / (TempPriOutTankOut - TempSecOutTankOut);
1138 0 : if (MdotPriToSec < DataBranchAirLoopPlant::MassFlowTolerance) MdotPriToSec = 0.0;
1139 0 : if (MdotPriToSec > MdotSec) MdotPriToSec = MdotSec;
1140 : } else {
1141 0 : MdotPriToSec = MdotSec;
1142 : }
1143 :
1144 : // eq. 2,
1145 0 : if ((MdotPriToSec + MdotPriRCLeg) > DataBranchAirLoopPlant::MassFlowTolerance) {
1146 0 : TempPriInlet = (MdotPriToSec * TempSecOutTankOut + MdotPriRCLeg * TempPriOutTankOut) / (MdotPriToSec + MdotPriRCLeg);
1147 : } else {
1148 0 : TempPriInlet = TempSecOutTankOut;
1149 : }
1150 :
1151 : // eq. 3
1152 0 : if ((plantCommonPipe.SupplySideInletPumpType == FlowType::Variable) && (curCallingCase == UpdateType::DemandLedPrimaryInlet)) {
1153 : // MdotPri is a variable to be calculated and flow request made
1154 0 : if (std::abs(TempPriOutTankOut - TempPriInlet) > DataPlant::DeltaTempTol) {
1155 0 : MdotPri = MdotSec * (TempCPSecondaryCntrlSetPoint - TempSecOutTankOut) / (TempPriOutTankOut - TempPriInlet);
1156 0 : if (MdotPri < DataBranchAirLoopPlant::MassFlowTolerance) MdotPri = 0.0;
1157 : } else {
1158 0 : MdotPri = MdotSec;
1159 : }
1160 0 : PlantLocation thisPlantLoc = {plantLoc.loopNum, DataPlant::LoopSideLocation::Supply, 1, 0};
1161 0 : PlantUtilities::SetActuatedBranchFlowRate(state, MdotPri, NodeNumPriIn, thisPlantLoc, false);
1162 : }
1163 :
1164 : // eq. 4
1165 0 : MdotSecRCLeg = MdotSec - MdotPriToSec;
1166 0 : if (MdotSecRCLeg < DataBranchAirLoopPlant::MassFlowTolerance) MdotSecRCLeg = 0.0;
1167 :
1168 : // eq. 5
1169 0 : MdotPriRCLeg = MdotPri - MdotPriToSec;
1170 0 : if (MdotPriRCLeg < DataBranchAirLoopPlant::MassFlowTolerance) MdotPriRCLeg = 0.0;
1171 :
1172 : // eq 6
1173 0 : if ((MdotPriToSec + MdotSecRCLeg) > DataBranchAirLoopPlant::MassFlowTolerance) {
1174 0 : TempSecInlet = (MdotPriToSec * TempPriOutTankOut + MdotSecRCLeg * TempSecOutTankOut) / (MdotPriToSec + MdotSecRCLeg);
1175 : } else {
1176 0 : TempSecInlet = TempPriOutTankOut;
1177 : }
1178 : }
1179 : }
1180 :
1181 : // update
1182 0 : plantCommonPipe.PriToSecFlow = MdotPriToSec;
1183 0 : plantCommonPipe.SecToPriFlow = MdotPriToSec;
1184 0 : plantCommonPipe.PriCPLegFlow = MdotPriRCLeg;
1185 0 : plantCommonPipe.SecCPLegFlow = MdotSecRCLeg;
1186 0 : state.dataLoopNodes->Node(NodeNumSecIn).Temp = TempSecInlet;
1187 0 : state.dataLoopNodes->Node(NodeNumPriIn).Temp = TempPriInlet;
1188 0 : }
1189 :
1190 0 : void SetupCommonPipes(EnergyPlusData &state)
1191 : {
1192 :
1193 : // SUBROUTINE INFORMATION:
1194 : // AUTHOR B. Griffith
1195 : // DATE WRITTEN Jan. 2010
1196 : // MODIFIED B. Griffith Oct. 2011
1197 : // RE-ENGINEERED na
1198 :
1199 : // PURPOSE OF THIS SUBROUTINE:
1200 : // collect allocation, outputs, and other set up for common pipes
1201 :
1202 0 : state.dataHVACInterfaceMgr->PlantCommonPipe.allocate(state.dataPlnt->TotNumLoops);
1203 :
1204 0 : for (int CurLoopNum = 1; CurLoopNum <= state.dataPlnt->TotNumLoops; ++CurLoopNum) {
1205 :
1206 : // reference to easily lookup the first item once
1207 0 : auto &thisPlantLoop = state.dataPlnt->PlantLoop(CurLoopNum);
1208 0 : auto &thisCommonPipe = state.dataHVACInterfaceMgr->PlantCommonPipe(CurLoopNum);
1209 0 : auto const &first_demand_component_type = thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Demand).Branch(1).Comp(1).Type;
1210 0 : auto const &first_supply_component_type = thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Supply).Branch(1).Comp(1).Type;
1211 :
1212 0 : switch (thisPlantLoop.CommonPipeType) {
1213 0 : case DataPlant::CommonPipeType::No:
1214 0 : thisCommonPipe.CommonPipeType = DataPlant::CommonPipeType::No;
1215 0 : break;
1216 0 : case DataPlant::CommonPipeType::Single: // Uncontrolled ('single') common pipe
1217 0 : thisCommonPipe.CommonPipeType = DataPlant::CommonPipeType::Single;
1218 0 : SetupOutputVariable(state,
1219 : "Plant Common Pipe Mass Flow Rate",
1220 : Constant::Units::kg_s,
1221 0 : thisCommonPipe.Flow,
1222 : OutputProcessor::TimeStepType::System,
1223 : OutputProcessor::StoreType::Average,
1224 0 : thisPlantLoop.Name);
1225 0 : SetupOutputVariable(state,
1226 : "Plant Common Pipe Temperature",
1227 : Constant::Units::C,
1228 0 : thisCommonPipe.Temp,
1229 : OutputProcessor::TimeStepType::System,
1230 : OutputProcessor::StoreType::Average,
1231 0 : thisPlantLoop.Name);
1232 0 : SetupOutputVariable(state,
1233 : "Plant Common Pipe Flow Direction Status",
1234 : Constant::Units::None,
1235 0 : thisCommonPipe.FlowDir,
1236 : OutputProcessor::TimeStepType::System,
1237 : OutputProcessor::StoreType::Average,
1238 0 : thisPlantLoop.Name);
1239 :
1240 0 : if (first_supply_component_type == DataPlant::PlantEquipmentType::PumpVariableSpeed) {
1241 : // If/when the model supports variable-pumping primary, this can be removed.
1242 0 : ShowWarningError(state, "SetupCommonPipes: detected variable speed pump on supply inlet of CommonPipe plant loop");
1243 0 : ShowContinueError(state, format("Occurs on plant loop name = {}", thisPlantLoop.Name));
1244 0 : ShowContinueError(state, "The common pipe model does not support varying the flow rate on the primary/supply side");
1245 0 : ShowContinueError(state, "The primary/supply side will operate as if constant speed, and the simulation continues");
1246 : }
1247 0 : break;
1248 0 : case DataPlant::CommonPipeType::TwoWay: // Controlled ('two-way') common pipe
1249 0 : thisCommonPipe.CommonPipeType = DataPlant::CommonPipeType::TwoWay;
1250 0 : SetupOutputVariable(state,
1251 : "Plant Common Pipe Primary Mass Flow Rate",
1252 : Constant::Units::kg_s,
1253 0 : thisCommonPipe.PriCPLegFlow,
1254 : OutputProcessor::TimeStepType::System,
1255 : OutputProcessor::StoreType::Average,
1256 0 : thisPlantLoop.Name);
1257 0 : SetupOutputVariable(state,
1258 : "Plant Common Pipe Secondary Mass Flow Rate",
1259 : Constant::Units::kg_s,
1260 0 : thisCommonPipe.SecCPLegFlow,
1261 : OutputProcessor::TimeStepType::System,
1262 : OutputProcessor::StoreType::Average,
1263 0 : thisPlantLoop.Name);
1264 0 : SetupOutputVariable(state,
1265 : "Plant Common Pipe Primary to Secondary Mass Flow Rate",
1266 : Constant::Units::kg_s,
1267 0 : thisCommonPipe.PriToSecFlow,
1268 : OutputProcessor::TimeStepType::System,
1269 : OutputProcessor::StoreType::Average,
1270 0 : thisPlantLoop.Name);
1271 0 : SetupOutputVariable(state,
1272 : "Plant Common Pipe Secondary to Primary Mass Flow Rate",
1273 : Constant::Units::kg_s,
1274 0 : thisCommonPipe.SecToPriFlow,
1275 : OutputProcessor::TimeStepType::System,
1276 : OutputProcessor::StoreType::Average,
1277 0 : thisPlantLoop.Name);
1278 :
1279 : // check type of pump on supply side inlet
1280 0 : if (first_supply_component_type == DataPlant::PlantEquipmentType::PumpConstantSpeed) {
1281 0 : thisCommonPipe.SupplySideInletPumpType = FlowType::Constant;
1282 0 : } else if (first_supply_component_type == DataPlant::PlantEquipmentType::PumpVariableSpeed) {
1283 0 : thisCommonPipe.SupplySideInletPumpType = FlowType::Variable;
1284 : // If/when the model supports variable-pumping primary, this can be removed.
1285 0 : ShowWarningError(state, "SetupCommonPipes: detected variable speed pump on supply inlet of TwoWayCommonPipe plant loop");
1286 0 : ShowContinueError(state, format("Occurs on plant loop name = {}", thisPlantLoop.Name));
1287 0 : ShowContinueError(state, "The common pipe model does not support varying the flow rate on the primary/supply side");
1288 0 : ShowContinueError(state, "The primary/supply side will operate as if constant speed, and the simulation continues");
1289 : }
1290 : // check type of pump on demand side inlet
1291 0 : if (first_demand_component_type == DataPlant::PlantEquipmentType::PumpConstantSpeed) {
1292 0 : thisCommonPipe.DemandSideInletPumpType = FlowType::Constant;
1293 0 : } else if (first_demand_component_type == DataPlant::PlantEquipmentType::PumpVariableSpeed) {
1294 0 : thisCommonPipe.DemandSideInletPumpType = FlowType::Variable;
1295 : }
1296 0 : break;
1297 0 : default:
1298 0 : assert(false);
1299 : }
1300 : }
1301 :
1302 0 : state.dataHVACInterfaceMgr->CommonPipeSetupFinished = true;
1303 0 : }
1304 :
1305 : } // namespace EnergyPlus::HVACInterfaceManager
|