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 : #include <string>
51 :
52 : // ObjexxFCL Headers
53 : #include <ObjexxFCL/Array.functions.hh>
54 : #include <ObjexxFCL/Array1D.hh>
55 : #include <ObjexxFCL/Array2D.hh>
56 : #include <ObjexxFCL/Fmath.hh>
57 : #include <ObjexxFCL/member.functions.hh>
58 :
59 : // EnergyPlus Headers
60 : #include <EnergyPlus/BaseboardRadiator.hh>
61 : #include <EnergyPlus/Construction.hh>
62 : #include <EnergyPlus/ConvectionCoefficients.hh>
63 : #include <EnergyPlus/Data/EnergyPlusData.hh>
64 : #include <EnergyPlus/DataAirLoop.hh>
65 : #include <EnergyPlus/DataBranchAirLoopPlant.hh>
66 : #include <EnergyPlus/DataEnvironment.hh>
67 : #include <EnergyPlus/DataHVACGlobals.hh>
68 : #include <EnergyPlus/DataHeatBalSurface.hh>
69 : #include <EnergyPlus/DataHeatBalance.hh>
70 : #include <EnergyPlus/DataLoopNode.hh>
71 : #include <EnergyPlus/DataSizing.hh>
72 : #include <EnergyPlus/DataSurfaces.hh>
73 : #include <EnergyPlus/DataZoneEquipment.hh>
74 : #include <EnergyPlus/ExhaustAirSystemManager.hh>
75 : #include <EnergyPlus/FanCoilUnits.hh>
76 : #include <EnergyPlus/GeneralRoutines.hh>
77 : #include <EnergyPlus/HVACSingleDuctInduc.hh>
78 : #include <EnergyPlus/HWBaseboardRadiator.hh>
79 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
80 : #include <EnergyPlus/Material.hh>
81 : #include <EnergyPlus/MixerComponent.hh>
82 : #include <EnergyPlus/OutdoorAirUnit.hh>
83 : #include <EnergyPlus/PlantUtilities.hh>
84 : #include <EnergyPlus/PoweredInductionUnits.hh>
85 : #include <EnergyPlus/Psychrometrics.hh>
86 : #include <EnergyPlus/PurchasedAirManager.hh>
87 : #include <EnergyPlus/ScheduleManager.hh>
88 : #include <EnergyPlus/SolarCollectors.hh>
89 : #include <EnergyPlus/SplitterComponent.hh>
90 : #include <EnergyPlus/SteamBaseboardRadiator.hh>
91 : #include <EnergyPlus/UnitHeater.hh>
92 : #include <EnergyPlus/UnitVentilator.hh>
93 : #include <EnergyPlus/UtilityRoutines.hh>
94 : #include <EnergyPlus/VentilatedSlab.hh>
95 : #include <EnergyPlus/WaterCoils.hh>
96 : #include <EnergyPlus/ZonePlenum.hh>
97 :
98 : namespace EnergyPlus {
99 :
100 : // Integer constants for different system types handled by the routines in this file
101 : enum GeneralRoutinesEquipNums
102 : {
103 : ParallelPIUReheatNum = 1,
104 : SeriesPIUReheatNum = 2,
105 : HeatingCoilWaterNum = 3,
106 : BBWaterConvOnlyNum = 4,
107 : BBSteamRadConvNum = 5,
108 : BBWaterRadConvNum = 6,
109 : FourPipeFanCoilNum = 7,
110 : OutdoorAirUnitNum = 8,
111 : UnitHeaterNum = 9,
112 : UnitVentilatorNum = 10,
113 : VentilatedSlabNum = 11
114 : };
115 :
116 : enum class AirLoopHVACCompType
117 : {
118 : Invalid = -1,
119 : SupplyPlenum,
120 : ZoneSplitter,
121 : ZoneMixer,
122 : ReturnPlenum,
123 : Num
124 : };
125 :
126 : constexpr std::array<std::string_view, static_cast<int>(AirLoopHVACCompType::Num)> AirLoopHVACCompTypeNamesUC{
127 : "AIRLOOPHVAC:SUPPLYPLENUM", "AIRLOOPHVAC:ZONESPLITTER", "AIRLOOPHVAC:ZONEMIXER", "AIRLOOPHVAC:RETURNPLENUM"};
128 :
129 12136644 : void ControlCompOutput(EnergyPlusData &state,
130 : std::string const &CompName, // the component Name
131 : std::string const &CompType, // Type of component
132 : int &CompNum, // Index of component in component array
133 : bool const FirstHVACIteration, // flag for 1st HVAV iteration in the time step
134 : Real64 const QZnReq, // zone load to be met
135 : int const ActuatedNode, // node that controls unit output
136 : Real64 const MaxFlow, // maximum water flow
137 : Real64 const MinFlow, // minimum water flow
138 : Real64 const ControlOffset, // really the tolerance
139 : int &ControlCompTypeNum, // Internal type num for CompType
140 : int &CompErrIndex, // for Recurring error call
141 : ObjexxFCL::Optional_int_const TempInNode, // inlet node for output calculation
142 : ObjexxFCL::Optional_int_const TempOutNode, // outlet node for output calculation
143 : ObjexxFCL::Optional<Real64 const> AirMassFlow, // air mass flow rate
144 : ObjexxFCL::Optional_int_const Action, // 1=reverse; 2=normal
145 : ObjexxFCL::Optional_int_const EquipIndex, // Identifier for equipment of Outdoor Air Unit "ONLY"
146 : PlantLocation const &plantLoc, // for plant components, Location
147 : ObjexxFCL::Optional_int_const ControlledZoneIndex // controlled zone index for the zone containing the component
148 : )
149 : {
150 :
151 : // SUBROUTINE INFORMATION:
152 : // AUTHOR Richard J. Liesen
153 : // DATE WRITTEN April 2000
154 : // MODIFIED Brent Griffith, Sept 2010 update plant interactions
155 :
156 : // PURPOSE OF THIS SUBROUTINE:
157 : // The purpose of this subroutine is to control the output of heating or cooling
158 : // meet the zone load.
159 :
160 : // METHODOLOGY EMPLOYED:
161 : // Currently this is using an interval halving scheme to a control tolerance
162 :
163 : // SUBROUTINE PARAMETER DEFINITIONS:
164 : // Iteration maximum for reheat control
165 : static int constexpr MaxIter = 25;
166 : static Real64 const iter_fac = 1.0 / std::pow(2, MaxIter - 3);
167 12136644 : int constexpr iReverseAction = 1;
168 12136644 : int constexpr iNormalAction = 2;
169 :
170 : // Note - order in routine must match order below
171 : // Plus -- order in ListOfComponents array must be in sorted order.
172 12136644 : int constexpr NumComponents = 11;
173 : static Array1D_string const ListOfComponents(NumComponents,
174 : {"AIRTERMINAL:SINGLEDUCT:PARALLELPIU:REHEAT",
175 : "AIRTERMINAL:SINGLEDUCT:SERIESPIU:REHEAT",
176 : "COIL:HEATING:WATER",
177 : "ZONEHVAC:BASEBOARD:CONVECTIVE:WATER",
178 : "ZONEHVAC:BASEBOARD:RADIANTCONVECTIVE:STEAM",
179 : "ZONEHVAC:BASEBOARD:RADIANTCONVECTIVE:WATER",
180 : "ZONEHVAC:FOURPIPEFANCOIL",
181 : "ZONEHVAC:OUTDOORAIRUNIT",
182 : "ZONEHVAC:UNITHEATER",
183 : "ZONEHVAC:UNITVENTILATOR",
184 12136644 : "ZONEHVAC:VENTILATEDSLAB"});
185 :
186 : // DERIVED TYPE DEFINITIONS
187 : // Interval Half Type used for Controller
188 :
189 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
190 : int SimCompNum; // internal number for case statement
191 :
192 : // Object Data
193 12136644 : auto &ZoneInterHalf = state.dataGeneralRoutines->ZoneInterHalf;
194 12136644 : auto &ZoneController = state.dataGeneralRoutines->ZoneController;
195 :
196 12136644 : if (ControlCompTypeNum != 0) {
197 12134396 : SimCompNum = ControlCompTypeNum;
198 : } else {
199 2248 : SimCompNum = Util::FindItemInSortedList(CompType, ListOfComponents, NumComponents);
200 2248 : ControlCompTypeNum = SimCompNum;
201 : }
202 :
203 12136644 : int Iter = 0; // Iteration limit for the interval halving process
204 12136644 : bool Converged = false;
205 12136644 : bool WaterCoilAirFlowControl = false; // True if controlling air flow through water coil, water flow fixed
206 12136644 : Real64 LoadMet = 0.0; // Actual output of unit (watts)
207 12136644 : Real64 HalvingPrec = 0.0; // precision of halving algorithm
208 : Real64 CpAir;
209 :
210 : // At the beginning of every time step the value is reset to the User Input
211 12136644 : ZoneController.SetPoint = 0.0;
212 :
213 : // Set to converged controller
214 12136644 : ZoneInterHalf.MaxFlowCalc = true;
215 12136644 : ZoneInterHalf.MinFlowCalc = false;
216 12136644 : ZoneInterHalf.NormFlowCalc = false;
217 12136644 : ZoneInterHalf.MinFlowResult = false;
218 12136644 : ZoneInterHalf.MaxResult = 1.0;
219 12136644 : ZoneInterHalf.MinResult = 0.0;
220 :
221 : // Start the Solution Iteration
222 88842451 : while (!Converged) {
223 :
224 84342952 : if (FirstHVACIteration) {
225 34825681 : state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMaxAvail = MaxFlow;
226 34825681 : state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMinAvail = MinFlow;
227 : // Check to make sure that the Minimum Flow rate is less than the max.
228 34825681 : if (MinFlow > MaxFlow) {
229 0 : ShowSevereError(state, format("ControlCompOutput:{}:{}, Min Control Flow is > Max Control Flow", CompType, CompName));
230 0 : ShowContinueError(
231 0 : state, format("Acuated Node={} MinFlow=[{:.3T}], Max Flow={:.3T}", state.dataLoopNodes->NodeID(ActuatedNode), MinFlow, MaxFlow));
232 0 : ShowContinueErrorTimeStamp(state, "");
233 0 : ShowFatalError(state, "Program terminates due to preceding condition.");
234 : }
235 : } // End of FirstHVACIteration Conditional If
236 : // The interface managers can reset the Max or Min to available values during the time step
237 : // and these will then be the new setpoint limits for the controller to work within.
238 84342952 : if ((SimCompNum == 3) && (!present(AirMassFlow))) {
239 6909146 : ZoneController.MaxSetPoint = state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMaxAvail;
240 6909146 : ZoneController.MinSetPoint = state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMinAvail;
241 : } else {
242 77433806 : ZoneController.MaxSetPoint =
243 77433806 : min(state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMaxAvail, state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMax);
244 77433806 : ZoneController.MinSetPoint =
245 77433806 : max(state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMinAvail, state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMin);
246 : }
247 : // The first time through run at maximum flow rate and find results
248 84342952 : if (ZoneInterHalf.MaxFlowCalc) {
249 12136644 : ZoneController.CalculatedSetPoint = ZoneController.MaxSetPoint;
250 12136644 : ZoneInterHalf.MaxFlow = ZoneController.MaxSetPoint;
251 12136644 : ZoneInterHalf.MaxFlowCalc = false;
252 12136644 : ZoneInterHalf.MinFlowCalc = true;
253 : // Record the maximum flow rates and set the flow to the minimum and find results
254 72206308 : } else if (ZoneInterHalf.MinFlowCalc) {
255 12030012 : ZoneInterHalf.MaxResult = ZoneController.SensedValue;
256 12030012 : ZoneController.CalculatedSetPoint = ZoneController.MinSetPoint;
257 12030012 : ZoneInterHalf.MinFlow = ZoneController.MinSetPoint;
258 12030012 : ZoneInterHalf.MinFlowCalc = false;
259 12030012 : ZoneInterHalf.MinFlowResult = true;
260 : // Record the minimum results and set flow to half way between the max and min and find results
261 60176296 : } else if (ZoneInterHalf.MinFlowResult) {
262 11977175 : ZoneInterHalf.MinResult = ZoneController.SensedValue;
263 11977175 : HalvingPrec = (ZoneInterHalf.MaxResult - ZoneInterHalf.MinResult) * iter_fac;
264 11977175 : ZoneInterHalf.MidFlow = (ZoneInterHalf.MaxFlow + ZoneInterHalf.MinFlow) / 2.0;
265 11977175 : ZoneController.CalculatedSetPoint = (ZoneInterHalf.MaxFlow + ZoneInterHalf.MinFlow) / 2.0;
266 11977175 : ZoneInterHalf.MinFlowResult = false;
267 11977175 : ZoneInterHalf.NormFlowCalc = true;
268 : // Record the Mid results and check all possibilities and start interval halving procedure
269 48199121 : } else if (ZoneInterHalf.NormFlowCalc) {
270 48199121 : ZoneInterHalf.MidResult = ZoneController.SensedValue;
271 :
272 : // First check to see if the component is running; if not converge and return
273 48199121 : if (ZoneInterHalf.MaxResult == ZoneInterHalf.MinResult) {
274 : // Set to converged controller
275 1359586 : ZoneInterHalf.MaxFlowCalc = true;
276 1359586 : ZoneInterHalf.MinFlowCalc = false;
277 1359586 : ZoneInterHalf.NormFlowCalc = false;
278 1359586 : ZoneInterHalf.MinFlowResult = false;
279 1359586 : ZoneInterHalf.MaxResult = 1.0;
280 1359586 : ZoneInterHalf.MinResult = 0.0;
281 1359586 : if ((SimCompNum >= 4) && (SimCompNum <= 6)) { // hot water baseboards use min flow
282 18719 : ZoneController.CalculatedSetPoint = 0.0; // CR7253
283 : } else {
284 1340867 : ZoneController.CalculatedSetPoint = ZoneInterHalf.MaxFlow; // CR7253
285 : }
286 : // Set the Actuated node MassFlowRate with zero value
287 1359586 : if (plantLoc.loopNum) { // this is a plant component
288 527263 : PlantUtilities::SetActuatedBranchFlowRate(state,
289 527263 : ZoneController.CalculatedSetPoint,
290 : ActuatedNode,
291 : plantLoc,
292 : false); // Autodesk:OPTIONAL LoopSide, BranchIndex used without PRESENT check
293 : } else { // assume not a plant component
294 832323 : state.dataLoopNodes->Node(ActuatedNode).MassFlowRate = ZoneController.CalculatedSetPoint;
295 : }
296 1359586 : return;
297 : }
298 :
299 : // The next series of checks is to determine what interval the current solution is in
300 : // comparison to the setpoint and then respond appropriately.
301 :
302 : // Normal controller assumes that MaxResult will be greater than MinResult. First check
303 : // to make sure that this is the case
304 46839535 : if (ZoneInterHalf.MaxResult <= ZoneInterHalf.MinResult) {
305 161080 : if (WaterCoilAirFlowControl) {
306 161037 : ZoneController.CalculatedSetPoint = ZoneInterHalf.MaxFlow;
307 : } else {
308 43 : ZoneController.CalculatedSetPoint = ZoneInterHalf.MinFlow;
309 : }
310 : // set to converged controller
311 161080 : Converged = true;
312 161080 : ZoneInterHalf.MaxFlowCalc = true;
313 161080 : ZoneInterHalf.MinFlowCalc = false;
314 161080 : ZoneInterHalf.NormFlowCalc = false;
315 161080 : ZoneInterHalf.MinFlowResult = false;
316 161080 : ZoneInterHalf.MaxResult = 1.0;
317 161080 : ZoneInterHalf.MinResult = 0.0;
318 : // MaxResult is greater than MinResult so simulation control algorithm may proceed normally
319 : } else {
320 : // Now check to see if the setpoint is outside the endpoints of the control range
321 : // First check to see if the water is too cold and if so set to the minimum flow.
322 46678455 : if (ZoneController.SetPoint <= ZoneInterHalf.MinResult) {
323 7841 : ZoneController.CalculatedSetPoint = ZoneInterHalf.MinFlow;
324 : // Set to Converged Controller
325 7841 : Converged = true;
326 7841 : ZoneInterHalf.MaxFlowCalc = true;
327 7841 : ZoneInterHalf.MinFlowCalc = false;
328 7841 : ZoneInterHalf.NormFlowCalc = false;
329 7841 : ZoneInterHalf.MinFlowResult = false;
330 7841 : ZoneInterHalf.MaxResult = 1.0;
331 7841 : ZoneInterHalf.MinResult = 0.0;
332 : // Then check if too hot and if so set it to the maximum flow
333 46670614 : } else if (ZoneController.SetPoint >= ZoneInterHalf.MaxResult) {
334 4330561 : ZoneController.CalculatedSetPoint = ZoneInterHalf.MaxFlow;
335 : // Set to Converged Controller
336 4330561 : Converged = true;
337 4330561 : ZoneInterHalf.MaxFlowCalc = true;
338 4330561 : ZoneInterHalf.MinFlowCalc = false;
339 4330561 : ZoneInterHalf.NormFlowCalc = false;
340 4330561 : ZoneInterHalf.MinFlowResult = false;
341 4330561 : ZoneInterHalf.MaxResult = 1.0;
342 4330561 : ZoneInterHalf.MinResult = 0.0;
343 : // If between the max and mid set to new flow and raise min to mid
344 42340053 : } else if (ZoneController.SetPoint >= ZoneInterHalf.MidResult) {
345 18233689 : ZoneController.CalculatedSetPoint = (ZoneInterHalf.MaxFlow + ZoneInterHalf.MidFlow) / 2.0;
346 18233689 : ZoneInterHalf.MinFlow = ZoneInterHalf.MidFlow;
347 18233689 : ZoneInterHalf.MinResult = ZoneInterHalf.MidResult;
348 18233689 : ZoneInterHalf.MidFlow = (ZoneInterHalf.MaxFlow + ZoneInterHalf.MidFlow) / 2.0;
349 : // If between the min and mid set to new flow and lower Max to mid
350 : } else {
351 24106364 : ZoneController.CalculatedSetPoint = (ZoneInterHalf.MinFlow + ZoneInterHalf.MidFlow) / 2.0;
352 24106364 : ZoneInterHalf.MaxFlow = ZoneInterHalf.MidFlow;
353 24106364 : ZoneInterHalf.MaxResult = ZoneInterHalf.MidResult;
354 24106364 : ZoneInterHalf.MidFlow = (ZoneInterHalf.MinFlow + ZoneInterHalf.MidFlow) / 2.0;
355 :
356 : } // End of the Conditional for the actual interval halving scheme itself
357 : } // end of max > min check
358 :
359 : } // End of the Conditinal for the first 3 iterations for the interval halving
360 :
361 : // Make sure that the Calculated setpoint falls between the minimum and maximum allowed
362 82983366 : if (ZoneController.CalculatedSetPoint > ZoneController.MaxSetPoint) {
363 0 : ZoneController.CalculatedSetPoint = ZoneController.MaxSetPoint;
364 0 : Converged = true;
365 0 : ZoneInterHalf.MaxFlowCalc = true;
366 0 : ZoneInterHalf.MinFlowCalc = false;
367 0 : ZoneInterHalf.NormFlowCalc = false;
368 0 : ZoneInterHalf.MinFlowResult = false;
369 0 : ZoneInterHalf.MaxResult = 1.0;
370 0 : ZoneInterHalf.MinResult = 0.0;
371 82983366 : } else if (ZoneController.CalculatedSetPoint < ZoneController.MinSetPoint) {
372 17 : ZoneController.CalculatedSetPoint = ZoneController.MinSetPoint;
373 17 : Converged = true;
374 17 : ZoneInterHalf.MaxFlowCalc = true;
375 17 : ZoneInterHalf.MinFlowCalc = false;
376 17 : ZoneInterHalf.NormFlowCalc = false;
377 17 : ZoneInterHalf.MinFlowResult = false;
378 17 : ZoneInterHalf.MaxResult = 1.0;
379 17 : ZoneInterHalf.MinResult = 0.0;
380 : }
381 :
382 : // check if hunting down around the limit of a significant mass flow in systems.
383 82983366 : if ((Iter > MaxIter / 2) && (ZoneController.CalculatedSetPoint < DataBranchAirLoopPlant::MassFlowTolerance)) {
384 0 : ZoneController.CalculatedSetPoint = ZoneController.MinSetPoint;
385 0 : Converged = true;
386 0 : ZoneInterHalf.MaxFlowCalc = true;
387 0 : ZoneInterHalf.MinFlowCalc = false;
388 0 : ZoneInterHalf.NormFlowCalc = false;
389 0 : ZoneInterHalf.MinFlowResult = false;
390 0 : ZoneInterHalf.MaxResult = 1.0;
391 0 : ZoneInterHalf.MinResult = 0.0;
392 : }
393 :
394 : // Set the Actuated node MassFlowRate with the new value
395 82983366 : if (plantLoc.loopNum) { // this is a plant component
396 76906543 : PlantUtilities::SetActuatedBranchFlowRate(state,
397 76906543 : ZoneController.CalculatedSetPoint,
398 : ActuatedNode,
399 : plantLoc,
400 : false); // Autodesk:OPTIONAL LoopSide, BranchIndex used without PRESENT check
401 : } else { // assume not a plant component, leave alone
402 6076823 : state.dataLoopNodes->Node(ActuatedNode).MassFlowRate = ZoneController.CalculatedSetPoint;
403 : }
404 :
405 : // The denominator of the control signal should be no less than 100 watts
406 82983366 : Real64 Denom = sign(max(std::abs(QZnReq), 100.0), QZnReq);
407 82983366 : if (present(Action)) {
408 753148 : if (Action == iNormalAction) {
409 530739 : Denom = max(std::abs(QZnReq), 100.0);
410 222409 : } else if (Action == iReverseAction) {
411 222409 : Denom = -max(std::abs(QZnReq), 100.0);
412 : } else {
413 0 : ShowFatalError(state, format("ControlCompOutput: Illegal Action argument =[{}]", Action));
414 : }
415 : }
416 :
417 82983366 : switch (SimCompNum) { // Tuned If block changed to switch
418 158624 : case ParallelPIUReheatNum: // 'AIRTERMINAL:SINGLEDUCT:PARALLELPIU:REHEAT'
419 : // simulate series piu reheat coil
420 158624 : WaterCoils::SimulateWaterCoilComponents(state, CompName, FirstHVACIteration, CompNum);
421 : // Calculate the control signal (the variable we are forcing to zero)
422 158624 : CpAir = Psychrometrics::PsyCpAirFnW(
423 158624 : state.dataLoopNodes->Node(TempOutNode).HumRat); // Autodesk:OPTIONAL TempInNode, TempOutNode used without PRESENT check
424 158624 : LoadMet = CpAir * state.dataLoopNodes->Node(TempOutNode).MassFlowRate *
425 158624 : (state.dataLoopNodes->Node(TempOutNode).Temp -
426 158624 : state.dataLoopNodes->Node(TempInNode).Temp); // Autodesk:OPTIONAL TempInNode, TempOutNode used without PRESENT check
427 158624 : ZoneController.SensedValue = (LoadMet - QZnReq) / Denom;
428 158624 : break;
429 :
430 956174 : case SeriesPIUReheatNum: // 'AIRTERMINAL:SINGLEDUCT:SERIESPIU:REHEAT'
431 : // simulate series piu reheat coil
432 956174 : WaterCoils::SimulateWaterCoilComponents(state, CompName, FirstHVACIteration, CompNum);
433 : // Calculate the control signal (the variable we are forcing to zero)
434 956174 : CpAir = Psychrometrics::PsyCpAirFnW(
435 956174 : state.dataLoopNodes->Node(TempOutNode).HumRat); // Autodesk:OPTIONAL TempInNode, TempOutNode used without PRESENT check
436 956174 : LoadMet = CpAir * state.dataLoopNodes->Node(TempOutNode).MassFlowRate *
437 956174 : (state.dataLoopNodes->Node(TempOutNode).Temp -
438 956174 : state.dataLoopNodes->Node(TempInNode).Temp); // Autodesk:OPTIONAL TempInNode, TempOutNode used without PRESENT check
439 956174 : ZoneController.SensedValue = (LoadMet - QZnReq) / Denom;
440 956174 : break;
441 :
442 79263522 : case HeatingCoilWaterNum: // 'COIL:HEATING:WATER'
443 : // Simulate reheat coil for the VAV system
444 79263522 : WaterCoils::SimulateWaterCoilComponents(state, CompName, FirstHVACIteration, CompNum);
445 : // Calculate the control signal (the variable we are forcing to zero)
446 79263522 : CpAir = Psychrometrics::PsyCpAirFnW(state.dataLoopNodes->Node(TempOutNode).HumRat);
447 79263522 : if (present(AirMassFlow)) {
448 73186699 : LoadMet = AirMassFlow * CpAir * state.dataLoopNodes->Node(TempOutNode).Temp;
449 73186699 : ZoneController.SensedValue = (LoadMet - QZnReq) / Denom;
450 : } else {
451 6076823 : WaterCoilAirFlowControl = true;
452 6076823 : LoadMet = state.dataLoopNodes->Node(TempOutNode).MassFlowRate * CpAir *
453 6076823 : (state.dataLoopNodes->Node(TempOutNode).Temp -
454 6076823 : state.dataLoopNodes->Node(TempInNode).Temp); // Autodesk:OPTIONAL TempInNode, TempOutNode used without PRESENT check
455 6076823 : ZoneController.SensedValue = (LoadMet - QZnReq) / Denom;
456 : }
457 79263522 : break;
458 :
459 731260 : case BBWaterConvOnlyNum: // 'ZONEHVAC:BASEBOARD:CONVECTIVE:WATER'
460 : // Simulate baseboard
461 731260 : BaseboardRadiator::SimHWConvective(state, CompNum, LoadMet);
462 : // Calculate the control signal (the variable we are forcing to zero)
463 731260 : ZoneController.SensedValue = (LoadMet - QZnReq) / Denom;
464 731260 : break;
465 :
466 27961 : case BBSteamRadConvNum: // 'ZONEHVAC:BASEBOARD:RADIANTCONVECTIVE:STEAM'
467 : // Simulate baseboard
468 27961 : SteamBaseboardRadiator::CalcSteamBaseboard(state, CompNum, LoadMet);
469 : // Calculate the control signal (the variable we are forcing to zero)
470 27961 : ZoneController.SensedValue = (LoadMet - QZnReq) / Denom;
471 27961 : break;
472 :
473 159997 : case BBWaterRadConvNum: // 'ZONEHVAC:BASEBOARD:RADIANTCONVECTIVE:WATER'
474 : // Simulate baseboard
475 159997 : HWBaseboardRadiator::CalcHWBaseboard(state, CompNum, LoadMet);
476 : // Calculate the control signal (the variable we are forcing to zero)
477 159997 : ZoneController.SensedValue = (LoadMet - QZnReq) / Denom;
478 159997 : break;
479 :
480 0 : case FourPipeFanCoilNum: // 'ZONEHVAC:FOURPIPEFANCOIL'
481 : // Simulate fancoil unit
482 0 : FanCoilUnits::Calc4PipeFanCoil(state, CompNum, ControlledZoneIndex, FirstHVACIteration, LoadMet);
483 : // Calculate the control signal (the variable we are forcing to zero)
484 0 : ZoneController.SensedValue = (LoadMet - QZnReq) / Denom;
485 0 : break;
486 :
487 753148 : case OutdoorAirUnitNum: //'ZONEHVAC:OUTDOORAIRUNIT'
488 : // Simulate outdoor air unit components
489 753148 : OutdoorAirUnit::CalcOAUnitCoilComps(
490 : state, CompNum, FirstHVACIteration, EquipIndex, LoadMet); // Autodesk:OPTIONAL EquipIndex used without PRESENT check
491 : // Calculate the control signal (the variable we are forcing to zero)
492 753148 : ZoneController.SensedValue = (LoadMet - QZnReq) / Denom;
493 753148 : break;
494 :
495 162775 : case UnitHeaterNum: // 'ZONEHVAC:UNITHEATER'
496 : // Simulate unit heater components
497 162775 : UnitHeater::CalcUnitHeaterComponents(state, CompNum, FirstHVACIteration, LoadMet);
498 : // Calculate the control signal (the variable we are forcing to zero)
499 162775 : ZoneController.SensedValue = (LoadMet - QZnReq) / Denom;
500 162775 : break;
501 :
502 342677 : case UnitVentilatorNum: // 'ZONEHVAC:UNITVENTILATOR'
503 : // Simulate unit ventilator components
504 342677 : UnitVentilator::CalcUnitVentilatorComponents(state, CompNum, FirstHVACIteration, LoadMet);
505 : // Calculate the control signal (the variable we are forcing to zero)
506 342677 : ZoneController.SensedValue = (LoadMet - QZnReq) / Denom;
507 342677 : break;
508 :
509 427228 : case VentilatedSlabNum: // 'ZONEHVAC:VENTILATEDSLAB'
510 : // Simulate unit ventilator components
511 427228 : VentilatedSlab::CalcVentilatedSlabComps(state, CompNum, FirstHVACIteration, LoadMet);
512 : // Calculate the control signal (the variable we are forcing to zero)
513 427228 : ZoneController.SensedValue = (LoadMet - QZnReq) / Denom;
514 427228 : break;
515 :
516 0 : default:
517 0 : ShowFatalError(state, format("ControlCompOutput: Illegal Component Number argument =[{}]", SimCompNum));
518 0 : break;
519 : }
520 :
521 : // Check for Controller convergence to see if within the offset
522 82983366 : if (std::abs(ZoneController.SensedValue) <= ControlOffset || std::abs(ZoneController.SensedValue) <= HalvingPrec) {
523 : // Set to converged controller
524 6211546 : ZoneInterHalf.MaxFlowCalc = true;
525 6211546 : ZoneInterHalf.MinFlowCalc = false;
526 6211546 : ZoneInterHalf.NormFlowCalc = false;
527 6211546 : ZoneInterHalf.MinFlowResult = false;
528 6211546 : ZoneInterHalf.MaxResult = 1.0;
529 6211546 : ZoneInterHalf.MinResult = 0.0;
530 6211546 : break;
531 : }
532 76771820 : if (!Converged) {
533 72272321 : bool BBConvergeCheckFlag = BBConvergeCheck(SimCompNum, ZoneInterHalf.MaxFlow, ZoneInterHalf.MinFlow);
534 72272321 : if (BBConvergeCheckFlag) {
535 : // Set to converged controller
536 66013 : ZoneInterHalf.MaxFlowCalc = true;
537 66013 : ZoneInterHalf.MinFlowCalc = false;
538 66013 : ZoneInterHalf.NormFlowCalc = false;
539 66013 : ZoneInterHalf.MinFlowResult = false;
540 66013 : ZoneInterHalf.MaxResult = 1.0;
541 66013 : ZoneInterHalf.MinResult = 0.0;
542 66013 : break;
543 : }
544 : }
545 :
546 76705807 : ++Iter;
547 76705807 : if ((Iter > MaxIter) && (!state.dataGlobal->WarmupFlag)) {
548 : // if ( CompErrIndex == 0 ) {
549 0 : ShowWarningMessage(state, format("ControlCompOutput: Maximum iterations exceeded for {} = {}", CompType, CompName));
550 0 : ShowContinueError(state, format("... Load met = {:.5T} W.", LoadMet));
551 0 : ShowContinueError(state, format("... Load requested = {:.5T} W.", QZnReq));
552 0 : ShowContinueError(state, format("... Error = {:.8T} %.", std::abs((LoadMet - QZnReq) * 100.0 / Denom)));
553 0 : ShowContinueError(state, format("... Tolerance = {:.8T} %.", ControlOffset * 100.0));
554 0 : ShowContinueError(state, "... Error = (Load met - Load requested) / MAXIMUM(Load requested, 100)");
555 0 : ShowContinueError(state, format("... Actuated Node Mass Flow Rate ={:.9R} kg/s", state.dataLoopNodes->Node(ActuatedNode).MassFlowRate));
556 0 : ShowContinueErrorTimeStamp(state, "");
557 0 : ShowRecurringWarningErrorAtEnd(state,
558 0 : "ControlCompOutput: Maximum iterations error for " + CompType + " = " + CompName,
559 : CompErrIndex,
560 0 : std::abs((LoadMet - QZnReq) * 100.0 / Denom),
561 0 : std::abs((LoadMet - QZnReq) * 100.0 / Denom),
562 : _,
563 : "%",
564 : "%");
565 : //}
566 0 : ShowRecurringWarningErrorAtEnd(state,
567 0 : "ControlCompOutput: Maximum iterations error for " + CompType + " = " + CompName,
568 : CompErrIndex,
569 0 : std::abs((LoadMet - QZnReq) * 100.0 / Denom),
570 0 : std::abs((LoadMet - QZnReq) * 100.0 / Denom),
571 : _,
572 : "%",
573 : "%");
574 0 : break; // It will not converge this time
575 76705807 : } else if (Iter > MaxIter * 2) {
576 0 : break;
577 : }
578 :
579 : } // End of the Convergence Iteration
580 : }
581 :
582 72272321 : bool BBConvergeCheck(int const SimCompNum, Real64 const MaxFlow, Real64 const MinFlow)
583 : {
584 :
585 : // FUNCTION INFORMATION:
586 : // AUTHOR Rick Strand
587 : // DATE WRITTEN November 2017
588 :
589 : // PURPOSE OF THIS SUBROUTINE:
590 : // This is an additional check for the radiant/convective baseboard units
591 : // to see if they are converged or the flow is sufficiently converged to
592 : // procede with the simulation. With the radiant component to these systems,
593 : // the impact on the load met is more difficult to calculate and the impact
594 : // on the actual system output is not as well behaved as for convective
595 : // systems. This additional check avoids excessive iterations and max
596 : // iteration warnings and provides sufficiently converged results. It is
597 : // only called from ControlCompOutput.
598 :
599 : // Return Value
600 : bool BBConvergeCheck;
601 :
602 : // SUBROUTINE PARAMETER DEFINITIONS:
603 : static Real64 constexpr BBIterLimit = 0.00001;
604 :
605 72272321 : if (SimCompNum != BBSteamRadConvNum && SimCompNum != BBWaterRadConvNum) {
606 : // For all zone equipment except radiant/convective baseboard (steam and water) units:
607 72092720 : BBConvergeCheck = false;
608 : } else {
609 : // For steam and water radiant/convective baseboard units:
610 179601 : if ((MaxFlow - MinFlow) > BBIterLimit) {
611 113588 : BBConvergeCheck = false;
612 : } else {
613 66013 : BBConvergeCheck = true;
614 : }
615 : }
616 :
617 72272321 : return BBConvergeCheck;
618 : }
619 :
620 6962 : void CheckSysSizing(EnergyPlusData &state,
621 : std::string_view const CompType, // Component Type (e.g. Chiller:Electric)
622 : std::string const &CompName // Component Name (e.g. Big Chiller)
623 : )
624 : {
625 :
626 : // SUBROUTINE INFORMATION:
627 : // AUTHOR Fred Buhl
628 : // DATE WRITTEN October 2002
629 :
630 : // PURPOSE OF THIS SUBROUTINE:
631 : // This routine is called when an "autosize" input is encountered in a component
632 : // sizing routine to check that the system sizing calculations have been done.
633 :
634 : // METHODOLOGY EMPLOYED:
635 : // Checks SysSizingRunDone flag. If false throws a fatal error.
636 :
637 6962 : if (!state.dataSize->SysSizingRunDone) {
638 0 : ShowSevereError(state, format("For autosizing of {} {}, a system sizing run must be done.", CompType, CompName));
639 0 : if (state.dataSize->NumSysSizInput == 0) {
640 0 : ShowContinueError(state, "No \"Sizing:System\" objects were entered.");
641 : }
642 0 : if (!state.dataGlobal->DoSystemSizing) {
643 0 : ShowContinueError(state, R"(The "SimulationControl" object did not have the field "Do System Sizing Calculation" set to Yes.)");
644 : }
645 0 : ShowFatalError(state, "Program terminates due to previously shown condition(s).");
646 : }
647 6962 : }
648 :
649 5212 : void CheckThisAirSystemForSizing(EnergyPlusData &state, int const AirLoopNum, bool &AirLoopWasSized)
650 : {
651 :
652 : // SUBROUTINE INFORMATION:
653 : // AUTHOR B. Griffith
654 : // DATE WRITTEN October 2013
655 :
656 5212 : AirLoopWasSized = false;
657 5212 : if (state.dataSize->SysSizingRunDone) {
658 25889 : for (int ThisAirSysSizineInputLoop = 1; ThisAirSysSizineInputLoop <= state.dataSize->NumSysSizInput; ++ThisAirSysSizineInputLoop) {
659 25881 : if (state.dataSize->SysSizInput(ThisAirSysSizineInputLoop).AirLoopNum == AirLoopNum) {
660 4851 : AirLoopWasSized = true;
661 4851 : break;
662 : }
663 : }
664 : }
665 5212 : }
666 :
667 15155 : void CheckZoneSizing(EnergyPlusData &state,
668 : std::string_view const CompType, // Component Type (e.g. Chiller:Electric)
669 : std::string_view const CompName // Component Name (e.g. Big Chiller)
670 : )
671 : {
672 :
673 : // SUBROUTINE INFORMATION:
674 : // AUTHOR Fred Buhl
675 : // DATE WRITTEN October 2002
676 :
677 : // PURPOSE OF THIS SUBROUTINE:
678 : // This routine is called when an "autosize" input is encountered in a component
679 : // sizing routine to check that the zone sizing calculations have been done.
680 :
681 : // METHODOLOGY EMPLOYED:
682 : // Checks ZoneSizingRunDone flag. If false throws a fatal error.
683 :
684 15155 : if (!state.dataSize->ZoneSizingRunDone) {
685 0 : ShowSevereError(state, format("For autosizing of {} {}, a zone sizing run must be done.", CompType, CompName));
686 0 : if (state.dataSize->NumZoneSizingInput == 0) {
687 0 : ShowContinueError(state, "No \"Sizing:Zone\" objects were entered.");
688 : }
689 0 : if (!state.dataGlobal->DoZoneSizing) {
690 0 : ShowContinueError(state, R"(The "SimulationControl" object did not have the field "Do Zone Sizing Calculation" set to Yes.)");
691 : }
692 0 : ShowFatalError(state, "Program terminates due to previously shown condition(s).");
693 : }
694 15155 : }
695 :
696 1032 : void CheckThisZoneForSizing(EnergyPlusData &state,
697 : int const ZoneNum, // zone index to be checked
698 : bool &ZoneWasSized)
699 : {
700 :
701 : // SUBROUTINE INFORMATION:
702 : // AUTHOR B. Griffith
703 : // DATE WRITTEN Oct 2013
704 :
705 : // PURPOSE OF THIS SUBROUTINE:
706 : // utility routine to see if a particular zone has a Sizing:Zone object for it
707 : // and that sizing was done.
708 :
709 1032 : ZoneWasSized = false;
710 1032 : if (state.dataSize->ZoneSizingRunDone) {
711 33618 : for (int ThisSizingInput = 1; ThisSizingInput <= state.dataSize->NumZoneSizingInput; ++ThisSizingInput) {
712 33618 : if (state.dataSize->ZoneSizingInput(ThisSizingInput).ZoneNum == ZoneNum) {
713 1032 : ZoneWasSized = true;
714 1032 : break;
715 : }
716 : }
717 : }
718 1032 : }
719 :
720 38093 : void ValidateComponent(EnergyPlusData &state,
721 : std::string_view CompType, // Component Type (e.g. Chiller:Electric)
722 : std::string const &CompName, // Component Name (e.g. Big Chiller)
723 : bool &IsNotOK, // .TRUE. if this component pair is invalid
724 : std::string_view CallString // Context of this pair -- for error message
725 : )
726 : {
727 :
728 : // SUBROUTINE INFORMATION:
729 : // AUTHOR Linda Lawrie
730 : // DATE WRITTEN October 2002
731 :
732 : // PURPOSE OF THIS SUBROUTINE:
733 : // This subroutine can be called to validate the component type-name pairs that
734 : // are so much a part of the EnergyPlus input. The main drawback to this validation
735 : // has been that the "GetInput" routine may not have been called and/or exists in
736 : // another module from the one with the list. This means that validation must be
737 : // done later, perhaps after simulation has already started or perhaps raises an
738 : // array bound error instead.
739 :
740 : // METHODOLOGY EMPLOYED:
741 : // Uses existing routines in InputProcessor. GetObjectItemNum uses the "standard"
742 : // convention of the Name of the item/object being the first Alpha Argument.
743 :
744 38093 : IsNotOK = false;
745 :
746 76186 : int ItemNum = state.dataInputProcessing->inputProcessor->getObjectItemNum(state, std::string{CompType}, CompName);
747 :
748 38093 : if (ItemNum < 0) {
749 0 : ShowSevereError(state, format("During {} Input, Invalid Component Type input={}", CallString, CompType));
750 0 : ShowContinueError(state, format("Component name={}", CompName));
751 0 : IsNotOK = true;
752 38093 : } else if (ItemNum == 0) {
753 0 : ShowSevereError(state, format("During {} Input, Invalid Component Name input={}", CallString, CompName));
754 0 : ShowContinueError(state, format("Component type={}", CompType));
755 0 : IsNotOK = true;
756 : }
757 38093 : }
758 :
759 15 : void ValidateComponent(EnergyPlusData &state,
760 : std::string_view CompType, // Component Type (e.g. Chiller:Electric)
761 : std::string const &CompValType, // Component "name" field type
762 : std::string const &CompName, // Component Name (e.g. Big Chiller)
763 : bool &IsNotOK, // .TRUE. if this component pair is invalid
764 : std::string_view CallString // Context of this pair -- for error message
765 : )
766 : {
767 :
768 : // SUBROUTINE INFORMATION:
769 : // AUTHOR Linda Lawrie
770 : // DATE WRITTEN October 2002
771 :
772 : // PURPOSE OF THIS SUBROUTINE:
773 : // This subroutine can be called to validate the component type-name pairs that
774 : // are so much a part of the EnergyPlus input. The main drawback to this validation
775 : // has been that the "GetInput" routine may not have been called and/or exists in
776 : // another module from the one with the list. This means that validation must be
777 : // done later, perhaps after simulation has already started or perhaps raises an
778 : // array bound error instead.
779 :
780 : // METHODOLOGY EMPLOYED:
781 : // Uses existing routines in InputProcessor. GetObjectItemNum uses the "standard"
782 : // convention of the Name of the item/object being the first Alpha Argument.
783 :
784 15 : IsNotOK = false;
785 :
786 15 : int ItemNum = state.dataInputProcessing->inputProcessor->getObjectItemNum(state, CompType, CompValType, CompName);
787 :
788 15 : if (ItemNum < 0) {
789 0 : ShowSevereError(state, format("During {} Input, Invalid Component Type input={}", CallString, CompType));
790 0 : ShowContinueError(state, format("Component name={}", CompName));
791 0 : IsNotOK = true;
792 15 : } else if (ItemNum == 0) {
793 0 : ShowSevereError(state, format("During {} Input, Invalid Component Name input={}", CallString, CompName));
794 0 : ShowContinueError(state, format("Component type={}", CompType));
795 0 : IsNotOK = true;
796 : }
797 15 : }
798 :
799 4881177 : void CalcBasinHeaterPower(EnergyPlusData &state,
800 : Real64 const Capacity, // Basin heater capacity per degree C below setpoint (W/C)
801 : Sched::Schedule *sched, // basin heater schedule
802 : Real64 const SetPointTemp, // setpoint temperature for basin heater operation (C)
803 : Real64 &Power // Basin heater power (W)
804 : )
805 : {
806 :
807 : // SUBROUTINE INFORMATION:
808 : // AUTHOR Chandan Sharma, FSEC
809 : // DATE WRITTEN Feb 2010
810 :
811 : // PURPOSE OF THIS SUBROUTINE:
812 : // To calculate basin heater power when the evaporative cooled equipment is not operating
813 : // and outdoor air dry-bulb temperature is below the set-point
814 :
815 : // METHODOLOGY EMPLOYED:
816 : // Checks to see whether schedule for basin heater exists or not. If the schedule exists,
817 : // the basin heater is operated for the schedule specified otherwise the heater runs
818 : // for the entire simulation timestep whenever the outdoor temperature is below setpoint
819 : // and water is not flowing through the evaporative cooled equipment.
820 :
821 4881177 : Power = 0.0;
822 : // Operate basin heater anytime outdoor temperature is below setpoint and water is not flowing through the equipment
823 : // IF schedule exists, basin heater performance can be scheduled OFF
824 4881177 : if (sched != nullptr) {
825 39418 : Real64 BasinHeaterSch = sched->getCurrentVal();
826 39418 : if (Capacity > 0.0 && BasinHeaterSch > 0.0) {
827 39412 : Power = max(0.0, Capacity * (SetPointTemp - state.dataEnvrn->OutDryBulbTemp));
828 : }
829 : } else {
830 : // IF schedule does not exist, basin heater operates anytime outdoor dry-bulb temp is below setpoint
831 4841759 : if (Capacity > 0.0) {
832 176638 : Power = max(0.0, Capacity * (SetPointTemp - state.dataEnvrn->OutDryBulbTemp));
833 : }
834 : }
835 4881177 : }
836 :
837 800 : void TestAirPathIntegrity(EnergyPlusData &state, bool &ErrFound)
838 : {
839 :
840 : // SUBROUTINE INFORMATION:
841 : // AUTHOR Linda Lawrie
842 : // DATE WRITTEN March 2003
843 :
844 : // PURPOSE OF THIS SUBROUTINE:
845 : // This subroutine tests supply, return and overall air path integrity.
846 :
847 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
848 : bool errFlag;
849 800 : Array2D_int ValRetAPaths;
850 800 : Array2D_int NumRAPNodes;
851 800 : Array2D_int ValSupAPaths;
852 800 : Array2D_int NumSAPNodes;
853 :
854 800 : NumSAPNodes.allocate(state.dataLoopNodes->NumOfNodes, state.dataHVACGlobal->NumPrimaryAirSys);
855 800 : NumRAPNodes.allocate(state.dataLoopNodes->NumOfNodes, state.dataHVACGlobal->NumPrimaryAirSys);
856 800 : ValRetAPaths.allocate(state.dataLoopNodes->NumOfNodes, state.dataHVACGlobal->NumPrimaryAirSys);
857 800 : ValSupAPaths.allocate(state.dataLoopNodes->NumOfNodes, state.dataHVACGlobal->NumPrimaryAirSys);
858 800 : NumSAPNodes = 0;
859 800 : NumRAPNodes = 0;
860 800 : ValRetAPaths = 0;
861 800 : ValSupAPaths = 0;
862 :
863 800 : TestSupplyAirPathIntegrity(state, errFlag);
864 800 : if (errFlag) {
865 0 : ErrFound = true;
866 : }
867 800 : TestReturnAirPathIntegrity(state, errFlag, ValRetAPaths);
868 800 : if (errFlag) {
869 0 : ErrFound = true;
870 : }
871 :
872 : // Final tests, look for duplicate nodes
873 2021 : for (int Loop = 1; Loop <= state.dataHVACGlobal->NumPrimaryAirSys; ++Loop) {
874 1221 : if (ValRetAPaths(1, Loop) != 0) {
875 1208 : continue;
876 : }
877 13 : if (state.dataAirLoop->AirToZoneNodeInfo(Loop).NumReturnNodes <= 0) {
878 0 : continue;
879 : }
880 13 : ValRetAPaths(1, Loop) = state.dataAirLoop->AirToZoneNodeInfo(Loop).ZoneEquipReturnNodeNum(1);
881 : }
882 :
883 2021 : for (int Loop = 1; Loop <= state.dataHVACGlobal->NumPrimaryAirSys; ++Loop) {
884 379523 : for (int Loop1 = 1; Loop1 <= state.dataLoopNodes->NumOfNodes; ++Loop1) {
885 378302 : int TestNode = ValRetAPaths(Loop1, Loop);
886 378302 : int Count = 0;
887 15427964 : for (int Loop2 = 1; Loop2 <= state.dataHVACGlobal->NumPrimaryAirSys; ++Loop2) {
888 51301813 : for (int Loop3 = 1; Loop3 <= state.dataLoopNodes->NumOfNodes; ++Loop3) {
889 51301813 : if (Loop2 == Loop && Loop1 == Loop3) {
890 6326 : continue; // Don't count test node
891 : }
892 51295487 : if (ValRetAPaths(Loop3, Loop2) == 0) {
893 15049662 : break;
894 : }
895 36245825 : if (ValRetAPaths(Loop3, Loop2) == TestNode) {
896 0 : ++Count;
897 : }
898 : }
899 : }
900 378302 : if (Count > 0) {
901 0 : ShowSevereError(state, "Duplicate Node detected in Return Air Paths");
902 0 : ShowContinueError(state, format("Test Node={}", state.dataLoopNodes->NodeID(TestNode)));
903 0 : ShowContinueError(state, format("In Air Path={}", state.dataAirLoop->AirToZoneNodeInfo(Loop).AirLoopName));
904 0 : ErrFound = true;
905 : }
906 : }
907 : }
908 :
909 800 : NumSAPNodes.deallocate();
910 800 : NumRAPNodes.deallocate();
911 800 : ValRetAPaths.deallocate();
912 800 : ValSupAPaths.deallocate();
913 800 : }
914 :
915 800 : void TestSupplyAirPathIntegrity(EnergyPlusData &state, bool &ErrFound)
916 : {
917 :
918 : // SUBROUTINE INFORMATION:
919 : // AUTHOR Linda Lawrie
920 : // DATE WRITTEN March 2003
921 :
922 : // PURPOSE OF THIS SUBROUTINE:
923 : // This subroutine tests supply air path integrity and displays the loop for each branch.
924 : // Also, input and output nodes.
925 :
926 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
927 800 : std::string PrimaryAirLoopName; // Air Loop to which this supply air path is connected
928 800 : Array1D_bool FoundSupplyPlenum;
929 800 : Array1D_bool FoundZoneSplitter;
930 800 : Array1D_string FoundNames;
931 800 : int NumErr = 0; // Error Counter //Autodesk:Init Initialization added
932 :
933 : // Do by Paths
934 1600 : ShowMessage(state, "Testing Individual Supply Air Path Integrity");
935 800 : ErrFound = false;
936 :
937 800 : print(state.files.bnd, "{}\n", "! ===============================================================");
938 : static constexpr std::string_view Format_700("! <#Supply Air Paths>,<Number of Supply Air Paths>");
939 800 : print(state.files.bnd, "{}\n", Format_700);
940 800 : print(state.files.bnd, " #Supply Air Paths,{}\n", state.dataZoneEquip->NumSupplyAirPaths);
941 : static constexpr std::string_view Format_702("! <Supply Air Path>,<Supply Air Path Count>,<Supply Air Path Name>,<AirLoopHVAC Name>");
942 800 : print(state.files.bnd, "{}\n", Format_702);
943 : static constexpr std::string_view Format_703("! <#Components on Supply Air Path>,<Number of Components>");
944 800 : print(state.files.bnd, "{}\n", Format_703);
945 : static constexpr std::string_view Format_704(
946 : "! <Supply Air Path Component>,<Component Count>,<Component Type>,<Component Name>,<AirLoopHVAC Name>");
947 800 : print(state.files.bnd, "{}\n", Format_704);
948 : static constexpr std::string_view Format_707("! <#Outlet Nodes on Supply Air Path Component>,<Number of Nodes>");
949 800 : print(state.files.bnd, "{}\n", Format_707);
950 : static constexpr std::string_view Format_708(
951 : "! <Supply Air Path Component Nodes>,<Node Count>,<Component Type>,<Component Name>,<Inlet Node Name>,<Outlet "
952 : "Node Name>,<AirLoopHVAC Name>");
953 800 : print(state.files.bnd, "{}\n", Format_708);
954 :
955 2025 : for (int BCount = 1; BCount <= state.dataZoneEquip->NumSupplyAirPaths; ++BCount) {
956 :
957 : // Determine which air loop this supply air path is connected to
958 1225 : int Found = 0;
959 6581 : for (int Count1 = 1; Count1 <= state.dataHVACGlobal->NumPrimaryAirSys; ++Count1) {
960 6581 : PrimaryAirLoopName = state.dataAirLoop->AirToZoneNodeInfo(Count1).AirLoopName;
961 6581 : Found = 0;
962 13178 : for (int Count2 = 1; Count2 <= state.dataAirLoop->AirToZoneNodeInfo(Count1).NumSupplyNodes; ++Count2) {
963 6597 : if (state.dataZoneEquip->SupplyAirPath(BCount).InletNodeNum ==
964 6597 : state.dataAirLoop->AirToZoneNodeInfo(Count1).ZoneEquipSupplyNodeNum(Count2)) {
965 1225 : Found = Count2;
966 : }
967 : }
968 6581 : if (Found != 0) {
969 1225 : break;
970 : }
971 : }
972 1225 : if (Found == 0) {
973 0 : PrimaryAirLoopName = "**Unknown**";
974 : }
975 :
976 1225 : print(state.files.bnd, " Supply Air Path,{},{},{}\n", BCount, state.dataZoneEquip->SupplyAirPath(BCount).Name, PrimaryAirLoopName);
977 1225 : print(state.files.bnd, " #Components on Supply Air Path,{}\n", state.dataZoneEquip->SupplyAirPath(BCount).NumOfComponents);
978 :
979 1225 : std::string AirPathNodeName = state.dataLoopNodes->NodeID(state.dataZoneEquip->SupplyAirPath(BCount).InletNodeNum);
980 :
981 2457 : for (int Count = 1; Count <= state.dataZoneEquip->SupplyAirPath(BCount).NumOfComponents; ++Count) {
982 :
983 1232 : print(state.files.bnd,
984 : " Supply Air Path Component,{},{},{},{}\n",
985 : Count,
986 1232 : state.dataZoneEquip->SupplyAirPath(BCount).ComponentType(Count),
987 1232 : state.dataZoneEquip->SupplyAirPath(BCount).ComponentName(Count),
988 : PrimaryAirLoopName);
989 :
990 : AirLoopHVACCompType CompType = static_cast<AirLoopHVACCompType>(
991 1232 : getEnumValue(AirLoopHVACCompTypeNamesUC, Util::makeUPPER(state.dataZoneEquip->SupplyAirPath(BCount).ComponentType(Count))));
992 :
993 1232 : switch (CompType) {
994 8 : case AirLoopHVACCompType::SupplyPlenum: {
995 20 : for (int Count2 = 1; Count2 <= state.dataZonePlenum->NumZoneSupplyPlenums; ++Count2) {
996 12 : if (state.dataZonePlenum->ZoneSupPlenCond(Count2).ZonePlenumName !=
997 12 : state.dataZoneEquip->SupplyAirPath(BCount).ComponentName(Count)) {
998 4 : continue;
999 : }
1000 8 : if (Count == 1 && AirPathNodeName != state.dataLoopNodes->NodeID(state.dataZonePlenum->ZoneSupPlenCond(Count2).InletNode)) {
1001 0 : ShowSevereError(state, format("Error in AirLoopHVAC:SupplyPath={}", state.dataZoneEquip->SupplyAirPath(BCount).Name));
1002 0 : ShowContinueError(state,
1003 0 : format("For AirLoopHVAC:SupplyPlenum={}", state.dataZonePlenum->ZoneSupPlenCond(Count2).ZonePlenumName));
1004 0 : ShowContinueError(state, format("Expected inlet node (supply air path)={}", AirPathNodeName));
1005 0 : ShowContinueError(state,
1006 0 : format("Encountered node name (supply plenum)={}",
1007 0 : state.dataLoopNodes->NodeID(state.dataZonePlenum->ZoneSupPlenCond(Count2).OutletNode(1))));
1008 0 : ErrFound = true;
1009 0 : ++NumErr;
1010 : }
1011 8 : print(state.files.bnd,
1012 : " #Outlet Nodes on Supply Air Path Component,{}\n",
1013 8 : state.dataZonePlenum->ZoneSupPlenCond(Count2).NumOutletNodes);
1014 20 : for (int Count1 = 1; Count1 <= state.dataZonePlenum->ZoneSupPlenCond(Count2).NumOutletNodes; ++Count1) {
1015 12 : print(state.files.bnd,
1016 : " Supply Air Path Component Nodes,{},{},{},{},{},{}\n",
1017 : Count1,
1018 12 : state.dataZoneEquip->SupplyAirPath(BCount).ComponentType(Count),
1019 12 : state.dataZoneEquip->SupplyAirPath(BCount).ComponentName(Count),
1020 12 : state.dataLoopNodes->NodeID(state.dataZonePlenum->ZoneSupPlenCond(Count2).InletNode),
1021 12 : state.dataLoopNodes->NodeID(state.dataZonePlenum->ZoneSupPlenCond(Count2).OutletNode(Count1)),
1022 : PrimaryAirLoopName);
1023 : }
1024 : }
1025 8 : } break;
1026 1224 : case AirLoopHVACCompType::ZoneSplitter: {
1027 13176 : for (int Count2 = 1; Count2 <= state.dataSplitterComponent->NumSplitters; ++Count2) {
1028 11952 : if (state.dataSplitterComponent->SplitterCond(Count2).SplitterName !=
1029 11952 : state.dataZoneEquip->SupplyAirPath(BCount).ComponentName(Count)) {
1030 10728 : continue;
1031 : }
1032 1224 : if (Count == 1 && AirPathNodeName != state.dataLoopNodes->NodeID(state.dataSplitterComponent->SplitterCond(Count2).InletNode)) {
1033 0 : ShowSevereError(state, format("Error in AirLoopHVAC:SupplyPath={}", state.dataZoneEquip->SupplyAirPath(BCount).Name));
1034 0 : ShowContinueError(state,
1035 0 : format("For AirLoopHVAC:ZoneSplitter={}", state.dataSplitterComponent->SplitterCond(Count2).SplitterName));
1036 0 : ShowContinueError(state, format("Expected inlet node (supply air path)={}", AirPathNodeName));
1037 0 : ShowContinueError(state,
1038 0 : format("Encountered node name (zone splitter)={}",
1039 0 : state.dataLoopNodes->NodeID(state.dataSplitterComponent->SplitterCond(Count2).InletNode)));
1040 0 : ErrFound = true;
1041 0 : ++NumErr;
1042 : }
1043 1224 : print(state.files.bnd,
1044 : " #Outlet Nodes on Supply Air Path Component,{}\n",
1045 1224 : state.dataSplitterComponent->SplitterCond(Count2).NumOutletNodes);
1046 5045 : for (int Count1 = 1; Count1 <= state.dataSplitterComponent->SplitterCond(Count2).NumOutletNodes; ++Count1) {
1047 3821 : print(state.files.bnd,
1048 : " Supply Air Path Component Nodes,{},{},{},{},{},{}\n",
1049 : Count1,
1050 3821 : state.dataZoneEquip->SupplyAirPath(BCount).ComponentType(Count),
1051 3821 : state.dataZoneEquip->SupplyAirPath(BCount).ComponentName(Count),
1052 3821 : state.dataLoopNodes->NodeID(state.dataSplitterComponent->SplitterCond(Count2).InletNode),
1053 3821 : state.dataLoopNodes->NodeID(state.dataSplitterComponent->SplitterCond(Count2).OutletNode(Count1)),
1054 : PrimaryAirLoopName);
1055 : }
1056 : }
1057 1224 : } break;
1058 0 : default: {
1059 0 : ShowSevereError(
1060 0 : state, format("Invalid Component Type in Supply Air Path={}", state.dataZoneEquip->SupplyAirPath(BCount).ComponentType(Count)));
1061 0 : ErrFound = true;
1062 0 : ++NumErr;
1063 0 : } break;
1064 : }
1065 : }
1066 :
1067 1225 : if (state.dataZoneEquip->SupplyAirPath(BCount).NumNodes > 0) {
1068 : static constexpr std::string_view Format_705("! <#Nodes on Supply Air Path>,<Number of Nodes>");
1069 1225 : print(state.files.bnd, "{}\n", Format_705);
1070 : static constexpr std::string_view Format_706("! <Supply Air Path Node>,<Node Type>,<Node Count>,<Node Name>,<AirLoopHVAC Name>");
1071 1225 : print(state.files.bnd, "{}\n", Format_706);
1072 1225 : print(state.files.bnd, "#Nodes on Supply Air Path,{}\n", state.dataZoneEquip->SupplyAirPath(BCount).NumNodes);
1073 6283 : for (int Count2 = 1; Count2 <= state.dataZoneEquip->SupplyAirPath(BCount).NumNodes; ++Count2) {
1074 5058 : if (state.dataZoneEquip->SupplyAirPath(BCount).NodeType(Count2) == DataZoneEquipment::AirNodeType::PathInlet) {
1075 1225 : print(state.files.bnd,
1076 : " Supply Air Path Node,Inlet Node,{},{},{}\n",
1077 : Count2,
1078 1225 : state.dataLoopNodes->NodeID(state.dataZoneEquip->SupplyAirPath(BCount).Node(Count2)),
1079 : PrimaryAirLoopName);
1080 3833 : } else if (state.dataZoneEquip->SupplyAirPath(BCount).NodeType(Count2) == DataZoneEquipment::AirNodeType::Intermediate) {
1081 7 : print(state.files.bnd,
1082 : " Supply Air Path Node,Through Node,{},{},{}\n",
1083 : Count2,
1084 7 : state.dataLoopNodes->NodeID(state.dataZoneEquip->SupplyAirPath(BCount).Node(Count2)),
1085 : PrimaryAirLoopName);
1086 3826 : } else if (state.dataZoneEquip->SupplyAirPath(BCount).NodeType(Count2) == DataZoneEquipment::AirNodeType::Outlet) {
1087 3826 : print(state.files.bnd,
1088 : " Supply Air Path Node,Outlet Node,{},{},{}\n",
1089 : Count2,
1090 3826 : state.dataLoopNodes->NodeID(state.dataZoneEquip->SupplyAirPath(BCount).Node(Count2)),
1091 : PrimaryAirLoopName);
1092 : }
1093 : }
1094 : }
1095 1225 : }
1096 :
1097 800 : if (state.dataSplitterComponent->NumSplitters == 0) {
1098 256 : if (state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "AirLoopHVAC:ZoneSplitter") > 0) {
1099 0 : SplitterComponent::GetSplitterInput(state);
1100 : }
1101 : }
1102 800 : if (state.dataZonePlenum->NumZoneSupplyPlenums == 0 && state.dataZonePlenum->NumZoneReturnPlenums == 0) {
1103 605 : if (state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "AirLoopHVAC:SupplyPlenum") > 0) {
1104 0 : ZonePlenum::GetZonePlenumInput(state);
1105 : }
1106 : }
1107 :
1108 : // now the reverse. is every zone splitter and supply plenum on supply air path
1109 800 : FoundSupplyPlenum.dimension(state.dataZonePlenum->NumZoneSupplyPlenums, false);
1110 800 : FoundZoneSplitter.dimension(state.dataSplitterComponent->NumSplitters, false);
1111 800 : FoundNames.allocate(state.dataZonePlenum->NumZoneSupplyPlenums);
1112 808 : for (int Count1 = 1; Count1 <= state.dataZonePlenum->NumZoneSupplyPlenums; ++Count1) {
1113 20 : for (int BCount = 1; BCount <= state.dataZoneEquip->NumSupplyAirPaths; ++BCount) {
1114 35 : for (int Count = 1; Count <= state.dataZoneEquip->SupplyAirPath(BCount).NumOfComponents; ++Count) {
1115 31 : if (state.dataZonePlenum->ZoneSupPlenCond(Count1).ZonePlenumName != state.dataZoneEquip->SupplyAirPath(BCount).ComponentName(Count) ||
1116 8 : state.dataZoneEquip->SupplyAirPath(BCount).ComponentType(Count) != "AIRLOOPHVAC:SUPPLYPLENUM") {
1117 15 : continue;
1118 : }
1119 8 : if (FoundSupplyPlenum(Count1)) {
1120 0 : ShowSevereError(
1121 : state,
1122 0 : format("AirLoopHVAC:SupplyPlenum=\"{}\", duplicate entry.", state.dataZonePlenum->ZoneSupPlenCond(Count1).ZonePlenumName));
1123 0 : ShowContinueError(state, format("already exists on AirLoopHVAC:SupplyPath=\"{}\".", FoundNames(Count1)));
1124 0 : ErrFound = true;
1125 : } else {
1126 : // record use
1127 8 : FoundSupplyPlenum(Count1) = true;
1128 8 : FoundNames(Count1) = state.dataZoneEquip->SupplyAirPath(BCount).Name;
1129 : }
1130 : }
1131 : }
1132 : }
1133 800 : FoundNames.deallocate();
1134 800 : FoundNames.allocate(state.dataSplitterComponent->NumSplitters);
1135 2024 : for (int Count1 = 1; Count1 <= state.dataSplitterComponent->NumSplitters; ++Count1) {
1136 13176 : for (int BCount = 1; BCount <= state.dataZoneEquip->NumSupplyAirPaths; ++BCount) {
1137 23915 : for (int Count = 1; Count <= state.dataZoneEquip->SupplyAirPath(BCount).NumOfComponents; ++Count) {
1138 11963 : if (state.dataSplitterComponent->SplitterCond(Count1).SplitterName !=
1139 25150 : state.dataZoneEquip->SupplyAirPath(BCount).ComponentName(Count) ||
1140 1224 : state.dataZoneEquip->SupplyAirPath(BCount).ComponentType(Count) != "AIRLOOPHVAC:ZONESPLITTER") {
1141 10739 : continue;
1142 : }
1143 1224 : if (FoundZoneSplitter(Count1)) {
1144 0 : ShowSevereError(
1145 : state,
1146 0 : format("AirLoopHVAC:ZoneSplitter=\"{}\", duplicate entry.", state.dataSplitterComponent->SplitterCond(Count1).SplitterName));
1147 0 : ShowContinueError(state, format("already exists on AirLoopHVAC:SupplyPath=\"{}\".", FoundNames(Count1)));
1148 0 : ErrFound = true;
1149 : } else {
1150 : // record use
1151 1224 : FoundZoneSplitter(Count1) = true;
1152 1224 : FoundNames(Count1) = state.dataZoneEquip->SupplyAirPath(BCount).Name;
1153 : }
1154 : }
1155 : }
1156 : }
1157 800 : FoundNames.deallocate();
1158 :
1159 800 : if (!all(FoundSupplyPlenum)) {
1160 0 : for (int Count1 = 1; Count1 <= state.dataZonePlenum->NumZoneSupplyPlenums; ++Count1) {
1161 0 : if (FoundSupplyPlenum(Count1)) {
1162 0 : continue;
1163 : }
1164 0 : ShowSevereError(state,
1165 0 : format("AirLoopHVAC:SupplyPlenum=\"{}\", not found on any AirLoopHVAC:SupplyPath.",
1166 0 : state.dataZonePlenum->ZoneSupPlenCond(Count1).ZonePlenumName));
1167 : }
1168 : }
1169 :
1170 800 : if (!all(FoundZoneSplitter)) {
1171 0 : for (int Count1 = 1; Count1 <= state.dataSplitterComponent->NumSplitters; ++Count1) {
1172 0 : if (FoundZoneSplitter(Count1)) {
1173 0 : continue;
1174 : }
1175 0 : ShowSevereError(state,
1176 0 : format("AirLoopHVAC:ZoneSplitter=\"{}\", not found on any AirLoopHVAC:SupplyPath.",
1177 0 : state.dataSplitterComponent->SplitterCond(Count1).SplitterName));
1178 : }
1179 : }
1180 :
1181 800 : FoundSupplyPlenum.deallocate();
1182 800 : FoundZoneSplitter.deallocate();
1183 :
1184 800 : if (ErrFound) {
1185 0 : ShowSevereError(state, "Supply Air Path(s) did not pass integrity testing");
1186 : } else {
1187 2400 : ShowMessage(state, "All Supply Air Paths passed integrity testing");
1188 : }
1189 800 : }
1190 :
1191 800 : void TestReturnAirPathIntegrity(EnergyPlusData &state, bool &ErrFound, Array2S_int ValRetAPaths)
1192 : {
1193 :
1194 : // SUBROUTINE INFORMATION:
1195 : // AUTHOR Linda Lawrie
1196 : // DATE WRITTEN March 2003
1197 :
1198 : // PURPOSE OF THIS SUBROUTINE:
1199 : // This subroutine tests return air path integrity and displays the loop for each branch.
1200 : // Also, input and output nodes.
1201 :
1202 : // REFERENCES:
1203 : // Return Air Path Validity Rules:
1204 : // Last component (zone mixer or zone return plenum) must resolve to
1205 : // be the outlet node for the return air path. Inlets to this component must be outlets from
1206 : // previous components or "controlled zone outlets"?.
1207 : // (though converse not true -- each outlet in previous components do not
1208 : // have to be inlets on this item -- though they must be inputs somewhere in the stream).
1209 : // If multiple components and no mixer, then a zone return plenums "outlet" must
1210 : // be represented as an inlet on a later plenum. i.e. some zone return plenums are
1211 : // really acting as "mixers" in a sense. These do not need to be stepwise in succession.
1212 : // Same caveat for inlets from previous item.
1213 : // If multiple components and mixer, then prior condition (nested plenums) is allowed as long as
1214 : // those aren't duplicated as mixer inlets. (i.e. zone rp 1 => zone rp 2 => zone mixer but
1215 : // zone rp 1 outlet should not also be inlet to mixer.
1216 : // Can have (nzrp -- nested zone return plenum, pzrp -- parallel zone return plenum):
1217 : // nzrp 1 => nzrp 2 & pzrp 3 => zm (inlets from nzrp 2 and pzrp 3). Or, likewise:
1218 : // pzrp 1 & pzrp 2 => zm => pzrp 3 (outlets from pzrp 1/2 are inlets to zm whose outlet is an
1219 : // inlet to pzrp 3 whose outlet is the outlet for the return air path.
1220 :
1221 : // Cannot have duplicate nodes in the "inlet" stream? (i.e. cannot have same zone feeding two independent
1222 : // plenums, for example). Similarly, Same return plenum can't be in two air loops nor as two independent
1223 : // return plenums in one return air path.
1224 :
1225 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1226 800 : std::string PrimaryAirLoopName; // Air Loop to which this return air path is connected
1227 800 : Array1D_bool FoundReturnPlenum;
1228 800 : Array1D_bool FoundZoneMixer;
1229 800 : Array1D_string FoundNames;
1230 800 : Array1D_int AllNodes;
1231 :
1232 : // Formats
1233 :
1234 : // Do by Paths
1235 1600 : ShowMessage(state, "Testing Individual Return Air Path Integrity");
1236 800 : ErrFound = false;
1237 800 : int NumErr = 0;
1238 :
1239 800 : print(state.files.bnd, "{}\n", "! ===============================================================");
1240 : static constexpr std::string_view Format_700("! <#Return Air Paths>,<Number of Return Air Paths>");
1241 800 : print(state.files.bnd, "{}\n", Format_700);
1242 800 : print(state.files.bnd, " #Return Air Paths,{}\n", state.dataZoneEquip->NumReturnAirPaths);
1243 : static constexpr std::string_view Format_702("! <Return Air Path>,<Return Air Path Count>,<Return Air Path Name>,<AirLoopHVAC Name>");
1244 800 : print(state.files.bnd, "{}\n", Format_702);
1245 : static constexpr std::string_view Format_703("! <#Components on Return Air Path>,<Number of Components>");
1246 800 : print(state.files.bnd, "{}\n", Format_703);
1247 : static constexpr std::string_view Format_704(
1248 : "! <Return Air Path Component>,<Component Count>,<Component Type>,<Component Name>,<AirLoopHVAC Name>");
1249 800 : print(state.files.bnd, "{}\n", Format_704);
1250 : static constexpr std::string_view Format_707("! <#Inlet Nodes on Return Air Path Component>,<Number of Nodes>");
1251 800 : print(state.files.bnd, "{}\n", Format_707);
1252 : static constexpr std::string_view Format_708(
1253 : "! <Return Air Path Component Nodes>,<Node Count>,<Component Type>,<Component Name>,<Inlet Node Name>,<Outlet "
1254 : "Node Name>,<AirLoopHVAC Name>");
1255 800 : print(state.files.bnd, "{}\n", Format_708);
1256 :
1257 800 : AllNodes.allocate(state.dataLoopNodes->NumOfNodes);
1258 :
1259 2008 : for (int BCount = 1; BCount <= state.dataZoneEquip->NumReturnAirPaths; ++BCount) {
1260 : // Determine which air loop this supply air path is connected to
1261 1208 : int Found = 0;
1262 6550 : for (int Count1 = 1; Count1 <= state.dataHVACGlobal->NumPrimaryAirSys; ++Count1) {
1263 6550 : PrimaryAirLoopName = state.dataAirLoop->AirToZoneNodeInfo(Count1).AirLoopName;
1264 6550 : Found = 0;
1265 13100 : for (int Count2 = 1; Count2 <= state.dataAirLoop->AirToZoneNodeInfo(Count1).NumReturnNodes; ++Count2) {
1266 6550 : if (state.dataZoneEquip->ReturnAirPath(BCount).OutletNodeNum ==
1267 6550 : state.dataAirLoop->AirToZoneNodeInfo(Count1).ZoneEquipReturnNodeNum(Count2)) {
1268 1208 : Found = Count2;
1269 : }
1270 : }
1271 6550 : if (Found != 0) {
1272 1208 : break;
1273 : }
1274 : }
1275 1208 : if (Found == 0) {
1276 0 : PrimaryAirLoopName = "**Unknown**";
1277 : }
1278 :
1279 1208 : print(state.files.bnd, " Return Air Path,{},{},{}\n", BCount, state.dataZoneEquip->ReturnAirPath(BCount).Name, PrimaryAirLoopName);
1280 :
1281 1208 : int NumComp = state.dataZoneEquip->ReturnAirPath(BCount).NumOfComponents;
1282 1208 : print(state.files.bnd, " #Components on Return Air Path,{}\n", NumComp);
1283 :
1284 1208 : std::string const &AirPathNodeName = state.dataLoopNodes->NodeID(state.dataZoneEquip->ReturnAirPath(BCount).OutletNodeNum);
1285 :
1286 1208 : int MixerCount = 0;
1287 2532 : for (int Count = 1; Count <= NumComp; ++Count) {
1288 1324 : print(state.files.bnd,
1289 : " Return Air Path Component,{},{},{},{}\n",
1290 : Count,
1291 1324 : state.dataZoneEquip->ReturnAirPath(BCount).ComponentType(Count),
1292 1324 : state.dataZoneEquip->ReturnAirPath(BCount).ComponentName(Count),
1293 : PrimaryAirLoopName);
1294 :
1295 1324 : if (Util::SameString(state.dataZoneEquip->ReturnAirPath(BCount).ComponentType(Count), "AirLoopHVAC:ZoneMixer")) {
1296 1040 : ++MixerCount;
1297 : }
1298 : }
1299 :
1300 1208 : if (MixerCount > 1) {
1301 0 : ShowSevereError(state, format("Too many zone mixers in Return Air Path={}", state.dataZoneEquip->ReturnAirPath(BCount).Name));
1302 0 : ErrFound = true;
1303 0 : ++NumErr;
1304 0 : continue;
1305 : }
1306 :
1307 1208 : AllNodes = 0;
1308 1208 : int CountNodes = 0;
1309 :
1310 1208 : if (NumComp > 0) {
1311 :
1312 : AirLoopHVACCompType CompType = static_cast<AirLoopHVACCompType>(
1313 1208 : getEnumValue(AirLoopHVACCompTypeNamesUC, Util::makeUPPER(state.dataZoneEquip->ReturnAirPath(BCount).ComponentType(NumComp))));
1314 :
1315 1208 : switch (CompType) {
1316 1040 : case AirLoopHVACCompType::ZoneMixer: {
1317 12598 : for (int Count2 = 1; Count2 <= state.dataMixerComponent->NumMixers; ++Count2) {
1318 11558 : if (state.dataZoneEquip->ReturnAirPath(BCount).ComponentName(NumComp) != state.dataMixerComponent->MixerCond(Count2).MixerName) {
1319 10518 : continue;
1320 : }
1321 : // Found correct Mixer (by name), check outlet node vs. return air path outlet node
1322 1040 : if (AirPathNodeName != state.dataLoopNodes->NodeID(state.dataMixerComponent->MixerCond(Count2).OutletNode)) {
1323 0 : ShowSevereError(state, format("Error in Return Air Path={}", state.dataZoneEquip->ReturnAirPath(BCount).Name));
1324 0 : ShowContinueError(state, format("For Connector:Mixer={}", state.dataZoneEquip->ReturnAirPath(BCount).ComponentName(NumComp)));
1325 0 : ShowContinueError(state, format("Expected outlet node (return air path)={}", AirPathNodeName));
1326 0 : ShowContinueError(state,
1327 0 : format("Encountered node name (mixer)={}",
1328 0 : state.dataLoopNodes->NodeID(state.dataMixerComponent->MixerCond(Count2).OutletNode)));
1329 0 : ErrFound = true;
1330 0 : ++NumErr;
1331 : } else {
1332 1040 : ++CountNodes;
1333 1040 : AllNodes(CountNodes) = state.dataMixerComponent->MixerCond(Count2).OutletNode;
1334 1040 : if (state.dataZoneEquip->ReturnAirPath(BCount).OutletNodeNum == state.dataMixerComponent->MixerCond(Count2).OutletNode) {
1335 1040 : state.dataZoneEquip->ReturnAirPath(BCount).OutletRetPathCompNum = NumComp;
1336 : }
1337 3617 : for (int Loop = 1; Loop <= state.dataMixerComponent->MixerCond(Count2).NumInletNodes; ++Loop) {
1338 2577 : ++CountNodes;
1339 2577 : AllNodes(CountNodes) = state.dataMixerComponent->MixerCond(Count2).InletNode(Loop);
1340 : }
1341 : }
1342 1040 : print(state.files.bnd,
1343 : " #Inlet Nodes on Return Air Path Component,{}\n",
1344 1040 : state.dataMixerComponent->MixerCond(Count2).NumInletNodes);
1345 3617 : for (int Count1 = 1; Count1 <= state.dataMixerComponent->MixerCond(Count2).NumInletNodes; ++Count1) {
1346 2577 : print(state.files.bnd,
1347 : " Return Air Path Component Nodes,{},{},{},{},{},{}\n",
1348 : Count1,
1349 2577 : state.dataZoneEquip->ReturnAirPath(BCount).ComponentType(NumComp),
1350 2577 : state.dataZoneEquip->ReturnAirPath(BCount).ComponentName(NumComp),
1351 2577 : state.dataLoopNodes->NodeID(state.dataMixerComponent->MixerCond(Count2).InletNode(Count1)),
1352 2577 : state.dataLoopNodes->NodeID(state.dataMixerComponent->MixerCond(Count2).OutletNode),
1353 : PrimaryAirLoopName);
1354 : }
1355 : }
1356 1040 : } break;
1357 168 : case AirLoopHVACCompType::ReturnPlenum: {
1358 518 : for (int Count2 = 1; Count2 <= state.dataZonePlenum->NumZoneReturnPlenums; ++Count2) {
1359 350 : if (state.dataZoneEquip->ReturnAirPath(BCount).ComponentName(NumComp) !=
1360 350 : state.dataZonePlenum->ZoneRetPlenCond(Count2).ZonePlenumName) {
1361 182 : continue;
1362 : }
1363 168 : if (AirPathNodeName != state.dataLoopNodes->NodeID(state.dataZonePlenum->ZoneRetPlenCond(Count2).OutletNode)) {
1364 0 : ShowSevereError(state, format("Error in Return Air Path={}", state.dataZoneEquip->ReturnAirPath(BCount).Name));
1365 0 : ShowContinueError(
1366 0 : state, format("For AirLoopHVAC:ReturnPlenum={}", state.dataZoneEquip->ReturnAirPath(BCount).ComponentName(NumComp)));
1367 0 : ShowContinueError(state, format("Expected outlet node (return air path)={}", AirPathNodeName));
1368 0 : ShowContinueError(state,
1369 0 : format("Encountered node name (zone return plenum)={}",
1370 0 : state.dataLoopNodes->NodeID(state.dataZonePlenum->ZoneRetPlenCond(Count2).OutletNode)));
1371 0 : ErrFound = true;
1372 0 : ++NumErr;
1373 : } else {
1374 168 : ++CountNodes;
1375 168 : AllNodes(CountNodes) = state.dataZonePlenum->ZoneRetPlenCond(Count2).OutletNode;
1376 168 : if (state.dataZoneEquip->ReturnAirPath(BCount).OutletNodeNum == state.dataZonePlenum->ZoneRetPlenCond(Count2).OutletNode) {
1377 168 : state.dataZoneEquip->ReturnAirPath(BCount).OutletRetPathCompNum = NumComp;
1378 : }
1379 918 : for (int Loop = 1; Loop <= state.dataZonePlenum->ZoneRetPlenCond(Count2).NumInletNodes; ++Loop) {
1380 750 : ++CountNodes;
1381 750 : AllNodes(CountNodes) = state.dataZonePlenum->ZoneRetPlenCond(Count2).InletNode(Loop);
1382 : }
1383 : }
1384 168 : print(state.files.bnd,
1385 : " #Inlet Nodes on Return Air Path Component,{}\n",
1386 168 : state.dataZonePlenum->ZoneRetPlenCond(Count2).NumInletNodes);
1387 918 : for (int Count1 = 1; Count1 <= state.dataZonePlenum->ZoneRetPlenCond(Count2).NumInletNodes; ++Count1) {
1388 750 : print(state.files.bnd,
1389 : " Return Air Path Component Nodes,{},{},{},{},{},{}\n",
1390 : Count1,
1391 750 : state.dataZoneEquip->ReturnAirPath(BCount).ComponentType(NumComp),
1392 750 : state.dataZoneEquip->ReturnAirPath(BCount).ComponentName(NumComp),
1393 750 : state.dataLoopNodes->NodeID(state.dataZonePlenum->ZoneRetPlenCond(Count2).InletNode(Count1)),
1394 750 : state.dataLoopNodes->NodeID(state.dataZonePlenum->ZoneRetPlenCond(Count2).OutletNode),
1395 : PrimaryAirLoopName);
1396 : }
1397 : }
1398 168 : } break;
1399 0 : default: // This already validated in GetReturnAirPath
1400 0 : break;
1401 : }
1402 : }
1403 :
1404 1208 : if (NumComp > 1) {
1405 231 : for (int Count3 = 1; Count3 <= NumComp - 1; ++Count3) {
1406 :
1407 : AirLoopHVACCompType CompType = static_cast<AirLoopHVACCompType>(
1408 116 : getEnumValue(AirLoopHVACCompTypeNamesUC, Util::makeUPPER(state.dataZoneEquip->ReturnAirPath(BCount).ComponentType(Count3))));
1409 :
1410 116 : switch (CompType) {
1411 0 : case AirLoopHVACCompType::ZoneMixer: {
1412 0 : for (int Count2 = 1; Count2 <= state.dataMixerComponent->NumMixers; ++Count2) {
1413 0 : if (state.dataZoneEquip->ReturnAirPath(BCount).ComponentName(Count3) !=
1414 0 : state.dataMixerComponent->MixerCond(Count2).MixerName) {
1415 0 : continue;
1416 : }
1417 0 : for (int Loop = 1; Loop <= state.dataMixerComponent->MixerCond(Count2).NumInletNodes; ++Loop) {
1418 0 : ++CountNodes;
1419 0 : AllNodes(CountNodes) = state.dataMixerComponent->MixerCond(Count2).InletNode(Loop);
1420 : }
1421 : }
1422 0 : } break;
1423 116 : case AirLoopHVACCompType::ReturnPlenum: {
1424 456 : for (int Count2 = 1; Count2 <= state.dataZonePlenum->NumZoneReturnPlenums; ++Count2) {
1425 340 : if (state.dataZoneEquip->ReturnAirPath(BCount).ComponentName(Count3) !=
1426 340 : state.dataZonePlenum->ZoneRetPlenCond(Count2).ZonePlenumName) {
1427 224 : continue;
1428 : }
1429 677 : for (int Loop = 1; Loop <= state.dataZonePlenum->ZoneRetPlenCond(Count2).NumInletNodes; ++Loop) {
1430 561 : ++CountNodes;
1431 561 : AllNodes(CountNodes) = state.dataZonePlenum->ZoneRetPlenCond(Count2).InletNode(Loop);
1432 : }
1433 : }
1434 116 : } break;
1435 0 : default: // This already validated in GetReturnAirPath
1436 0 : break;
1437 : }
1438 : }
1439 : }
1440 1208 : if (CountNodes > 0) {
1441 : static constexpr std::string_view Format_705("! <#Nodes on Return Air Path>,<Number of Nodes>");
1442 1208 : print(state.files.bnd, "{}\n", Format_705);
1443 : static constexpr std::string_view Format_706("! <Return Air Path Node>,<Node Type>,<Node Count>,<Node Name>,<AirLoopHVAC Name>");
1444 1208 : print(state.files.bnd, "{}\n", Format_706);
1445 1208 : print(state.files.bnd, " #Nodes on Return Air Path,{}\n", CountNodes);
1446 6304 : for (int Count2 = 1; Count2 <= CountNodes; ++Count2) {
1447 5096 : if (Count2 == 1) {
1448 1208 : print(state.files.bnd,
1449 : " Return Air Path Node,Outlet Node,{},{},{}\n",
1450 : Count2,
1451 1208 : state.dataLoopNodes->NodeID(AllNodes(Count2)),
1452 : PrimaryAirLoopName);
1453 : } else {
1454 3888 : print(state.files.bnd,
1455 : " Return Air Path Node,Inlet Node,{},{},{}\n",
1456 : Count2,
1457 3888 : state.dataLoopNodes->NodeID(AllNodes(Count2)),
1458 : PrimaryAirLoopName);
1459 : }
1460 : }
1461 : }
1462 : // Determine Air Loop this Return Air Path is on
1463 6550 : for (int Count2 = 1; Count2 <= state.dataHVACGlobal->NumPrimaryAirSys; ++Count2) {
1464 6550 : if (state.dataAirLoop->AirToZoneNodeInfo(Count2).NumReturnNodes > 0) {
1465 6550 : if (AllNodes(1) == state.dataAirLoop->AirToZoneNodeInfo(Count2).ZoneEquipReturnNodeNum(1)) {
1466 1208 : const int WAirLoop = Count2;
1467 1208 : ValRetAPaths(_, WAirLoop) = 0;
1468 1208 : ValRetAPaths({1, CountNodes}, WAirLoop) = AllNodes({1, CountNodes});
1469 1208 : break;
1470 : }
1471 : } else {
1472 0 : ShowWarningError(state,
1473 0 : format("TestReturnAirPathIntegrity: Air Loop has no Zone Equipment Return Node={}",
1474 0 : state.dataAirLoop->AirToZoneNodeInfo(Count2).AirLoopName));
1475 : }
1476 : }
1477 : }
1478 :
1479 800 : AllNodes.deallocate();
1480 :
1481 800 : if (state.dataMixerComponent->NumMixers == 0) {
1482 389 : if (state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "AirLoopHVAC:ZoneMixer") > 0) {
1483 0 : MixerComponent::GetMixerInput(state);
1484 : }
1485 : }
1486 800 : if (state.dataZonePlenum->NumZoneSupplyPlenums == 0 && state.dataZonePlenum->NumZoneReturnPlenums == 0) {
1487 605 : if (state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "AirLoopHVAC:ReturnPlenum") > 0) {
1488 0 : ZonePlenum::GetZonePlenumInput(state);
1489 : }
1490 : }
1491 :
1492 : // now the reverse. is every zone Mixer and Return plenum on Return air path
1493 800 : FoundReturnPlenum.dimension(state.dataZonePlenum->NumZoneReturnPlenums, false);
1494 800 : FoundZoneMixer.dimension(state.dataMixerComponent->NumMixers, false);
1495 800 : FoundNames.allocate(state.dataZonePlenum->NumZoneReturnPlenums);
1496 1085 : for (int Count1 = 1; Count1 <= state.dataZonePlenum->NumZoneReturnPlenums; ++Count1) {
1497 1104 : for (int BCount = 1; BCount <= state.dataZoneEquip->NumReturnAirPaths; ++BCount) {
1498 1978 : for (int Count = 1; Count <= state.dataZoneEquip->ReturnAirPath(BCount).NumOfComponents; ++Count) {
1499 1443 : if (state.dataZonePlenum->ZoneRetPlenCond(Count1).ZonePlenumName != state.dataZoneEquip->ReturnAirPath(BCount).ComponentName(Count) ||
1500 284 : state.dataZoneEquip->ReturnAirPath(BCount).ComponentType(Count) != "AIRLOOPHVAC:RETURNPLENUM") {
1501 875 : continue;
1502 : }
1503 284 : if (FoundReturnPlenum(Count1)) {
1504 0 : ShowSevereError(
1505 : state,
1506 0 : format("AirLoopHVAC:ReturnPlenum=\"{}\", duplicate entry.", state.dataZonePlenum->ZoneRetPlenCond(Count1).ZonePlenumName));
1507 0 : ShowContinueError(state, format("already exists on AirLoopHVAC:ReturnPath=\"{}\".", FoundNames(Count1)));
1508 0 : ErrFound = true;
1509 : } else {
1510 : // record use
1511 284 : FoundReturnPlenum(Count1) = true;
1512 284 : FoundNames(Count1) = state.dataZoneEquip->ReturnAirPath(BCount).Name;
1513 : }
1514 : }
1515 : }
1516 285 : if (PurchasedAirManager::CheckPurchasedAirForReturnPlenum(state, Count1)) {
1517 1 : FoundReturnPlenum(Count1) = true;
1518 : }
1519 : }
1520 800 : FoundNames.deallocate();
1521 800 : FoundNames.allocate(state.dataMixerComponent->NumMixers);
1522 1903 : for (int Count1 = 1; Count1 <= state.dataMixerComponent->NumMixers; ++Count1) {
1523 12708 : for (int BCount = 1; BCount <= state.dataZoneEquip->NumReturnAirPaths; ++BCount) {
1524 23719 : for (int Count = 1; Count <= state.dataZoneEquip->ReturnAirPath(BCount).NumOfComponents; ++Count) {
1525 13154 : if (state.dataMixerComponent->MixerCond(Count1).MixerName != state.dataZoneEquip->ReturnAirPath(BCount).ComponentName(Count) ||
1526 1040 : state.dataZoneEquip->ReturnAirPath(BCount).ComponentType(Count) != "AIRLOOPHVAC:ZONEMIXER") {
1527 11074 : continue;
1528 : }
1529 1040 : if (FoundZoneMixer(Count1)) {
1530 0 : ShowSevereError(state,
1531 0 : format("AirLoopHVAC:ZoneMixer=\"{}\", duplicate entry.", state.dataMixerComponent->MixerCond(Count1).MixerName));
1532 0 : ShowContinueError(state, format("already exists on AirLoopHVAC:ReturnPath=\"{}\".", FoundNames(Count1)));
1533 0 : ErrFound = true;
1534 : } else {
1535 : // record use
1536 1040 : FoundZoneMixer(Count1) = true;
1537 1040 : FoundNames(Count1) = state.dataZoneEquip->ReturnAirPath(BCount).Name;
1538 : }
1539 : }
1540 : }
1541 1103 : if (!FoundZoneMixer(Count1)) { // could be as child on other items
1542 : // PIU Units
1543 63 : if (PoweredInductionUnits::PIUnitHasMixer(state, state.dataMixerComponent->MixerCond(Count1).MixerName)) {
1544 57 : FoundZoneMixer(Count1) = true;
1545 : }
1546 : }
1547 1103 : if (!FoundZoneMixer(Count1)) { // could be as child on other items
1548 : // fourPipeInduction units
1549 6 : if (HVACSingleDuctInduc::FourPipeInductionUnitHasMixer(state, state.dataMixerComponent->MixerCond(Count1).MixerName)) {
1550 4 : FoundZoneMixer(Count1) = true;
1551 : }
1552 : }
1553 1103 : if (!FoundZoneMixer(Count1)) { // could be as child on other items
1554 : // Exhaust Systems
1555 2 : if (ExhaustAirSystemManager::ExhaustSystemHasMixer(state, state.dataMixerComponent->MixerCond(Count1).MixerName)) {
1556 2 : FoundZoneMixer(Count1) = true;
1557 : }
1558 : }
1559 : }
1560 800 : FoundNames.deallocate();
1561 :
1562 800 : if (!all(FoundReturnPlenum)) {
1563 0 : for (int Count1 = 1; Count1 <= state.dataZonePlenum->NumZoneReturnPlenums; ++Count1) {
1564 0 : if (FoundReturnPlenum(Count1)) {
1565 0 : continue;
1566 : }
1567 0 : ShowSevereError(state,
1568 0 : format("AirLoopHVAC:ReturnPlenum=\"{}\", not found on any AirLoopHVAC:ReturnPath.",
1569 0 : state.dataZonePlenum->ZoneRetPlenCond(Count1).ZonePlenumName));
1570 : }
1571 : }
1572 :
1573 800 : if (!all(FoundZoneMixer)) {
1574 0 : for (int Count1 = 1; Count1 <= state.dataMixerComponent->NumMixers; ++Count1) {
1575 0 : if (FoundZoneMixer(Count1)) {
1576 0 : continue;
1577 : }
1578 0 : ShowSevereError(state,
1579 0 : format("AirLoopHVAC:ZoneMixer=\"{}\", not found on any AirLoopHVAC:ReturnPath, AirLoopHVAC:ExhaustSystem, "
1580 : "AirTerminal:SingleDuct:SeriesPIU:Reheat,",
1581 0 : state.dataMixerComponent->MixerCond(Count1).MixerName));
1582 0 : ShowContinueError(state, "AirTerminal:SingleDuct:ParallelPIU:Reheat or AirTerminal:SingleDuct:ConstantVolume:FourPipeInduction.");
1583 : }
1584 : }
1585 :
1586 800 : FoundReturnPlenum.deallocate();
1587 800 : FoundZoneMixer.deallocate();
1588 :
1589 800 : if (ErrFound) {
1590 0 : ShowSevereError(state, "Return Air Path(s) did not pass integrity testing");
1591 : } else {
1592 2400 : ShowMessage(state, "All Return Air Paths passed integrity testing");
1593 : }
1594 800 : }
1595 :
1596 37753440 : void CalcComponentSensibleLatentOutput(Real64 const MassFlow, // air mass flow rate, {kg/s}
1597 : Real64 const TDB2, // dry-bulb temperature at state 2 {C}
1598 : Real64 const W2, // humidity ratio at state 2
1599 : Real64 const TDB1, // dry-bulb temperature at at state 1 {C}
1600 : Real64 const W1, // humidity ratio at state 1
1601 : Real64 &SensibleOutput, // sensible output rate (state 2 -> State 1), {W}
1602 : Real64 &LatentOutput, // latent output rate (state 2 -> State 1), {W}
1603 : Real64 &TotalOutput // total = sensible + latent putput rate (state 2 -> State 1), {W}
1604 : )
1605 : {
1606 :
1607 : // Purpose:
1608 : // returns total, sensible and latent heat rate of change of moist air transitioning
1609 : // between two states. The moist air energy transfer can be cooling or heating process
1610 : // across a cooling, a heating coil, or an HVAC component.
1611 :
1612 : // Methodology:
1613 : // Q_total = m_dot * (h2 - h1)
1614 : // Q_sensible = m_dot * Psychrometrics::PsyDeltaHSenFnTdb2W2Tdb1W1(TDB2, W2, TDB1, W1);
1615 : // or Q_sensible = m_dot * cp_moistair_MinHumRat * (TDB2 - TDB1)
1616 : // cp_moistair_MinHumRat = Psychrometrics::PsyCpAirFnW(min(W2, W1));
1617 : // Q_latent = Q_total - Q_latent;
1618 :
1619 37753440 : TotalOutput = 0.0;
1620 37753440 : LatentOutput = 0.0;
1621 37753440 : SensibleOutput = 0.0;
1622 37753440 : if (MassFlow > 0.0) {
1623 36342106 : TotalOutput = MassFlow * (Psychrometrics::PsyHFnTdbW(TDB2, W2) - Psychrometrics::PsyHFnTdbW(TDB1, W1)); // total addition/removal rate, {W};
1624 36342106 : SensibleOutput = MassFlow * Psychrometrics::PsyDeltaHSenFnTdb2W2Tdb1W1(TDB2, W2, TDB1, W1); // sensible addition/removal rate, {W};
1625 36342106 : LatentOutput = TotalOutput - SensibleOutput; // latent addition/removal rate, {W}
1626 : }
1627 37753440 : }
1628 :
1629 75654879 : void CalcZoneSensibleLatentOutput(Real64 const MassFlow, // air mass flow rate, {kg/s}
1630 : Real64 const TDBEquip, // dry-bulb temperature at equipment outlet {C}
1631 : Real64 const WEquip, // humidity ratio at equipment outlet
1632 : Real64 const TDBZone, // dry-bulb temperature at zone air node {C}
1633 : Real64 const WZone, // humidity ratio at zone air node
1634 : Real64 &SensibleOutput, // sensible output rate (state 2 -> State 1), {W}
1635 : Real64 &LatentOutput, // latent output rate (state 2 -> State 1), {W}
1636 : Real64 &TotalOutput // total = sensible + latent putput rate (state 2 -> State 1), {W}
1637 : )
1638 : {
1639 :
1640 : // Purpose:
1641 : // returns total, sensible and latent heat rate of transfer between the supply air zone inlet
1642 : // node and zone air node. The moist air energy transfer can be cooling or heating depending
1643 : // on the supply air zone inlet node and zone air node conditions.
1644 :
1645 : // Methodology:
1646 : // Q_total = m_dot * (hEquip - hZone)
1647 : // Q_sensible = m_dot * Psychrometrics::PsyDeltaHSenFnTdbEquipTdbWZone(TDBEquip, TDBZone, WZone);
1648 : // or Q_sensible = m_dot * cp_moistair_zoneHumRat * (TDBEquip - TDBZone)
1649 : // cp_moistair_zoneHumRat = Psychrometrics::PsyCpAirFnW(WZone);
1650 : // Q_latent = Q_total - Q_latent;
1651 :
1652 75654879 : TotalOutput = 0.0;
1653 75654879 : LatentOutput = 0.0;
1654 75654879 : SensibleOutput = 0.0;
1655 75654879 : if (MassFlow > 0.0) {
1656 61402707 : TotalOutput = MassFlow * (Psychrometrics::PsyHFnTdbW(TDBEquip, WEquip) -
1657 61402707 : Psychrometrics::PsyHFnTdbW(TDBZone, WZone)); // total addition/removal rate, {W};
1658 61402707 : SensibleOutput = MassFlow * Psychrometrics::PsyDeltaHSenFnTdb2Tdb1W(TDBEquip, TDBZone, WZone); // sensible addition/removal rate, {W};
1659 61402707 : LatentOutput = TotalOutput - SensibleOutput; // latent addition/removal rate, {W}
1660 : }
1661 75654879 : }
1662 :
1663 54914772 : Real64 calcZoneSensibleOutput(Real64 const MassFlow, // air mass flow rate, {kg/s}
1664 : Real64 const TDBEquip, // dry-bulb temperature at equipment outlet {C}
1665 : Real64 const TDBZone, // dry-bulb temperature at zone air node {C}
1666 : Real64 const WZone // humidity ratio at zone air node
1667 : )
1668 : {
1669 :
1670 : // Purpose:
1671 : // returns sensible heat rate of transfer between the supply air zone inlet node and
1672 : // zone air node. The moist air energy transfer can be cooling or heating depending
1673 : // on the supply air zone inlet node and zone air node conditions.
1674 :
1675 : // Methodology:
1676 : // Q_sensible = m_dot * Psychrometrics::PsyDeltaHSenFnTdbEquipTdbWZone(TDBEquip, TDBZone, WZone);
1677 : // or Q_sensible = m_dot * cp_moistair_zoneHumRat * (TDBEquip - TDBZone)
1678 : // cp_moistair_zoneHumRat = Psychrometrics::PsyCpAirFnW(WZone);
1679 :
1680 54914772 : Real64 sensibleOutput = 0.0; // sensible output rate (state 2 -> State 1), {W}
1681 54914772 : if (MassFlow > 0.0) {
1682 44465158 : sensibleOutput = MassFlow * Psychrometrics::PsyDeltaHSenFnTdb2Tdb1W(TDBEquip, TDBZone, WZone); // sensible addition/removal rate, {W};
1683 : }
1684 54914772 : return sensibleOutput;
1685 : }
1686 : } // namespace EnergyPlus
|