Line data Source code
1 : // EnergyPlus, Copyright (c) 1996-2024, 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 11785742 : 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 11785742 : int constexpr iReverseAction = 1;
168 11785742 : 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 11785742 : 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 11785742 : "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 11785742 : auto &ZoneInterHalf = state.dataGeneralRoutines->ZoneInterHalf;
194 11785742 : auto &ZoneController = state.dataGeneralRoutines->ZoneController;
195 :
196 11785742 : if (ControlCompTypeNum != 0) {
197 11783497 : SimCompNum = ControlCompTypeNum;
198 : } else {
199 2245 : SimCompNum = Util::FindItemInSortedList(CompType, ListOfComponents, NumComponents);
200 2245 : ControlCompTypeNum = SimCompNum;
201 : }
202 :
203 11785742 : int Iter = 0; // Iteration limit for the interval halving process
204 11785742 : bool Converged = false;
205 11785742 : bool WaterCoilAirFlowControl = false; // True if controlling air flow through water coil, water flow fixed
206 11785742 : Real64 LoadMet = 0.0; // Actual output of unit (watts)
207 11785742 : 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 11785742 : ZoneController.SetPoint = 0.0;
212 :
213 : // Set to converged controller
214 11785742 : ZoneInterHalf.MaxFlowCalc = true;
215 11785742 : ZoneInterHalf.MinFlowCalc = false;
216 11785742 : ZoneInterHalf.NormFlowCalc = false;
217 11785742 : ZoneInterHalf.MinFlowResult = false;
218 11785742 : ZoneInterHalf.MaxResult = 1.0;
219 11785742 : ZoneInterHalf.MinResult = 0.0;
220 :
221 : // Start the Solution Iteration
222 87403761 : while (!Converged) {
223 :
224 83059555 : if (FirstHVACIteration) {
225 34793782 : state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMaxAvail = MaxFlow;
226 34793782 : state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMinAvail = MinFlow;
227 : // Check to make sure that the Minimum Flow rate is less than the max.
228 34793782 : 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 83059555 : if ((SimCompNum == 3) && (!present(AirMassFlow))) {
239 6031080 : ZoneController.MaxSetPoint = state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMaxAvail;
240 6031080 : ZoneController.MinSetPoint = state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMinAvail;
241 : } else {
242 77028475 : ZoneController.MaxSetPoint =
243 77028475 : min(state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMaxAvail, state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMax);
244 77028475 : ZoneController.MinSetPoint =
245 77028475 : 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 83059555 : if (ZoneInterHalf.MaxFlowCalc) {
249 11785742 : ZoneController.CalculatedSetPoint = ZoneController.MaxSetPoint;
250 11785742 : ZoneInterHalf.MaxFlow = ZoneController.MaxSetPoint;
251 11785742 : ZoneInterHalf.MaxFlowCalc = false;
252 11785742 : ZoneInterHalf.MinFlowCalc = true;
253 : // Record the maximum flow rates and set the flow to the minimum and find results
254 71273813 : } else if (ZoneInterHalf.MinFlowCalc) {
255 11686943 : ZoneInterHalf.MaxResult = ZoneController.SensedValue;
256 11686943 : ZoneController.CalculatedSetPoint = ZoneController.MinSetPoint;
257 11686943 : ZoneInterHalf.MinFlow = ZoneController.MinSetPoint;
258 11686943 : ZoneInterHalf.MinFlowCalc = false;
259 11686943 : ZoneInterHalf.MinFlowResult = true;
260 : // Record the minimum results and set flow to half way between the max and min and find results
261 59586870 : } else if (ZoneInterHalf.MinFlowResult) {
262 11636955 : ZoneInterHalf.MinResult = ZoneController.SensedValue;
263 11636955 : HalvingPrec = (ZoneInterHalf.MaxResult - ZoneInterHalf.MinResult) * iter_fac;
264 11636955 : ZoneInterHalf.MidFlow = (ZoneInterHalf.MaxFlow + ZoneInterHalf.MinFlow) / 2.0;
265 11636955 : ZoneController.CalculatedSetPoint = (ZoneInterHalf.MaxFlow + ZoneInterHalf.MinFlow) / 2.0;
266 11636955 : ZoneInterHalf.MinFlowResult = false;
267 11636955 : ZoneInterHalf.NormFlowCalc = true;
268 : // Record the Mid results and check all possibilities and start interval halving procedure
269 47949915 : } else if (ZoneInterHalf.NormFlowCalc) {
270 47949915 : ZoneInterHalf.MidResult = ZoneController.SensedValue;
271 :
272 : // First check to see if the component is running; if not converge and return
273 47949915 : if (ZoneInterHalf.MaxResult == ZoneInterHalf.MinResult) {
274 : // Set to converged controller
275 1150382 : ZoneInterHalf.MaxFlowCalc = true;
276 1150382 : ZoneInterHalf.MinFlowCalc = false;
277 1150382 : ZoneInterHalf.NormFlowCalc = false;
278 1150382 : ZoneInterHalf.MinFlowResult = false;
279 1150382 : ZoneInterHalf.MaxResult = 1.0;
280 1150382 : ZoneInterHalf.MinResult = 0.0;
281 1150382 : if ((SimCompNum >= 4) && (SimCompNum <= 6)) { // hot water baseboards use min flow
282 18719 : ZoneController.CalculatedSetPoint = 0.0; // CR7253
283 : } else {
284 1131663 : ZoneController.CalculatedSetPoint = ZoneInterHalf.MaxFlow; // CR7253
285 : }
286 : // Set the Actuated node MassFlowRate with zero value
287 1150382 : if (plantLoc.loopNum) { // this is a plant component
288 498674 : PlantUtilities::SetActuatedBranchFlowRate(state,
289 498674 : ZoneController.CalculatedSetPoint,
290 : ActuatedNode,
291 : plantLoc,
292 : false); // Autodesk:OPTIONAL LoopSide, BranchIndex used without PRESENT check
293 : } else { // assume not a plant component
294 651708 : state.dataLoopNodes->Node(ActuatedNode).MassFlowRate = ZoneController.CalculatedSetPoint;
295 : }
296 1150382 : 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 46799533 : if (ZoneInterHalf.MaxResult <= ZoneInterHalf.MinResult) {
305 138212 : if (WaterCoilAirFlowControl) {
306 138169 : ZoneController.CalculatedSetPoint = ZoneInterHalf.MaxFlow;
307 : } else {
308 43 : ZoneController.CalculatedSetPoint = ZoneInterHalf.MinFlow;
309 : }
310 : // set to converged controller
311 138212 : Converged = true;
312 138212 : ZoneInterHalf.MaxFlowCalc = true;
313 138212 : ZoneInterHalf.MinFlowCalc = false;
314 138212 : ZoneInterHalf.NormFlowCalc = false;
315 138212 : ZoneInterHalf.MinFlowResult = false;
316 138212 : ZoneInterHalf.MaxResult = 1.0;
317 138212 : 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 46661321 : if (ZoneController.SetPoint <= ZoneInterHalf.MinResult) {
323 6995 : ZoneController.CalculatedSetPoint = ZoneInterHalf.MinFlow;
324 : // Set to Converged Controller
325 6995 : Converged = true;
326 6995 : ZoneInterHalf.MaxFlowCalc = true;
327 6995 : ZoneInterHalf.MinFlowCalc = false;
328 6995 : ZoneInterHalf.NormFlowCalc = false;
329 6995 : ZoneInterHalf.MinFlowResult = false;
330 6995 : ZoneInterHalf.MaxResult = 1.0;
331 6995 : ZoneInterHalf.MinResult = 0.0;
332 : // Then check if too hot and if so set it to the maximum flow
333 46654326 : } else if (ZoneController.SetPoint >= ZoneInterHalf.MaxResult) {
334 4198982 : ZoneController.CalculatedSetPoint = ZoneInterHalf.MaxFlow;
335 : // Set to Converged Controller
336 4198982 : Converged = true;
337 4198982 : ZoneInterHalf.MaxFlowCalc = true;
338 4198982 : ZoneInterHalf.MinFlowCalc = false;
339 4198982 : ZoneInterHalf.NormFlowCalc = false;
340 4198982 : ZoneInterHalf.MinFlowResult = false;
341 4198982 : ZoneInterHalf.MaxResult = 1.0;
342 4198982 : ZoneInterHalf.MinResult = 0.0;
343 : // If between the max and mid set to new flow and raise min to mid
344 42455344 : } else if (ZoneController.SetPoint >= ZoneInterHalf.MidResult) {
345 18425197 : ZoneController.CalculatedSetPoint = (ZoneInterHalf.MaxFlow + ZoneInterHalf.MidFlow) / 2.0;
346 18425197 : ZoneInterHalf.MinFlow = ZoneInterHalf.MidFlow;
347 18425197 : ZoneInterHalf.MinResult = ZoneInterHalf.MidResult;
348 18425197 : 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 24030147 : ZoneController.CalculatedSetPoint = (ZoneInterHalf.MinFlow + ZoneInterHalf.MidFlow) / 2.0;
352 24030147 : ZoneInterHalf.MaxFlow = ZoneInterHalf.MidFlow;
353 24030147 : ZoneInterHalf.MaxResult = ZoneInterHalf.MidResult;
354 24030147 : 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 81909173 : 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 81909173 : } 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 81909173 : 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 81909173 : if (plantLoc.loopNum) { // this is a plant component
396 76529801 : PlantUtilities::SetActuatedBranchFlowRate(state,
397 76529801 : 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 5379372 : state.dataLoopNodes->Node(ActuatedNode).MassFlowRate = ZoneController.CalculatedSetPoint;
403 : }
404 :
405 : // The denominator of the control signal should be no less than 100 watts
406 81909173 : Real64 Denom = sign(max(std::abs(QZnReq), 100.0), QZnReq);
407 81909173 : 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 81909173 : switch (SimCompNum) { // Tuned If block changed to switch
418 158452 : case ParallelPIUReheatNum: // 'AIRTERMINAL:SINGLEDUCT:PARALLELPIU:REHEAT'
419 : // simulate series piu reheat coil
420 158452 : WaterCoils::SimulateWaterCoilComponents(state, CompName, FirstHVACIteration, CompNum);
421 : // Calculate the control signal (the variable we are forcing to zero)
422 158452 : CpAir = Psychrometrics::PsyCpAirFnW(
423 158452 : state.dataLoopNodes->Node(TempOutNode).HumRat); // Autodesk:OPTIONAL TempInNode, TempOutNode used without PRESENT check
424 158452 : LoadMet = CpAir * state.dataLoopNodes->Node(TempOutNode).MassFlowRate *
425 158452 : (state.dataLoopNodes->Node(TempOutNode).Temp -
426 158452 : state.dataLoopNodes->Node(TempInNode).Temp); // Autodesk:OPTIONAL TempInNode, TempOutNode used without PRESENT check
427 158452 : ZoneController.SensedValue = (LoadMet - QZnReq) / Denom;
428 158452 : break;
429 :
430 963794 : case SeriesPIUReheatNum: // 'AIRTERMINAL:SINGLEDUCT:SERIESPIU:REHEAT'
431 : // simulate series piu reheat coil
432 963794 : WaterCoils::SimulateWaterCoilComponents(state, CompName, FirstHVACIteration, CompNum);
433 : // Calculate the control signal (the variable we are forcing to zero)
434 963794 : CpAir = Psychrometrics::PsyCpAirFnW(
435 963794 : state.dataLoopNodes->Node(TempOutNode).HumRat); // Autodesk:OPTIONAL TempInNode, TempOutNode used without PRESENT check
436 963794 : LoadMet = CpAir * state.dataLoopNodes->Node(TempOutNode).MassFlowRate *
437 963794 : (state.dataLoopNodes->Node(TempOutNode).Temp -
438 963794 : state.dataLoopNodes->Node(TempInNode).Temp); // Autodesk:OPTIONAL TempInNode, TempOutNode used without PRESENT check
439 963794 : ZoneController.SensedValue = (LoadMet - QZnReq) / Denom;
440 963794 : break;
441 :
442 78184195 : case HeatingCoilWaterNum: // 'COIL:HEATING:WATER'
443 : // Simulate reheat coil for the VAV system
444 78184195 : WaterCoils::SimulateWaterCoilComponents(state, CompName, FirstHVACIteration, CompNum);
445 : // Calculate the control signal (the variable we are forcing to zero)
446 78184195 : CpAir = Psychrometrics::PsyCpAirFnW(state.dataLoopNodes->Node(TempOutNode).HumRat);
447 78184195 : if (present(AirMassFlow)) {
448 72804823 : LoadMet = AirMassFlow * CpAir * state.dataLoopNodes->Node(TempOutNode).Temp;
449 72804823 : ZoneController.SensedValue = (LoadMet - QZnReq) / Denom;
450 : } else {
451 5379372 : WaterCoilAirFlowControl = true;
452 5379372 : LoadMet = state.dataLoopNodes->Node(TempOutNode).MassFlowRate * CpAir *
453 5379372 : (state.dataLoopNodes->Node(TempOutNode).Temp -
454 5379372 : state.dataLoopNodes->Node(TempInNode).Temp); // Autodesk:OPTIONAL TempInNode, TempOutNode used without PRESENT check
455 5379372 : ZoneController.SensedValue = (LoadMet - QZnReq) / Denom;
456 : }
457 78184195 : break;
458 :
459 728696 : case BBWaterConvOnlyNum: // 'ZONEHVAC:BASEBOARD:CONVECTIVE:WATER'
460 : // Simulate baseboard
461 728696 : BaseboardRadiator::SimHWConvective(state, CompNum, LoadMet);
462 : // Calculate the control signal (the variable we are forcing to zero)
463 728696 : ZoneController.SensedValue = (LoadMet - QZnReq) / Denom;
464 728696 : 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 160405 : case BBWaterRadConvNum: // 'ZONEHVAC:BASEBOARD:RADIANTCONVECTIVE:WATER'
474 : // Simulate baseboard
475 160405 : HWBaseboardRadiator::CalcHWBaseboard(state, CompNum, LoadMet);
476 : // Calculate the control signal (the variable we are forcing to zero)
477 160405 : ZoneController.SensedValue = (LoadMet - QZnReq) / Denom;
478 160405 : 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 427070 : case VentilatedSlabNum: // 'ZONEHVAC:VENTILATEDSLAB'
510 : // Simulate unit ventilator components
511 427070 : VentilatedSlab::CalcVentilatedSlabComps(state, CompNum, FirstHVACIteration, LoadMet);
512 : // Calculate the control signal (the variable we are forcing to zero)
513 427070 : ZoneController.SensedValue = (LoadMet - QZnReq) / Denom;
514 427070 : 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 81909173 : if (std::abs(ZoneController.SensedValue) <= ControlOffset || std::abs(ZoneController.SensedValue) <= HalvingPrec) {
523 : // Set to converged controller
524 6225613 : ZoneInterHalf.MaxFlowCalc = true;
525 6225613 : ZoneInterHalf.MinFlowCalc = false;
526 6225613 : ZoneInterHalf.NormFlowCalc = false;
527 6225613 : ZoneInterHalf.MinFlowResult = false;
528 6225613 : ZoneInterHalf.MaxResult = 1.0;
529 6225613 : ZoneInterHalf.MinResult = 0.0;
530 6225613 : break;
531 : }
532 75683560 : if (!Converged) {
533 71339354 : bool BBConvergeCheckFlag = BBConvergeCheck(SimCompNum, ZoneInterHalf.MaxFlow, ZoneInterHalf.MinFlow);
534 71339354 : if (BBConvergeCheckFlag) {
535 : // Set to converged controller
536 65541 : ZoneInterHalf.MaxFlowCalc = true;
537 65541 : ZoneInterHalf.MinFlowCalc = false;
538 65541 : ZoneInterHalf.NormFlowCalc = false;
539 65541 : ZoneInterHalf.MinFlowResult = false;
540 65541 : ZoneInterHalf.MaxResult = 1.0;
541 65541 : ZoneInterHalf.MinResult = 0.0;
542 65541 : break;
543 : }
544 : }
545 :
546 75618019 : ++Iter;
547 75618019 : 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 75618019 : } else if (Iter > MaxIter * 2) {
576 0 : break;
577 : }
578 :
579 : } // End of the Convergence Iteration
580 : }
581 :
582 71339354 : 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 71339354 : if (SimCompNum != BBSteamRadConvNum && SimCompNum != BBWaterRadConvNum) {
606 : // For all zone equipment except radiant/convective baseboard (steam and water) units:
607 71159565 : BBConvergeCheck = false;
608 : } else {
609 : // For steam and water radiant/convective baseboard units:
610 179789 : if ((MaxFlow - MinFlow) > BBIterLimit) {
611 114248 : BBConvergeCheck = false;
612 : } else {
613 65541 : BBConvergeCheck = true;
614 : }
615 : }
616 :
617 71339354 : return BBConvergeCheck;
618 : }
619 :
620 6884 : 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 6884 : 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 6884 : }
648 :
649 5052 : 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 5052 : AirLoopWasSized = false;
657 5052 : if (state.dataSize->SysSizingRunDone) {
658 25422 : for (int ThisAirSysSizineInputLoop = 1; ThisAirSysSizineInputLoop <= state.dataSize->NumSysSizInput; ++ThisAirSysSizineInputLoop) {
659 25414 : if (state.dataSize->SysSizInput(ThisAirSysSizineInputLoop).AirLoopNum == AirLoopNum) {
660 4693 : AirLoopWasSized = true;
661 4693 : break;
662 : }
663 : }
664 : }
665 5052 : }
666 :
667 13583 : 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 13583 : 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 13583 : }
695 :
696 638 : 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 638 : ZoneWasSized = false;
710 638 : if (state.dataSize->ZoneSizingRunDone) {
711 8442 : for (int ThisSizingInput = 1; ThisSizingInput <= state.dataSize->NumZoneSizingInput; ++ThisSizingInput) {
712 8442 : if (state.dataSize->ZoneSizingInput(ThisSizingInput).ZoneNum == ZoneNum) {
713 632 : ZoneWasSized = true;
714 632 : break;
715 : }
716 : }
717 : }
718 638 : }
719 :
720 38842 : 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 38842 : IsNotOK = false;
745 :
746 38842 : int ItemNum = state.dataInputProcessing->inputProcessor->getObjectItemNum(state, std::string{CompType}, CompName);
747 :
748 38842 : 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 38842 : } 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 38842 : }
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 4880276 : void CalcBasinHeaterPower(EnergyPlusData &state,
800 : Real64 const Capacity, // Basin heater capacity per degree C below setpoint (W/C)
801 : int const SchedulePtr, // Pointer to 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 4880276 : 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 4880276 : if (SchedulePtr > 0) {
825 23493 : Real64 BasinHeaterSch = ScheduleManager::GetCurrentScheduleValue(state, SchedulePtr);
826 23493 : if (Capacity > 0.0 && BasinHeaterSch > 0.0) {
827 23487 : 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 4856783 : if (Capacity > 0.0) {
832 192589 : Power = max(0.0, Capacity * (SetPointTemp - state.dataEnvrn->OutDryBulbTemp));
833 : }
834 : }
835 4880276 : }
836 :
837 795 : 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 795 : Array2D_int ValRetAPaths;
850 795 : Array2D_int NumRAPNodes;
851 795 : Array2D_int ValSupAPaths;
852 795 : Array2D_int NumSAPNodes;
853 :
854 795 : NumSAPNodes.allocate(state.dataLoopNodes->NumOfNodes, state.dataHVACGlobal->NumPrimaryAirSys);
855 795 : NumRAPNodes.allocate(state.dataLoopNodes->NumOfNodes, state.dataHVACGlobal->NumPrimaryAirSys);
856 795 : ValRetAPaths.allocate(state.dataLoopNodes->NumOfNodes, state.dataHVACGlobal->NumPrimaryAirSys);
857 795 : ValSupAPaths.allocate(state.dataLoopNodes->NumOfNodes, state.dataHVACGlobal->NumPrimaryAirSys);
858 795 : NumSAPNodes = 0;
859 795 : NumRAPNodes = 0;
860 795 : ValRetAPaths = 0;
861 795 : ValSupAPaths = 0;
862 :
863 795 : TestSupplyAirPathIntegrity(state, errFlag);
864 795 : if (errFlag) ErrFound = true;
865 795 : TestReturnAirPathIntegrity(state, errFlag, ValRetAPaths);
866 795 : if (errFlag) ErrFound = true;
867 :
868 : // Final tests, look for duplicate nodes
869 2002 : for (int Loop = 1; Loop <= state.dataHVACGlobal->NumPrimaryAirSys; ++Loop) {
870 1207 : if (ValRetAPaths(1, Loop) != 0) continue;
871 13 : if (state.dataAirLoop->AirToZoneNodeInfo(Loop).NumReturnNodes <= 0) continue;
872 13 : ValRetAPaths(1, Loop) = state.dataAirLoop->AirToZoneNodeInfo(Loop).ZoneEquipReturnNodeNum(1);
873 : }
874 :
875 2002 : for (int Loop = 1; Loop <= state.dataHVACGlobal->NumPrimaryAirSys; ++Loop) {
876 363250 : for (int Loop1 = 1; Loop1 <= state.dataLoopNodes->NumOfNodes; ++Loop1) {
877 362043 : int TestNode = ValRetAPaths(Loop1, Loop);
878 362043 : int Count = 0;
879 15315356 : for (int Loop2 = 1; Loop2 <= state.dataHVACGlobal->NumPrimaryAirSys; ++Loop2) {
880 49072819 : for (int Loop3 = 1; Loop3 <= state.dataLoopNodes->NumOfNodes; ++Loop3) {
881 49072819 : if (Loop2 == Loop && Loop1 == Loop3) continue; // Don't count test node
882 49066664 : if (ValRetAPaths(Loop3, Loop2) == 0) break;
883 34113351 : if (ValRetAPaths(Loop3, Loop2) == TestNode) ++Count;
884 : }
885 : }
886 362043 : if (Count > 0) {
887 0 : ShowSevereError(state, "Duplicate Node detected in Return Air Paths");
888 0 : ShowContinueError(state, format("Test Node={}", state.dataLoopNodes->NodeID(TestNode)));
889 0 : ShowContinueError(state, format("In Air Path={}", state.dataAirLoop->AirToZoneNodeInfo(Loop).AirLoopName));
890 0 : ErrFound = true;
891 : }
892 : }
893 : }
894 :
895 795 : NumSAPNodes.deallocate();
896 795 : NumRAPNodes.deallocate();
897 795 : ValRetAPaths.deallocate();
898 795 : ValSupAPaths.deallocate();
899 795 : }
900 :
901 795 : void TestSupplyAirPathIntegrity(EnergyPlusData &state, bool &ErrFound)
902 : {
903 :
904 : // SUBROUTINE INFORMATION:
905 : // AUTHOR Linda Lawrie
906 : // DATE WRITTEN March 2003
907 :
908 : // PURPOSE OF THIS SUBROUTINE:
909 : // This subroutine tests supply air path integrity and displays the loop for each branch.
910 : // Also, input and output nodes.
911 :
912 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
913 795 : std::string PrimaryAirLoopName; // Air Loop to which this supply air path is connected
914 795 : Array1D_bool FoundSupplyPlenum;
915 795 : Array1D_bool FoundZoneSplitter;
916 795 : Array1D_string FoundNames;
917 795 : int NumErr = 0; // Error Counter //Autodesk:Init Initialization added
918 :
919 : // Do by Paths
920 795 : ShowMessage(state, "Testing Individual Supply Air Path Integrity");
921 795 : ErrFound = false;
922 :
923 795 : print(state.files.bnd, "{}\n", "! ===============================================================");
924 : static constexpr std::string_view Format_700("! <#Supply Air Paths>,<Number of Supply Air Paths>");
925 795 : print(state.files.bnd, "{}\n", Format_700);
926 795 : print(state.files.bnd, " #Supply Air Paths,{}\n", state.dataZoneEquip->NumSupplyAirPaths);
927 : static constexpr std::string_view Format_702("! <Supply Air Path>,<Supply Air Path Count>,<Supply Air Path Name>,<AirLoopHVAC Name>");
928 795 : print(state.files.bnd, "{}\n", Format_702);
929 : static constexpr std::string_view Format_703("! <#Components on Supply Air Path>,<Number of Components>");
930 795 : print(state.files.bnd, "{}\n", Format_703);
931 : static constexpr std::string_view Format_704(
932 : "! <Supply Air Path Component>,<Component Count>,<Component Type>,<Component Name>,<AirLoopHVAC Name>");
933 795 : print(state.files.bnd, "{}\n", Format_704);
934 : static constexpr std::string_view Format_707("! <#Outlet Nodes on Supply Air Path Component>,<Number of Nodes>");
935 795 : print(state.files.bnd, "{}\n", Format_707);
936 : static constexpr std::string_view Format_708(
937 : "! <Supply Air Path Component Nodes>,<Node Count>,<Component Type>,<Component Name>,<Inlet Node Name>,<Outlet "
938 : "Node Name>,<AirLoopHVAC Name>");
939 795 : print(state.files.bnd, "{}\n", Format_708);
940 :
941 2006 : for (int BCount = 1; BCount <= state.dataZoneEquip->NumSupplyAirPaths; ++BCount) {
942 :
943 : // Determine which air loop this supply air path is connected to
944 1211 : int Found = 0;
945 6542 : for (int Count1 = 1; Count1 <= state.dataHVACGlobal->NumPrimaryAirSys; ++Count1) {
946 6542 : PrimaryAirLoopName = state.dataAirLoop->AirToZoneNodeInfo(Count1).AirLoopName;
947 6542 : Found = 0;
948 13100 : for (int Count2 = 1; Count2 <= state.dataAirLoop->AirToZoneNodeInfo(Count1).NumSupplyNodes; ++Count2) {
949 6558 : if (state.dataZoneEquip->SupplyAirPath(BCount).InletNodeNum ==
950 6558 : state.dataAirLoop->AirToZoneNodeInfo(Count1).ZoneEquipSupplyNodeNum(Count2))
951 1211 : Found = Count2;
952 : }
953 6542 : if (Found != 0) break;
954 : }
955 1211 : if (Found == 0) PrimaryAirLoopName = "**Unknown**";
956 :
957 1211 : print(state.files.bnd, " Supply Air Path,{},{},{}\n", BCount, state.dataZoneEquip->SupplyAirPath(BCount).Name, PrimaryAirLoopName);
958 1211 : print(state.files.bnd, " #Components on Supply Air Path,{}\n", state.dataZoneEquip->SupplyAirPath(BCount).NumOfComponents);
959 :
960 1211 : std::string AirPathNodeName = state.dataLoopNodes->NodeID(state.dataZoneEquip->SupplyAirPath(BCount).InletNodeNum);
961 :
962 2429 : for (int Count = 1; Count <= state.dataZoneEquip->SupplyAirPath(BCount).NumOfComponents; ++Count) {
963 :
964 1218 : print(state.files.bnd,
965 : " Supply Air Path Component,{},{},{},{}\n",
966 : Count,
967 1218 : state.dataZoneEquip->SupplyAirPath(BCount).ComponentType(Count),
968 1218 : state.dataZoneEquip->SupplyAirPath(BCount).ComponentName(Count),
969 : PrimaryAirLoopName);
970 :
971 : AirLoopHVACCompType CompType = static_cast<AirLoopHVACCompType>(
972 1218 : getEnumValue(AirLoopHVACCompTypeNamesUC, Util::makeUPPER(state.dataZoneEquip->SupplyAirPath(BCount).ComponentType(Count))));
973 :
974 1218 : switch (CompType) {
975 8 : case AirLoopHVACCompType::SupplyPlenum: {
976 20 : for (int Count2 = 1; Count2 <= state.dataZonePlenum->NumZoneSupplyPlenums; ++Count2) {
977 12 : if (state.dataZonePlenum->ZoneSupPlenCond(Count2).ZonePlenumName !=
978 12 : state.dataZoneEquip->SupplyAirPath(BCount).ComponentName(Count))
979 4 : continue;
980 8 : if (Count == 1 && AirPathNodeName != state.dataLoopNodes->NodeID(state.dataZonePlenum->ZoneSupPlenCond(Count2).InletNode)) {
981 0 : ShowSevereError(state, format("Error in AirLoopHVAC:SupplyPath={}", state.dataZoneEquip->SupplyAirPath(BCount).Name));
982 0 : ShowContinueError(state,
983 0 : format("For AirLoopHVAC:SupplyPlenum={}", state.dataZonePlenum->ZoneSupPlenCond(Count2).ZonePlenumName));
984 0 : ShowContinueError(state, format("Expected inlet node (supply air path)={}", AirPathNodeName));
985 0 : ShowContinueError(state,
986 0 : format("Encountered node name (supply plenum)={}",
987 0 : state.dataLoopNodes->NodeID(state.dataZonePlenum->ZoneSupPlenCond(Count2).OutletNode(1))));
988 0 : ErrFound = true;
989 0 : ++NumErr;
990 : }
991 8 : print(state.files.bnd,
992 : " #Outlet Nodes on Supply Air Path Component,{}\n",
993 8 : state.dataZonePlenum->ZoneSupPlenCond(Count2).NumOutletNodes);
994 20 : for (int Count1 = 1; Count1 <= state.dataZonePlenum->ZoneSupPlenCond(Count2).NumOutletNodes; ++Count1) {
995 12 : print(state.files.bnd,
996 : " Supply Air Path Component Nodes,{},{},{},{},{},{}\n",
997 : Count1,
998 12 : state.dataZoneEquip->SupplyAirPath(BCount).ComponentType(Count),
999 12 : state.dataZoneEquip->SupplyAirPath(BCount).ComponentName(Count),
1000 12 : state.dataLoopNodes->NodeID(state.dataZonePlenum->ZoneSupPlenCond(Count2).InletNode),
1001 12 : state.dataLoopNodes->NodeID(state.dataZonePlenum->ZoneSupPlenCond(Count2).OutletNode(Count1)),
1002 : PrimaryAirLoopName);
1003 : }
1004 : }
1005 8 : } break;
1006 1210 : case AirLoopHVACCompType::ZoneSplitter: {
1007 13098 : for (int Count2 = 1; Count2 <= state.dataSplitterComponent->NumSplitters; ++Count2) {
1008 11888 : if (state.dataSplitterComponent->SplitterCond(Count2).SplitterName !=
1009 11888 : state.dataZoneEquip->SupplyAirPath(BCount).ComponentName(Count))
1010 10678 : continue;
1011 1210 : if (Count == 1 && AirPathNodeName != state.dataLoopNodes->NodeID(state.dataSplitterComponent->SplitterCond(Count2).InletNode)) {
1012 0 : ShowSevereError(state, format("Error in AirLoopHVAC:SupplyPath={}", state.dataZoneEquip->SupplyAirPath(BCount).Name));
1013 0 : ShowContinueError(state,
1014 0 : format("For AirLoopHVAC:ZoneSplitter={}", state.dataSplitterComponent->SplitterCond(Count2).SplitterName));
1015 0 : ShowContinueError(state, format("Expected inlet node (supply air path)={}", AirPathNodeName));
1016 0 : ShowContinueError(state,
1017 0 : format("Encountered node name (zone splitter)={}",
1018 0 : state.dataLoopNodes->NodeID(state.dataSplitterComponent->SplitterCond(Count2).InletNode)));
1019 0 : ErrFound = true;
1020 0 : ++NumErr;
1021 : }
1022 1210 : print(state.files.bnd,
1023 : " #Outlet Nodes on Supply Air Path Component,{}\n",
1024 1210 : state.dataSplitterComponent->SplitterCond(Count2).NumOutletNodes);
1025 4889 : for (int Count1 = 1; Count1 <= state.dataSplitterComponent->SplitterCond(Count2).NumOutletNodes; ++Count1) {
1026 3679 : print(state.files.bnd,
1027 : " Supply Air Path Component Nodes,{},{},{},{},{},{}\n",
1028 : Count1,
1029 3679 : state.dataZoneEquip->SupplyAirPath(BCount).ComponentType(Count),
1030 3679 : state.dataZoneEquip->SupplyAirPath(BCount).ComponentName(Count),
1031 3679 : state.dataLoopNodes->NodeID(state.dataSplitterComponent->SplitterCond(Count2).InletNode),
1032 3679 : state.dataLoopNodes->NodeID(state.dataSplitterComponent->SplitterCond(Count2).OutletNode(Count1)),
1033 : PrimaryAirLoopName);
1034 : }
1035 : }
1036 1210 : } break;
1037 0 : default: {
1038 0 : ShowSevereError(
1039 0 : state, format("Invalid Component Type in Supply Air Path={}", state.dataZoneEquip->SupplyAirPath(BCount).ComponentType(Count)));
1040 0 : ErrFound = true;
1041 0 : ++NumErr;
1042 0 : } break;
1043 : }
1044 : }
1045 :
1046 1211 : if (state.dataZoneEquip->SupplyAirPath(BCount).NumNodes > 0) {
1047 : static constexpr std::string_view Format_705("! <#Nodes on Supply Air Path>,<Number of Nodes>");
1048 1211 : print(state.files.bnd, "{}\n", Format_705);
1049 : static constexpr std::string_view Format_706("! <Supply Air Path Node>,<Node Type>,<Node Count>,<Node Name>,<AirLoopHVAC Name>");
1050 1211 : print(state.files.bnd, "{}\n", Format_706);
1051 1211 : print(state.files.bnd, "#Nodes on Supply Air Path,{}\n", state.dataZoneEquip->SupplyAirPath(BCount).NumNodes);
1052 6113 : for (int Count2 = 1; Count2 <= state.dataZoneEquip->SupplyAirPath(BCount).NumNodes; ++Count2) {
1053 4902 : if (state.dataZoneEquip->SupplyAirPath(BCount).NodeType(Count2) == DataZoneEquipment::AirNodeType::PathInlet) {
1054 1211 : print(state.files.bnd,
1055 : " Supply Air Path Node,Inlet Node,{},{},{}\n",
1056 : Count2,
1057 1211 : state.dataLoopNodes->NodeID(state.dataZoneEquip->SupplyAirPath(BCount).Node(Count2)),
1058 : PrimaryAirLoopName);
1059 3691 : } else if (state.dataZoneEquip->SupplyAirPath(BCount).NodeType(Count2) == DataZoneEquipment::AirNodeType::Intermediate) {
1060 7 : print(state.files.bnd,
1061 : " Supply Air Path Node,Through Node,{},{},{}\n",
1062 : Count2,
1063 7 : state.dataLoopNodes->NodeID(state.dataZoneEquip->SupplyAirPath(BCount).Node(Count2)),
1064 : PrimaryAirLoopName);
1065 3684 : } else if (state.dataZoneEquip->SupplyAirPath(BCount).NodeType(Count2) == DataZoneEquipment::AirNodeType::Outlet) {
1066 3684 : print(state.files.bnd,
1067 : " Supply Air Path Node,Outlet Node,{},{},{}\n",
1068 : Count2,
1069 3684 : state.dataLoopNodes->NodeID(state.dataZoneEquip->SupplyAirPath(BCount).Node(Count2)),
1070 : PrimaryAirLoopName);
1071 : }
1072 : }
1073 : }
1074 1211 : }
1075 :
1076 795 : if (state.dataSplitterComponent->NumSplitters == 0) {
1077 256 : if (state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "AirLoopHVAC:ZoneSplitter") > 0) {
1078 0 : SplitterComponent::GetSplitterInput(state);
1079 : }
1080 : }
1081 795 : if (state.dataZonePlenum->NumZoneSupplyPlenums == 0 && state.dataZonePlenum->NumZoneReturnPlenums == 0) {
1082 601 : if (state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "AirLoopHVAC:SupplyPlenum") > 0) {
1083 0 : ZonePlenum::GetZonePlenumInput(state);
1084 : }
1085 : }
1086 :
1087 : // now the reverse. is every zone splitter and supply plenum on supply air path
1088 795 : FoundSupplyPlenum.dimension(state.dataZonePlenum->NumZoneSupplyPlenums, false);
1089 795 : FoundZoneSplitter.dimension(state.dataSplitterComponent->NumSplitters, false);
1090 795 : FoundNames.allocate(state.dataZonePlenum->NumZoneSupplyPlenums);
1091 803 : for (int Count1 = 1; Count1 <= state.dataZonePlenum->NumZoneSupplyPlenums; ++Count1) {
1092 20 : for (int BCount = 1; BCount <= state.dataZoneEquip->NumSupplyAirPaths; ++BCount) {
1093 35 : for (int Count = 1; Count <= state.dataZoneEquip->SupplyAirPath(BCount).NumOfComponents; ++Count) {
1094 31 : if (state.dataZonePlenum->ZoneSupPlenCond(Count1).ZonePlenumName != state.dataZoneEquip->SupplyAirPath(BCount).ComponentName(Count) ||
1095 8 : state.dataZoneEquip->SupplyAirPath(BCount).ComponentType(Count) != "AIRLOOPHVAC:SUPPLYPLENUM")
1096 15 : continue;
1097 8 : if (FoundSupplyPlenum(Count1)) {
1098 0 : ShowSevereError(
1099 : state,
1100 0 : format("AirLoopHVAC:SupplyPlenum=\"{}\", duplicate entry.", state.dataZonePlenum->ZoneSupPlenCond(Count1).ZonePlenumName));
1101 0 : ShowContinueError(state, format("already exists on AirLoopHVAC:SupplyPath=\"{}\".", FoundNames(Count1)));
1102 0 : ErrFound = true;
1103 : } else {
1104 : // record use
1105 8 : FoundSupplyPlenum(Count1) = true;
1106 8 : FoundNames(Count1) = state.dataZoneEquip->SupplyAirPath(BCount).Name;
1107 : }
1108 : }
1109 : }
1110 : }
1111 795 : FoundNames.deallocate();
1112 795 : FoundNames.allocate(state.dataSplitterComponent->NumSplitters);
1113 2005 : for (int Count1 = 1; Count1 <= state.dataSplitterComponent->NumSplitters; ++Count1) {
1114 13098 : for (int BCount = 1; BCount <= state.dataZoneEquip->NumSupplyAirPaths; ++BCount) {
1115 23787 : for (int Count = 1; Count <= state.dataZoneEquip->SupplyAirPath(BCount).NumOfComponents; ++Count) {
1116 11899 : if (state.dataSplitterComponent->SplitterCond(Count1).SplitterName !=
1117 25008 : state.dataZoneEquip->SupplyAirPath(BCount).ComponentName(Count) ||
1118 1210 : state.dataZoneEquip->SupplyAirPath(BCount).ComponentType(Count) != "AIRLOOPHVAC:ZONESPLITTER")
1119 10689 : continue;
1120 1210 : if (FoundZoneSplitter(Count1)) {
1121 0 : ShowSevereError(
1122 : state,
1123 0 : format("AirLoopHVAC:ZoneSplitter=\"{}\", duplicate entry.", state.dataSplitterComponent->SplitterCond(Count1).SplitterName));
1124 0 : ShowContinueError(state, format("already exists on AirLoopHVAC:SupplyPath=\"{}\".", FoundNames(Count1)));
1125 0 : ErrFound = true;
1126 : } else {
1127 : // record use
1128 1210 : FoundZoneSplitter(Count1) = true;
1129 1210 : FoundNames(Count1) = state.dataZoneEquip->SupplyAirPath(BCount).Name;
1130 : }
1131 : }
1132 : }
1133 : }
1134 795 : FoundNames.deallocate();
1135 :
1136 795 : if (!all(FoundSupplyPlenum)) {
1137 0 : for (int Count1 = 1; Count1 <= state.dataZonePlenum->NumZoneSupplyPlenums; ++Count1) {
1138 0 : if (FoundSupplyPlenum(Count1)) continue;
1139 0 : ShowSevereError(state,
1140 0 : format("AirLoopHVAC:SupplyPlenum=\"{}\", not found on any AirLoopHVAC:SupplyPath.",
1141 0 : state.dataZonePlenum->ZoneSupPlenCond(Count1).ZonePlenumName));
1142 : }
1143 : }
1144 :
1145 795 : if (!all(FoundZoneSplitter)) {
1146 0 : for (int Count1 = 1; Count1 <= state.dataSplitterComponent->NumSplitters; ++Count1) {
1147 0 : if (FoundZoneSplitter(Count1)) continue;
1148 0 : ShowSevereError(state,
1149 0 : format("AirLoopHVAC:ZoneSplitter=\"{}\", not found on any AirLoopHVAC:SupplyPath.",
1150 0 : state.dataSplitterComponent->SplitterCond(Count1).SplitterName));
1151 : }
1152 : }
1153 :
1154 795 : FoundSupplyPlenum.deallocate();
1155 795 : FoundZoneSplitter.deallocate();
1156 :
1157 795 : if (ErrFound) {
1158 0 : ShowSevereError(state, "Supply Air Path(s) did not pass integrity testing");
1159 : } else {
1160 795 : ShowMessage(state, "All Supply Air Paths passed integrity testing");
1161 : }
1162 795 : }
1163 :
1164 795 : void TestReturnAirPathIntegrity(EnergyPlusData &state, bool &ErrFound, Array2S_int ValRetAPaths)
1165 : {
1166 :
1167 : // SUBROUTINE INFORMATION:
1168 : // AUTHOR Linda Lawrie
1169 : // DATE WRITTEN March 2003
1170 :
1171 : // PURPOSE OF THIS SUBROUTINE:
1172 : // This subroutine tests return air path integrity and displays the loop for each branch.
1173 : // Also, input and output nodes.
1174 :
1175 : // REFERENCES:
1176 : // Return Air Path Validity Rules:
1177 : // Last component (zone mixer or zone return plenum) must resolve to
1178 : // be the outlet node for the return air path. Inlets to this component must be outlets from
1179 : // previous components or "controlled zone outlets"?.
1180 : // (though converse not true -- each outlet in previous components do not
1181 : // have to be inlets on this item -- though they must be inputs somewhere in the stream).
1182 : // If multiple components and no mixer, then a zone return plenums "outlet" must
1183 : // be represented as an inlet on a later plenum. i.e. some zone return plenums are
1184 : // really acting as "mixers" in a sense. These do not need to be stepwise in succession.
1185 : // Same caveat for inlets from previous item.
1186 : // If multiple components and mixer, then prior condition (nested plenums) is allowed as long as
1187 : // those aren't duplicated as mixer inlets. (i.e. zone rp 1 => zone rp 2 => zone mixer but
1188 : // zone rp 1 outlet should not also be inlet to mixer.
1189 : // Can have (nzrp -- nested zone return plenum, pzrp -- parallel zone return plenum):
1190 : // nzrp 1 => nzrp 2 & pzrp 3 => zm (inlets from nzrp 2 and pzrp 3). Or, likewise:
1191 : // pzrp 1 & pzrp 2 => zm => pzrp 3 (outlets from pzrp 1/2 are inlets to zm whose outlet is an
1192 : // inlet to pzrp 3 whose outlet is the outlet for the return air path.
1193 :
1194 : // Cannot have duplicate nodes in the "inlet" stream? (i.e. cannot have same zone feeding two independent
1195 : // plenums, for example). Similarly, Same return plenum can't be in two air loops nor as two independent
1196 : // return plenums in one return air path.
1197 :
1198 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1199 795 : std::string PrimaryAirLoopName; // Air Loop to which this return air path is connected
1200 795 : Array1D_bool FoundReturnPlenum;
1201 795 : Array1D_bool FoundZoneMixer;
1202 795 : Array1D_string FoundNames;
1203 795 : Array1D_int AllNodes;
1204 :
1205 : // Formats
1206 :
1207 : // Do by Paths
1208 795 : ShowMessage(state, "Testing Individual Return Air Path Integrity");
1209 795 : ErrFound = false;
1210 795 : int NumErr = 0;
1211 :
1212 795 : print(state.files.bnd, "{}\n", "! ===============================================================");
1213 : static constexpr std::string_view Format_700("! <#Return Air Paths>,<Number of Return Air Paths>");
1214 795 : print(state.files.bnd, "{}\n", Format_700);
1215 795 : print(state.files.bnd, " #Return Air Paths,{}\n", state.dataZoneEquip->NumReturnAirPaths);
1216 : static constexpr std::string_view Format_702("! <Return Air Path>,<Return Air Path Count>,<Return Air Path Name>,<AirLoopHVAC Name>");
1217 795 : print(state.files.bnd, "{}\n", Format_702);
1218 : static constexpr std::string_view Format_703("! <#Components on Return Air Path>,<Number of Components>");
1219 795 : print(state.files.bnd, "{}\n", Format_703);
1220 : static constexpr std::string_view Format_704(
1221 : "! <Return Air Path Component>,<Component Count>,<Component Type>,<Component Name>,<AirLoopHVAC Name>");
1222 795 : print(state.files.bnd, "{}\n", Format_704);
1223 : static constexpr std::string_view Format_707("! <#Inlet Nodes on Return Air Path Component>,<Number of Nodes>");
1224 795 : print(state.files.bnd, "{}\n", Format_707);
1225 : static constexpr std::string_view Format_708(
1226 : "! <Return Air Path Component Nodes>,<Node Count>,<Component Type>,<Component Name>,<Inlet Node Name>,<Outlet "
1227 : "Node Name>,<AirLoopHVAC Name>");
1228 795 : print(state.files.bnd, "{}\n", Format_708);
1229 :
1230 795 : AllNodes.allocate(state.dataLoopNodes->NumOfNodes);
1231 :
1232 1989 : for (int BCount = 1; BCount <= state.dataZoneEquip->NumReturnAirPaths; ++BCount) {
1233 : // Determine which air loop this supply air path is connected to
1234 1194 : int Found = 0;
1235 6511 : for (int Count1 = 1; Count1 <= state.dataHVACGlobal->NumPrimaryAirSys; ++Count1) {
1236 6511 : PrimaryAirLoopName = state.dataAirLoop->AirToZoneNodeInfo(Count1).AirLoopName;
1237 6511 : Found = 0;
1238 13022 : for (int Count2 = 1; Count2 <= state.dataAirLoop->AirToZoneNodeInfo(Count1).NumReturnNodes; ++Count2) {
1239 6511 : if (state.dataZoneEquip->ReturnAirPath(BCount).OutletNodeNum ==
1240 6511 : state.dataAirLoop->AirToZoneNodeInfo(Count1).ZoneEquipReturnNodeNum(Count2))
1241 1194 : Found = Count2;
1242 : }
1243 6511 : if (Found != 0) break;
1244 : }
1245 1194 : if (Found == 0) PrimaryAirLoopName = "**Unknown**";
1246 :
1247 1194 : print(state.files.bnd, " Return Air Path,{},{},{}\n", BCount, state.dataZoneEquip->ReturnAirPath(BCount).Name, PrimaryAirLoopName);
1248 :
1249 1194 : int NumComp = state.dataZoneEquip->ReturnAirPath(BCount).NumOfComponents;
1250 1194 : print(state.files.bnd, " #Components on Return Air Path,{}\n", NumComp);
1251 :
1252 1194 : std::string const &AirPathNodeName = state.dataLoopNodes->NodeID(state.dataZoneEquip->ReturnAirPath(BCount).OutletNodeNum);
1253 :
1254 1194 : int MixerCount = 0;
1255 2503 : for (int Count = 1; Count <= NumComp; ++Count) {
1256 1309 : print(state.files.bnd,
1257 : " Return Air Path Component,{},{},{},{}\n",
1258 : Count,
1259 1309 : state.dataZoneEquip->ReturnAirPath(BCount).ComponentType(Count),
1260 1309 : state.dataZoneEquip->ReturnAirPath(BCount).ComponentName(Count),
1261 : PrimaryAirLoopName);
1262 :
1263 1309 : if (Util::SameString(state.dataZoneEquip->ReturnAirPath(BCount).ComponentType(Count), "AirLoopHVAC:ZoneMixer")) {
1264 1026 : ++MixerCount;
1265 : }
1266 : }
1267 :
1268 1194 : if (MixerCount > 1) {
1269 0 : ShowSevereError(state, format("Too many zone mixers in Return Air Path={}", state.dataZoneEquip->ReturnAirPath(BCount).Name));
1270 0 : ErrFound = true;
1271 0 : ++NumErr;
1272 0 : continue;
1273 : }
1274 :
1275 1194 : AllNodes = 0;
1276 1194 : int CountNodes = 0;
1277 :
1278 1194 : if (NumComp > 0) {
1279 :
1280 : AirLoopHVACCompType CompType = static_cast<AirLoopHVACCompType>(
1281 1194 : getEnumValue(AirLoopHVACCompTypeNamesUC, Util::makeUPPER(state.dataZoneEquip->ReturnAirPath(BCount).ComponentType(NumComp))));
1282 :
1283 1194 : switch (CompType) {
1284 1026 : case AirLoopHVACCompType::ZoneMixer: {
1285 12520 : for (int Count2 = 1; Count2 <= state.dataMixerComponent->NumMixers; ++Count2) {
1286 11494 : if (state.dataZoneEquip->ReturnAirPath(BCount).ComponentName(NumComp) != state.dataMixerComponent->MixerCond(Count2).MixerName)
1287 10468 : continue;
1288 : // Found correct Mixer (by name), check outlet node vs. return air path outlet node
1289 1026 : if (AirPathNodeName != state.dataLoopNodes->NodeID(state.dataMixerComponent->MixerCond(Count2).OutletNode)) {
1290 0 : ShowSevereError(state, format("Error in Return Air Path={}", state.dataZoneEquip->ReturnAirPath(BCount).Name));
1291 0 : ShowContinueError(state, format("For Connector:Mixer={}", state.dataZoneEquip->ReturnAirPath(BCount).ComponentName(NumComp)));
1292 0 : ShowContinueError(state, format("Expected outlet node (return air path)={}", AirPathNodeName));
1293 0 : ShowContinueError(state,
1294 0 : format("Encountered node name (mixer)={}",
1295 0 : state.dataLoopNodes->NodeID(state.dataMixerComponent->MixerCond(Count2).OutletNode)));
1296 0 : ErrFound = true;
1297 0 : ++NumErr;
1298 : } else {
1299 1026 : ++CountNodes;
1300 1026 : AllNodes(CountNodes) = state.dataMixerComponent->MixerCond(Count2).OutletNode;
1301 1026 : if (state.dataZoneEquip->ReturnAirPath(BCount).OutletNodeNum == state.dataMixerComponent->MixerCond(Count2).OutletNode) {
1302 1026 : state.dataZoneEquip->ReturnAirPath(BCount).OutletRetPathCompNum = NumComp;
1303 : }
1304 3461 : for (int Loop = 1; Loop <= state.dataMixerComponent->MixerCond(Count2).NumInletNodes; ++Loop) {
1305 2435 : ++CountNodes;
1306 2435 : AllNodes(CountNodes) = state.dataMixerComponent->MixerCond(Count2).InletNode(Loop);
1307 : }
1308 : }
1309 1026 : print(state.files.bnd,
1310 : " #Inlet Nodes on Return Air Path Component,{}\n",
1311 1026 : state.dataMixerComponent->MixerCond(Count2).NumInletNodes);
1312 3461 : for (int Count1 = 1; Count1 <= state.dataMixerComponent->MixerCond(Count2).NumInletNodes; ++Count1) {
1313 2435 : print(state.files.bnd,
1314 : " Return Air Path Component Nodes,{},{},{},{},{},{}\n",
1315 : Count1,
1316 2435 : state.dataZoneEquip->ReturnAirPath(BCount).ComponentType(NumComp),
1317 2435 : state.dataZoneEquip->ReturnAirPath(BCount).ComponentName(NumComp),
1318 2435 : state.dataLoopNodes->NodeID(state.dataMixerComponent->MixerCond(Count2).InletNode(Count1)),
1319 2435 : state.dataLoopNodes->NodeID(state.dataMixerComponent->MixerCond(Count2).OutletNode),
1320 : PrimaryAirLoopName);
1321 : }
1322 : }
1323 1026 : } break;
1324 168 : case AirLoopHVACCompType::ReturnPlenum: {
1325 518 : for (int Count2 = 1; Count2 <= state.dataZonePlenum->NumZoneReturnPlenums; ++Count2) {
1326 350 : if (state.dataZoneEquip->ReturnAirPath(BCount).ComponentName(NumComp) !=
1327 350 : state.dataZonePlenum->ZoneRetPlenCond(Count2).ZonePlenumName)
1328 182 : continue;
1329 168 : if (AirPathNodeName != state.dataLoopNodes->NodeID(state.dataZonePlenum->ZoneRetPlenCond(Count2).OutletNode)) {
1330 0 : ShowSevereError(state, format("Error in Return Air Path={}", state.dataZoneEquip->ReturnAirPath(BCount).Name));
1331 0 : ShowContinueError(
1332 0 : state, format("For AirLoopHVAC:ReturnPlenum={}", state.dataZoneEquip->ReturnAirPath(BCount).ComponentName(NumComp)));
1333 0 : ShowContinueError(state, format("Expected outlet node (return air path)={}", AirPathNodeName));
1334 0 : ShowContinueError(state,
1335 0 : format("Encountered node name (zone return plenum)={}",
1336 0 : state.dataLoopNodes->NodeID(state.dataZonePlenum->ZoneRetPlenCond(Count2).OutletNode)));
1337 0 : ErrFound = true;
1338 0 : ++NumErr;
1339 : } else {
1340 168 : ++CountNodes;
1341 168 : AllNodes(CountNodes) = state.dataZonePlenum->ZoneRetPlenCond(Count2).OutletNode;
1342 168 : if (state.dataZoneEquip->ReturnAirPath(BCount).OutletNodeNum == state.dataZonePlenum->ZoneRetPlenCond(Count2).OutletNode) {
1343 168 : state.dataZoneEquip->ReturnAirPath(BCount).OutletRetPathCompNum = NumComp;
1344 : }
1345 918 : for (int Loop = 1; Loop <= state.dataZonePlenum->ZoneRetPlenCond(Count2).NumInletNodes; ++Loop) {
1346 750 : ++CountNodes;
1347 750 : AllNodes(CountNodes) = state.dataZonePlenum->ZoneRetPlenCond(Count2).InletNode(Loop);
1348 : }
1349 : }
1350 168 : print(state.files.bnd,
1351 : " #Inlet Nodes on Return Air Path Component,{}\n",
1352 168 : state.dataZonePlenum->ZoneRetPlenCond(Count2).NumInletNodes);
1353 918 : for (int Count1 = 1; Count1 <= state.dataZonePlenum->ZoneRetPlenCond(Count2).NumInletNodes; ++Count1) {
1354 750 : print(state.files.bnd,
1355 : " Return Air Path Component Nodes,{},{},{},{},{},{}\n",
1356 : Count1,
1357 750 : state.dataZoneEquip->ReturnAirPath(BCount).ComponentType(NumComp),
1358 750 : state.dataZoneEquip->ReturnAirPath(BCount).ComponentName(NumComp),
1359 750 : state.dataLoopNodes->NodeID(state.dataZonePlenum->ZoneRetPlenCond(Count2).InletNode(Count1)),
1360 750 : state.dataLoopNodes->NodeID(state.dataZonePlenum->ZoneRetPlenCond(Count2).OutletNode),
1361 : PrimaryAirLoopName);
1362 : }
1363 : }
1364 168 : } break;
1365 0 : default: // This already validated in GetReturnAirPath
1366 0 : break;
1367 : }
1368 : }
1369 :
1370 1194 : if (NumComp > 1) {
1371 229 : for (int Count3 = 1; Count3 <= NumComp - 1; ++Count3) {
1372 :
1373 : AirLoopHVACCompType CompType = static_cast<AirLoopHVACCompType>(
1374 115 : getEnumValue(AirLoopHVACCompTypeNamesUC, Util::makeUPPER(state.dataZoneEquip->ReturnAirPath(BCount).ComponentType(Count3))));
1375 :
1376 115 : switch (CompType) {
1377 0 : case AirLoopHVACCompType::ZoneMixer: {
1378 0 : for (int Count2 = 1; Count2 <= state.dataMixerComponent->NumMixers; ++Count2) {
1379 0 : if (state.dataZoneEquip->ReturnAirPath(BCount).ComponentName(Count3) != state.dataMixerComponent->MixerCond(Count2).MixerName)
1380 0 : continue;
1381 0 : for (int Loop = 1; Loop <= state.dataMixerComponent->MixerCond(Count2).NumInletNodes; ++Loop) {
1382 0 : ++CountNodes;
1383 0 : AllNodes(CountNodes) = state.dataMixerComponent->MixerCond(Count2).InletNode(Loop);
1384 : }
1385 : }
1386 0 : } break;
1387 115 : case AirLoopHVACCompType::ReturnPlenum: {
1388 454 : for (int Count2 = 1; Count2 <= state.dataZonePlenum->NumZoneReturnPlenums; ++Count2) {
1389 339 : if (state.dataZoneEquip->ReturnAirPath(BCount).ComponentName(Count3) !=
1390 339 : state.dataZonePlenum->ZoneRetPlenCond(Count2).ZonePlenumName)
1391 224 : continue;
1392 675 : for (int Loop = 1; Loop <= state.dataZonePlenum->ZoneRetPlenCond(Count2).NumInletNodes; ++Loop) {
1393 560 : ++CountNodes;
1394 560 : AllNodes(CountNodes) = state.dataZonePlenum->ZoneRetPlenCond(Count2).InletNode(Loop);
1395 : }
1396 : }
1397 115 : } break;
1398 0 : default: // This already validated in GetReturnAirPath
1399 0 : break;
1400 : }
1401 : }
1402 : }
1403 1194 : if (CountNodes > 0) {
1404 : static constexpr std::string_view Format_705("! <#Nodes on Return Air Path>,<Number of Nodes>");
1405 1194 : print(state.files.bnd, "{}\n", Format_705);
1406 : static constexpr std::string_view Format_706("! <Return Air Path Node>,<Node Type>,<Node Count>,<Node Name>,<AirLoopHVAC Name>");
1407 1194 : print(state.files.bnd, "{}\n", Format_706);
1408 1194 : print(state.files.bnd, " #Nodes on Return Air Path,{}\n", CountNodes);
1409 6133 : for (int Count2 = 1; Count2 <= CountNodes; ++Count2) {
1410 4939 : if (Count2 == 1) {
1411 1194 : print(state.files.bnd,
1412 : " Return Air Path Node,Outlet Node,{},{},{}\n",
1413 : Count2,
1414 1194 : state.dataLoopNodes->NodeID(AllNodes(Count2)),
1415 : PrimaryAirLoopName);
1416 : } else {
1417 3745 : print(state.files.bnd,
1418 : " Return Air Path Node,Inlet Node,{},{},{}\n",
1419 : Count2,
1420 3745 : state.dataLoopNodes->NodeID(AllNodes(Count2)),
1421 : PrimaryAirLoopName);
1422 : }
1423 : }
1424 : }
1425 : // Determine Air Loop this Return Air Path is on
1426 6511 : for (int Count2 = 1; Count2 <= state.dataHVACGlobal->NumPrimaryAirSys; ++Count2) {
1427 6511 : if (state.dataAirLoop->AirToZoneNodeInfo(Count2).NumReturnNodes > 0) {
1428 6511 : if (AllNodes(1) == state.dataAirLoop->AirToZoneNodeInfo(Count2).ZoneEquipReturnNodeNum(1)) {
1429 1194 : const int WAirLoop = Count2;
1430 1194 : ValRetAPaths(_, WAirLoop) = 0;
1431 1194 : ValRetAPaths({1, CountNodes}, WAirLoop) = AllNodes({1, CountNodes});
1432 1194 : break;
1433 : }
1434 : } else {
1435 0 : ShowWarningError(state,
1436 0 : format("TestReturnAirPathIntegrity: Air Loop has no Zone Equipment Return Node={}",
1437 0 : state.dataAirLoop->AirToZoneNodeInfo(Count2).AirLoopName));
1438 : }
1439 : }
1440 : }
1441 :
1442 795 : AllNodes.deallocate();
1443 :
1444 795 : if (state.dataMixerComponent->NumMixers == 0) {
1445 389 : if (state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "AirLoopHVAC:ZoneMixer") > 0) {
1446 0 : MixerComponent::GetMixerInput(state);
1447 : }
1448 : }
1449 795 : if (state.dataZonePlenum->NumZoneSupplyPlenums == 0 && state.dataZonePlenum->NumZoneReturnPlenums == 0) {
1450 601 : if (state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "AirLoopHVAC:ReturnPlenum") > 0) {
1451 0 : ZonePlenum::GetZonePlenumInput(state);
1452 : }
1453 : }
1454 :
1455 : // now the reverse. is every zone Mixer and Return plenum on Return air path
1456 795 : FoundReturnPlenum.dimension(state.dataZonePlenum->NumZoneReturnPlenums, false);
1457 795 : FoundZoneMixer.dimension(state.dataMixerComponent->NumMixers, false);
1458 795 : FoundNames.allocate(state.dataZonePlenum->NumZoneReturnPlenums);
1459 1079 : for (int Count1 = 1; Count1 <= state.dataZonePlenum->NumZoneReturnPlenums; ++Count1) {
1460 1102 : for (int BCount = 1; BCount <= state.dataZoneEquip->NumReturnAirPaths; ++BCount) {
1461 1975 : for (int Count = 1; Count <= state.dataZoneEquip->ReturnAirPath(BCount).NumOfComponents; ++Count) {
1462 1440 : if (state.dataZonePlenum->ZoneRetPlenCond(Count1).ZonePlenumName != state.dataZoneEquip->ReturnAirPath(BCount).ComponentName(Count) ||
1463 283 : state.dataZoneEquip->ReturnAirPath(BCount).ComponentType(Count) != "AIRLOOPHVAC:RETURNPLENUM")
1464 874 : continue;
1465 283 : if (FoundReturnPlenum(Count1)) {
1466 0 : ShowSevereError(
1467 : state,
1468 0 : format("AirLoopHVAC:ReturnPlenum=\"{}\", duplicate entry.", state.dataZonePlenum->ZoneRetPlenCond(Count1).ZonePlenumName));
1469 0 : ShowContinueError(state, format("already exists on AirLoopHVAC:ReturnPath=\"{}\".", FoundNames(Count1)));
1470 0 : ErrFound = true;
1471 : } else {
1472 : // record use
1473 283 : FoundReturnPlenum(Count1) = true;
1474 283 : FoundNames(Count1) = state.dataZoneEquip->ReturnAirPath(BCount).Name;
1475 : }
1476 : }
1477 : }
1478 284 : if (PurchasedAirManager::CheckPurchasedAirForReturnPlenum(state, Count1)) FoundReturnPlenum(Count1) = true;
1479 : }
1480 795 : FoundNames.deallocate();
1481 795 : FoundNames.allocate(state.dataMixerComponent->NumMixers);
1482 1884 : for (int Count1 = 1; Count1 <= state.dataMixerComponent->NumMixers; ++Count1) {
1483 12630 : for (int BCount = 1; BCount <= state.dataZoneEquip->NumReturnAirPaths; ++BCount) {
1484 23590 : for (int Count = 1; Count <= state.dataZoneEquip->ReturnAirPath(BCount).NumOfComponents; ++Count) {
1485 13075 : if (state.dataMixerComponent->MixerCond(Count1).MixerName != state.dataZoneEquip->ReturnAirPath(BCount).ComponentName(Count) ||
1486 1026 : state.dataZoneEquip->ReturnAirPath(BCount).ComponentType(Count) != "AIRLOOPHVAC:ZONEMIXER")
1487 11023 : continue;
1488 1026 : if (FoundZoneMixer(Count1)) {
1489 0 : ShowSevereError(state,
1490 0 : format("AirLoopHVAC:ZoneMixer=\"{}\", duplicate entry.", state.dataMixerComponent->MixerCond(Count1).MixerName));
1491 0 : ShowContinueError(state, format("already exists on AirLoopHVAC:ReturnPath=\"{}\".", FoundNames(Count1)));
1492 0 : ErrFound = true;
1493 : } else {
1494 : // record use
1495 1026 : FoundZoneMixer(Count1) = true;
1496 1026 : FoundNames(Count1) = state.dataZoneEquip->ReturnAirPath(BCount).Name;
1497 : }
1498 : }
1499 : }
1500 1089 : if (!FoundZoneMixer(Count1)) { // could be as child on other items
1501 : // PIU Units
1502 63 : if (PoweredInductionUnits::PIUnitHasMixer(state, state.dataMixerComponent->MixerCond(Count1).MixerName)) FoundZoneMixer(Count1) = true;
1503 : }
1504 1089 : if (!FoundZoneMixer(Count1)) { // could be as child on other items
1505 : // fourPipeInduction units
1506 6 : if (HVACSingleDuctInduc::FourPipeInductionUnitHasMixer(state, state.dataMixerComponent->MixerCond(Count1).MixerName))
1507 4 : FoundZoneMixer(Count1) = true;
1508 : }
1509 1089 : if (!FoundZoneMixer(Count1)) { // could be as child on other items
1510 : // Exhaust Systems
1511 2 : if (ExhaustAirSystemManager::ExhaustSystemHasMixer(state, state.dataMixerComponent->MixerCond(Count1).MixerName))
1512 2 : FoundZoneMixer(Count1) = true;
1513 : }
1514 : }
1515 795 : FoundNames.deallocate();
1516 :
1517 795 : if (!all(FoundReturnPlenum)) {
1518 0 : for (int Count1 = 1; Count1 <= state.dataZonePlenum->NumZoneReturnPlenums; ++Count1) {
1519 0 : if (FoundReturnPlenum(Count1)) continue;
1520 0 : ShowSevereError(state,
1521 0 : format("AirLoopHVAC:ReturnPlenum=\"{}\", not found on any AirLoopHVAC:ReturnPath.",
1522 0 : state.dataZonePlenum->ZoneRetPlenCond(Count1).ZonePlenumName));
1523 : }
1524 : }
1525 :
1526 795 : if (!all(FoundZoneMixer)) {
1527 0 : for (int Count1 = 1; Count1 <= state.dataMixerComponent->NumMixers; ++Count1) {
1528 0 : if (FoundZoneMixer(Count1)) continue;
1529 0 : ShowSevereError(state,
1530 0 : format("AirLoopHVAC:ZoneMixer=\"{}\", not found on any AirLoopHVAC:ReturnPath, AirLoopHVAC:ExhaustSystem, "
1531 : "AirTerminal:SingleDuct:SeriesPIU:Reheat,",
1532 0 : state.dataMixerComponent->MixerCond(Count1).MixerName));
1533 0 : ShowContinueError(state, "AirTerminal:SingleDuct:ParallelPIU:Reheat or AirTerminal:SingleDuct:ConstantVolume:FourPipeInduction.");
1534 : }
1535 : }
1536 :
1537 795 : FoundReturnPlenum.deallocate();
1538 795 : FoundZoneMixer.deallocate();
1539 :
1540 795 : if (ErrFound) {
1541 0 : ShowSevereError(state, "Return Air Path(s) did not pass integrity testing");
1542 : } else {
1543 795 : ShowMessage(state, "All Return Air Paths passed integrity testing");
1544 : }
1545 795 : }
1546 :
1547 37687013 : void CalcComponentSensibleLatentOutput(Real64 const MassFlow, // air mass flow rate, {kg/s}
1548 : Real64 const TDB2, // dry-bulb temperature at state 2 {C}
1549 : Real64 const W2, // humidity ratio at state 2
1550 : Real64 const TDB1, // dry-bulb temperature at at state 1 {C}
1551 : Real64 const W1, // humidity ratio at state 1
1552 : Real64 &SensibleOutput, // sensible output rate (state 2 -> State 1), {W}
1553 : Real64 &LatentOutput, // latent output rate (state 2 -> State 1), {W}
1554 : Real64 &TotalOutput // total = sensible + latent putput rate (state 2 -> State 1), {W}
1555 : )
1556 : {
1557 :
1558 : // Purpose:
1559 : // returns total, sensible and latent heat rate of change of moist air transitioning
1560 : // between two states. The moist air energy transfer can be cooling or heating process
1561 : // across a cooling, a heating coil, or an HVAC component.
1562 :
1563 : // Methodology:
1564 : // Q_total = m_dot * (h2 - h1)
1565 : // Q_sensible = m_dot * Psychrometrics::PsyDeltaHSenFnTdb2W2Tdb1W1(TDB2, W2, TDB1, W1);
1566 : // or Q_sensible = m_dot * cp_moistair_MinHumRat * (TDB2 - TDB1)
1567 : // cp_moistair_MinHumRat = Psychrometrics::PsyCpAirFnW(min(W2, W1));
1568 : // Q_latent = Q_total - Q_latent;
1569 :
1570 37687013 : TotalOutput = 0.0;
1571 37687013 : LatentOutput = 0.0;
1572 37687013 : SensibleOutput = 0.0;
1573 37687013 : if (MassFlow > 0.0) {
1574 36281989 : TotalOutput = MassFlow * (Psychrometrics::PsyHFnTdbW(TDB2, W2) - Psychrometrics::PsyHFnTdbW(TDB1, W1)); // total addition/removal rate, {W};
1575 36281989 : SensibleOutput = MassFlow * Psychrometrics::PsyDeltaHSenFnTdb2W2Tdb1W1(TDB2, W2, TDB1, W1); // sensible addition/removal rate, {W};
1576 36281989 : LatentOutput = TotalOutput - SensibleOutput; // latent addition/removal rate, {W}
1577 : }
1578 37687013 : }
1579 :
1580 61740243 : void CalcZoneSensibleLatentOutput(Real64 const MassFlow, // air mass flow rate, {kg/s}
1581 : Real64 const TDBEquip, // dry-bulb temperature at equipment outlet {C}
1582 : Real64 const WEquip, // humidity ratio at equipment outlet
1583 : Real64 const TDBZone, // dry-bulb temperature at zone air node {C}
1584 : Real64 const WZone, // humidity ratio at zone air node
1585 : Real64 &SensibleOutput, // sensible output rate (state 2 -> State 1), {W}
1586 : Real64 &LatentOutput, // latent output rate (state 2 -> State 1), {W}
1587 : Real64 &TotalOutput // total = sensible + latent putput rate (state 2 -> State 1), {W}
1588 : )
1589 : {
1590 :
1591 : // Purpose:
1592 : // returns total, sensible and latent heat rate of transfer between the supply air zone inlet
1593 : // node and zone air node. The moist air energy transfer can be cooling or heating depending
1594 : // on the supply air zone inlet node and zone air node conditions.
1595 :
1596 : // Methodology:
1597 : // Q_total = m_dot * (hEquip - hZone)
1598 : // Q_sensible = m_dot * Psychrometrics::PsyDeltaHSenFnTdbEquipTdbWZone(TDBEquip, TDBZone, WZone);
1599 : // or Q_sensible = m_dot * cp_moistair_zoneHumRat * (TDBEquip - TDBZone)
1600 : // cp_moistair_zoneHumRat = Psychrometrics::PsyCpAirFnW(WZone);
1601 : // Q_latent = Q_total - Q_latent;
1602 :
1603 61740243 : TotalOutput = 0.0;
1604 61740243 : LatentOutput = 0.0;
1605 61740243 : SensibleOutput = 0.0;
1606 61740243 : if (MassFlow > 0.0) {
1607 49194861 : TotalOutput = MassFlow * (Psychrometrics::PsyHFnTdbW(TDBEquip, WEquip) -
1608 49194861 : Psychrometrics::PsyHFnTdbW(TDBZone, WZone)); // total addition/removal rate, {W};
1609 49194861 : SensibleOutput = MassFlow * Psychrometrics::PsyDeltaHSenFnTdb2Tdb1W(TDBEquip, TDBZone, WZone); // sensible addition/removal rate, {W};
1610 49194861 : LatentOutput = TotalOutput - SensibleOutput; // latent addition/removal rate, {W}
1611 : }
1612 61740243 : }
1613 :
1614 51146948 : Real64 calcZoneSensibleOutput(Real64 const MassFlow, // air mass flow rate, {kg/s}
1615 : Real64 const TDBEquip, // dry-bulb temperature at equipment outlet {C}
1616 : Real64 const TDBZone, // dry-bulb temperature at zone air node {C}
1617 : Real64 const WZone // humidity ratio at zone air node
1618 : )
1619 : {
1620 :
1621 : // Purpose:
1622 : // returns sensible heat rate of transfer between the supply air zone inlet node and
1623 : // zone air node. The moist air energy transfer can be cooling or heating depending
1624 : // on the supply air zone inlet node and zone air node conditions.
1625 :
1626 : // Methodology:
1627 : // Q_sensible = m_dot * Psychrometrics::PsyDeltaHSenFnTdbEquipTdbWZone(TDBEquip, TDBZone, WZone);
1628 : // or Q_sensible = m_dot * cp_moistair_zoneHumRat * (TDBEquip - TDBZone)
1629 : // cp_moistair_zoneHumRat = Psychrometrics::PsyCpAirFnW(WZone);
1630 :
1631 51146948 : Real64 sensibleOutput = 0.0; // sensible output rate (state 2 -> State 1), {W}
1632 51146948 : if (MassFlow > 0.0) {
1633 41159518 : sensibleOutput = MassFlow * Psychrometrics::PsyDeltaHSenFnTdb2Tdb1W(TDBEquip, TDBZone, WZone); // sensible addition/removal rate, {W};
1634 : }
1635 51146948 : return sensibleOutput;
1636 : }
1637 : } // namespace EnergyPlus
|