Line data Source code
1 : // EnergyPlus, Copyright (c) 1996-2025, The Board of Trustees of the University of Illinois,
2 : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
3 : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
4 : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
5 : // contributors. All rights reserved.
6 : //
7 : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
8 : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
9 : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
10 : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
11 : // derivative works, and perform publicly and display publicly, and to permit others to do so.
12 : //
13 : // Redistribution and use in source and binary forms, with or without modification, are permitted
14 : // provided that the following conditions are met:
15 : //
16 : // (1) Redistributions of source code must retain the above copyright notice, this list of
17 : // conditions and the following disclaimer.
18 : //
19 : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
20 : // conditions and the following disclaimer in the documentation and/or other materials
21 : // provided with the distribution.
22 : //
23 : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
24 : // the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
25 : // used to endorse or promote products derived from this software without specific prior
26 : // written permission.
27 : //
28 : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
29 : // without changes from the version obtained under this License, or (ii) Licensee makes a
30 : // reference solely to the software portion of its product, Licensee must refer to the
31 : // software as "EnergyPlus version X" software, where "X" is the version number Licensee
32 : // obtained under this License and may not use a different name for the software. Except as
33 : // specifically required in this Section (4), Licensee shall not use in a company name, a
34 : // product name, in advertising, publicity, or other promotional activities any name, trade
35 : // name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
36 : // similar designation, without the U.S. Department of Energy's prior written consent.
37 : //
38 : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
39 : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
40 : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
41 : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
43 : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44 : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
45 : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46 : // POSSIBILITY OF SUCH DAMAGE.
47 :
48 : // C++ Headers
49 : #include <cmath>
50 :
51 : // ObjexxFCL Headers
52 : #include <ObjexxFCL/Array.functions.hh>
53 : #include <ObjexxFCL/Array1D.hh>
54 : #include <ObjexxFCL/Fmath.hh>
55 :
56 : // EnergyPlus Headers
57 : #include <EnergyPlus/CurveManager.hh>
58 : #include <EnergyPlus/Data/EnergyPlusData.hh>
59 : #include <EnergyPlus/DataBranchAirLoopPlant.hh>
60 : #include <EnergyPlus/DataEnvironment.hh>
61 : #include <EnergyPlus/DataLoopNode.hh>
62 : #include <EnergyPlus/FluidProperties.hh>
63 : #include <EnergyPlus/OutputProcessor.hh>
64 : #include <EnergyPlus/Plant/DataPlant.hh>
65 : #include <EnergyPlus/PlantPressureSystem.hh>
66 : #include <EnergyPlus/UtilityRoutines.hh>
67 :
68 : namespace EnergyPlus::PlantPressureSystem {
69 :
70 : // Module containing the routines dealing with the PlantPressureSystem simulation
71 :
72 : // MODULE INFORMATION:
73 : // AUTHOR Edwin Lee
74 : // DATE WRITTEN August 2009
75 : // MODIFIED February 2010: Add phase 2: loop flow correction
76 : // RE-ENGINEERED na
77 :
78 : // PURPOSE OF THIS MODULE:
79 : // This module manages plant pressure-based simulations
80 :
81 : // METHODOLOGY EMPLOYED:
82 : // General EnergyPlus Methodology:
83 :
84 : // OTHER NOTES:
85 : // Phase 1: Pump Power Correction: -Loop/Parallel flows are not resolved based on pressure drop
86 : // -Every flow path must see at least one branch with pressure information
87 : // -Pump power is updated based on the required pump head
88 : // Phase 2: Pump Flow Correction: -Loop flow resolved based on pump curve and loop pressure drop
89 : // -Parallel flows not resolved
90 : // -Every flow path must see at least one branch with pressure information
91 : // -Pump curve must be given also
92 : // Phase 3: Pressure Simulation: -Loop and parallel flows are resolved
93 : // -All branches must have pressure information and pump must have pump curve
94 : // -Not currently implemented
95 :
96 : // Using/Aliasing
97 : using namespace DataBranchAirLoopPlant;
98 :
99 1116114 : void SimPressureDropSystem(EnergyPlusData &state,
100 : int const LoopNum, // Plant Loop to update pressure information
101 : bool const FirstHVACIteration, // System flag
102 : DataPlant::PressureCall const CallType, // Enumerated call type
103 : DataPlant::LoopSideLocation LoopSideNum, // Loop side num for specific branch simulation
104 : ObjexxFCL::Optional_int_const BranchNum // Branch num for specific branch simulation
105 : )
106 : {
107 :
108 : // SUBROUTINE INFORMATION:
109 : // AUTHOR Edwin Lee
110 : // DATE WRITTEN August 2009
111 : // MODIFIED na
112 : // RE-ENGINEERED na
113 :
114 : // PURPOSE OF THIS SUBROUTINE:
115 : // This routine is the public interface for pressure system simulation
116 : // Calls are made to private components as needed
117 :
118 : // METHODOLOGY EMPLOYED:
119 : // Standard EnergyPlus methodology
120 :
121 : // Using/Aliasing
122 :
123 : // Exit out of any calculation routines if we don't do pressure simulation for this loop
124 1292532 : if ((state.dataPlnt->PlantLoop(LoopNum).PressureSimType == DataPlant::PressSimType::NoPressure) &&
125 176418 : ((CallType == DataPlant::PressureCall::Calc) || (CallType == DataPlant::PressureCall::Update)))
126 1027904 : return;
127 :
128 : // Pass to another routine based on calling flag
129 88210 : switch (CallType) {
130 88210 : case DataPlant::PressureCall::Init: {
131 88210 : InitPressureDrop(state, LoopNum, FirstHVACIteration);
132 88210 : } break;
133 0 : case DataPlant::PressureCall::Calc: {
134 0 : BranchPressureDrop(state, LoopNum, LoopSideNum, BranchNum); // Autodesk:OPTIONAL LoopSideNum, BranchNum used without PRESENT check
135 0 : } break;
136 0 : case DataPlant::PressureCall::Update: {
137 0 : UpdatePressureDrop(state, LoopNum);
138 0 : } break;
139 0 : default: {
140 : // Calling routines should only use the three possible keywords here
141 0 : } break;
142 : }
143 : }
144 :
145 88210 : void InitPressureDrop(EnergyPlusData &state, int const LoopNum, bool const FirstHVACIteration)
146 : {
147 :
148 : // SUBROUTINE INFORMATION:
149 : // AUTHOR Edwin Lee
150 : // DATE WRITTEN August 2009
151 : // MODIFIED na
152 : // RE-ENGINEERED na
153 :
154 : // PURPOSE OF THIS SUBROUTINE:
155 : // Initializes output variables and data structure
156 : // On FirstHVAC, updates the demand inlet node pressure
157 :
158 88210 : if (state.dataPlantPressureSys->InitPressureDropOneTimeInit) {
159 : // First allocate the initialization array to each plant loop
160 21 : state.dataPlantPressureSys->LoopInit.allocate(size(state.dataPlnt->PlantLoop));
161 21 : state.dataPlantPressureSys->LoopInit = true;
162 21 : state.dataPlantPressureSys->InitPressureDropOneTimeInit = false;
163 : }
164 :
165 88210 : auto &loop(state.dataPlnt->PlantLoop(LoopNum));
166 :
167 : // CurrentModuleObject='Curve:Functional:PressureDrop'
168 88210 : if (state.dataPlantPressureSys->LoopInit(LoopNum)) {
169 :
170 : // Initialize
171 35 : bool ErrorsFound(false);
172 :
173 : // Need to go along plant loop and set up component pressure drop data structure!
174 105 : for (DataPlant::LoopSideLocation LoopSideNum : DataPlant::LoopSideKeys) {
175 70 : auto &loop_side(loop.LoopSide(LoopSideNum));
176 :
177 : // Loop through all branches on this loop side
178 352 : for (int BranchNum = 1; BranchNum <= isize(loop_side.Branch); ++BranchNum) {
179 282 : auto &branch(loop_side.Branch(BranchNum));
180 :
181 : // If this branch has valid pressure drop data
182 282 : if (branch.PressureCurveIndex > 0) {
183 :
184 : // Update flags for higher level structure
185 0 : branch.HasPressureComponents = true;
186 0 : loop_side.HasPressureComponents = true;
187 0 : loop.HasPressureComponents = true;
188 :
189 : // Setup output variable
190 0 : SetupOutputVariable(state,
191 : "Plant Branch Pressure Difference",
192 : Constant::Units::Pa,
193 0 : branch.PressureDrop,
194 : OutputProcessor::TimeStepType::System,
195 : OutputProcessor::StoreType::Average,
196 0 : branch.Name);
197 : }
198 : }
199 :
200 : // Set up LoopSide level variables if applicable
201 70 : if (loop_side.HasPressureComponents) {
202 0 : if (LoopSideNum == DataPlant::LoopSideLocation::Demand) {
203 :
204 0 : SetupOutputVariable(state,
205 : "Plant Demand Side Loop Pressure Difference",
206 : Constant::Units::Pa,
207 0 : loop_side.PressureDrop,
208 : OutputProcessor::TimeStepType::System,
209 : OutputProcessor::StoreType::Average,
210 0 : loop.Name);
211 :
212 0 : } else if (LoopSideNum == DataPlant::LoopSideLocation::Supply) {
213 :
214 0 : SetupOutputVariable(state,
215 : "Plant Supply Side Loop Pressure Difference",
216 : Constant::Units::Pa,
217 0 : loop_side.PressureDrop,
218 : OutputProcessor::TimeStepType::System,
219 : OutputProcessor::StoreType::Average,
220 0 : loop.Name);
221 : }
222 : }
223 : }
224 :
225 35 : if (loop.HasPressureComponents) {
226 0 : bool SeriesPressureComponentFound = false;
227 0 : state.dataPlantPressureSys->FullParallelBranchSetFound[static_cast<int>(DataPlant::LoopSideLocation::Demand)] =
228 0 : state.dataPlantPressureSys->FullParallelBranchSetFound[static_cast<int>(DataPlant::LoopSideLocation::Supply)] = false;
229 :
230 : // Set up loop level variables if applicable
231 :
232 0 : SetupOutputVariable(state,
233 : "Plant Loop Pressure Difference",
234 : Constant::Units::Pa,
235 0 : loop.PressureDrop,
236 : OutputProcessor::TimeStepType::System,
237 : OutputProcessor::StoreType::Average,
238 0 : loop.Name);
239 :
240 : // Check for illegal configurations on this plant loop
241 0 : for (DataPlant::LoopSideLocation LoopSideNum : DataPlant::LoopSideKeys) {
242 : // Check for illegal parallel branch setups
243 0 : auto &loop_side(loop.LoopSide(LoopSideNum));
244 0 : int BranchPressureTally = 0;
245 0 : int NumBranches = size(loop_side.Branch);
246 0 : if (NumBranches > 2) {
247 0 : for (int BranchNum = 2; BranchNum <= NumBranches - 1; ++BranchNum) {
248 0 : if (loop_side.Branch(BranchNum).HasPressureComponents) {
249 0 : loop_side.HasParallelPressComps = true;
250 0 : ++BranchPressureTally;
251 : }
252 : }
253 : }
254 0 : if (BranchPressureTally == 0) {
255 : // no parallel branches, ok for this check
256 0 : } else if (BranchPressureTally == isize(loop_side.Branch) - 2) {
257 : // all parallel branches have pressure components
258 0 : state.dataPlantPressureSys->FullParallelBranchSetFound[static_cast<int>(LoopSideNum)] = true;
259 : } else {
260 : // we aren't ok
261 0 : ShowSevereError(state, format("Pressure drop component configuration error detected on loop: {}", loop.Name));
262 0 : ShowContinueError(state, "Pressure drop components must be on ALL or NONE of the parallel branches.");
263 0 : ShowContinueError(state, "Partial distribution is not allowed.");
264 0 : ErrorsFound = true;
265 : }
266 0 : if (loop_side.Branch(1).HasPressureComponents || loop_side.Branch(NumBranches).HasPressureComponents) {
267 : // we have a series component pressure branch (whether a single branch half loop or mixer/splitter setup
268 0 : SeriesPressureComponentFound = true;
269 : }
270 : }
271 :
272 : // Check for full path pressure data
273 0 : if (state.dataPlantPressureSys->FullParallelBranchSetFound[static_cast<int>(DataPlant::LoopSideLocation::Demand)] ||
274 0 : state.dataPlantPressureSys->FullParallelBranchSetFound[static_cast<int>(DataPlant::LoopSideLocation::Supply)] ||
275 : SeriesPressureComponentFound) {
276 : // we are fine, either way we will always have a path with at least one pressure component hit
277 : } else {
278 0 : ShowSevereError(state, format("Pressure drop component configuration error detected on loop: {}", loop.Name));
279 0 : ShowContinueError(state, "The loop has at least one fluid path which does not encounter a pressure component.");
280 0 : ShowContinueError(state, "Either use at least one serial component for pressure drop OR all possible parallel paths");
281 0 : ShowContinueError(state, "must be pressure drop components.");
282 0 : ErrorsFound = true;
283 : } // valid pressure path
284 :
285 : } // Has pressure components
286 :
287 35 : if (ErrorsFound) ShowFatalError(state, "Preceding errors cause program termination");
288 :
289 : // Also issue one time warning if there is a mismatch between plant loop simulation type and whether objects were entered
290 35 : if (loop.HasPressureComponents && (loop.PressureSimType == DataPlant::PressSimType::NoPressure)) {
291 : // Then we found pressure components on the branches, but the plant loop said it didn't want to do pressure simulation
292 0 : ShowWarningError(state, format("Error for pressure simulation on plant loop: {}", loop.Name));
293 0 : ShowContinueError(state, "Plant loop contains pressure simulation components on the branches,");
294 0 : ShowContinueError(state, " yet in the PlantLoop object, there is no pressure simulation specified.");
295 0 : ShowContinueError(state, "Simulation continues, ignoring pressure simulation data.");
296 35 : } else if ((!loop.HasPressureComponents) && (loop.PressureSimType != DataPlant::PressSimType::NoPressure)) {
297 : // Then we don't have any pressure components on the branches, yet the plant loop wants to do some sort of pressure simulation
298 0 : ShowWarningError(state, format("Error for pressure simulation on plant loop: {}", loop.Name));
299 0 : ShowContinueError(state, "Plant loop is requesting a pressure simulation,");
300 0 : ShowContinueError(state, " yet there are no pressure simulation components detected on any of the branches in that loop.");
301 0 : ShowContinueError(state, "Simulation continues, ignoring pressure simulation data.");
302 : }
303 :
304 35 : state.dataPlantPressureSys->LoopInit(LoopNum) = false;
305 :
306 : } // LoopInit = TRUE
307 :
308 : // Initialize the entire plant loop to the outdoor pressure if that loop has data
309 : // This value at the demand side outlet node will be used as a starting reference point
310 : // for pressure calcs
311 : // The value is smeared across the loop, however, so that any nodes before a pump will
312 : // have a proper value for pressure
313 88210 : if (loop.HasPressureComponents && FirstHVACIteration) {
314 0 : for (DataPlant::LoopSideLocation LoopSideNum : DataPlant::LoopSideKeys) {
315 0 : auto const &loop_side(loop.LoopSide(LoopSideNum));
316 0 : for (int BranchNum = 1, BranchNum_end = isize(loop_side.Branch); BranchNum <= BranchNum_end; ++BranchNum) {
317 0 : auto const &branch(loop_side.Branch(BranchNum));
318 0 : for (int CompNum = 1, CompNum_end = isize(branch.Comp); CompNum <= CompNum_end; ++CompNum) {
319 0 : auto const &component(branch.Comp(CompNum));
320 0 : state.dataLoopNodes->Node(component.NodeNumIn).Press = state.dataEnvrn->StdBaroPress;
321 0 : state.dataLoopNodes->Node(component.NodeNumOut).Press = state.dataEnvrn->StdBaroPress;
322 : }
323 : }
324 : }
325 : }
326 :
327 : // Now tell the pump routine whether or not to use the pressure data to calculate power
328 88210 : if (loop.HasPressureComponents) {
329 0 : loop.UsePressureForPumpCalcs = !FirstHVACIteration;
330 : } else { // No Pressure Components
331 88210 : loop.UsePressureForPumpCalcs = false;
332 : }
333 :
334 : // Before we leave, override any settings in case we are doing common pipe simulation
335 88210 : if (loop.HasPressureComponents) {
336 : // We need to make sure we aren't doing an invalid configuration here
337 0 : if (loop.CommonPipeType != DataPlant::CommonPipeType::No) {
338 : // There is a common pipe!
339 0 : if (!state.dataPlantPressureSys->CommonPipeErrorEncountered) {
340 0 : ShowSevereError(state, format("Invalid pressure simulation configuration for Plant Loop={}", loop.Name));
341 0 : ShowContinueError(state, "Currently pressure simulations cannot be performed for loops with common pipes.");
342 0 : ShowContinueError(state, "To repair, either remove the common pipe simulation, or remove the pressure simulation.");
343 0 : ShowContinueError(state, "The simulation will continue, but the pump power is not updated with pressure drop data.");
344 0 : ShowContinueError(state, "Check all results including node pressures to ensure proper simulation.");
345 0 : ShowContinueError(state, "This message is reported once, but may have been encountered in multiple loops.");
346 0 : state.dataPlantPressureSys->CommonPipeErrorEncountered = true;
347 : }
348 0 : loop.UsePressureForPumpCalcs = false;
349 : }
350 : }
351 88210 : }
352 :
353 0 : void BranchPressureDrop(EnergyPlusData &state,
354 : int const LoopNum, // Plant Loop Index
355 : const DataPlant::LoopSideLocation LoopSideNum, // LoopSide on Plant Loop LoopNum
356 : int const BranchNum // Branch Index on LoopSide LoopSideNum
357 : )
358 : {
359 :
360 : // SUBROUTINE INFORMATION:
361 : // AUTHOR Edwin Lee
362 : // DATE WRITTEN August 2009
363 : // MODIFIED na
364 : // RE-ENGINEERED na
365 :
366 : // PURPOSE OF THIS SUBROUTINE:
367 : // This will choose an appropriate pressure drop calculation routine based on structure flags
368 :
369 : // Using/Aliasing
370 : using Curve::CurveValue;
371 : using Curve::PressureCurveValue;
372 :
373 : // SUBROUTINE PARAMETER DEFINITIONS:
374 : static constexpr std::string_view RoutineName("CalcPlantPressureSystem");
375 :
376 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
377 : int InletNodeNum; // Component inlet node number
378 : DataBranchAirLoopPlant::PressureCurveType pressureCurveType; // Type of curve used to evaluate pressure drop
379 : int PressureCurveIndex; // Curve index for PerfCurve structure
380 : Real64 NodeMassFlow; // Nodal mass flow rate {kg/s}
381 : Real64 NodeTemperature; // Nodal temperature {C}
382 : Real64 NodeDensity; // Nodal density {kg/m3}
383 : Real64 NodeViscosity; // Nodal viscosity, assuming mu here (dynamic viscosity)
384 0 : Real64 BranchDeltaPress(0.0); // Pressure drop for component, {Pa}
385 :
386 : // Exit early if need be
387 0 : if (!state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).HasPressureComponents) {
388 0 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).PressureDrop = 0.0;
389 0 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).PressureEffectiveK = 0.0;
390 0 : return;
391 : }
392 :
393 : // Get data from data structure
394 0 : InletNodeNum = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).NodeNumIn;
395 0 : pressureCurveType = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).PressureCurveType;
396 0 : PressureCurveIndex = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).PressureCurveIndex;
397 :
398 : // Get nodal conditions
399 0 : NodeMassFlow = state.dataLoopNodes->Node(InletNodeNum).MassFlowRate;
400 0 : NodeTemperature = state.dataLoopNodes->Node(InletNodeNum).Temp;
401 0 : NodeDensity = state.dataPlnt->PlantLoop(LoopNum).glycol->getDensity(state, NodeTemperature, RoutineName);
402 0 : NodeViscosity = state.dataPlnt->PlantLoop(LoopNum).glycol->getViscosity(state, NodeTemperature, RoutineName);
403 :
404 : // Call the appropriate pressure calculation routine
405 0 : switch (pressureCurveType) {
406 0 : case DataBranchAirLoopPlant::PressureCurveType::Pressure: {
407 : // DeltaP = [f*(L/D) + K] * (rho * V^2) / 2
408 0 : BranchDeltaPress = PressureCurveValue(state, PressureCurveIndex, NodeMassFlow, NodeDensity, NodeViscosity);
409 0 : } break;
410 0 : case DataBranchAirLoopPlant::PressureCurveType::Generic: {
411 : // DeltaP = func(mdot)
412 : // Generic curve, only pass V1=mass flow rate
413 0 : BranchDeltaPress = CurveValue(state, PressureCurveIndex, NodeMassFlow);
414 0 : } break;
415 0 : default: {
416 : // Shouldn't end up here, but just in case
417 0 : ++state.dataPlantPressureSys->ErrorCounter;
418 0 : if (state.dataPlantPressureSys->ErrorCounter == 1) {
419 0 : ShowSevereError(state, "Plant pressure simulation encountered a branch which contains invalid branch pressure curve type.");
420 0 : ShowContinueError(state,
421 0 : format("Occurs for branch: {}", state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).Name));
422 0 : ShowContinueError(state, "This error will be issued only once, although other branches may encounter the same problem");
423 0 : ShowContinueError(state, "For now, pressure drop on this branch will be set to zero.");
424 0 : ShowContinueError(state, "Verify all pressure inputs and pressure drop output variables to ensure proper simulation");
425 : }
426 0 : } break;
427 : }
428 :
429 : // Log this pressure in the data structure to be handled by the update routine later
430 0 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).PressureDrop = BranchDeltaPress;
431 :
432 : // Update the effective K-value for this branch
433 0 : if (NodeMassFlow > 0.0) {
434 0 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).PressureEffectiveK = BranchDeltaPress / pow_2(NodeMassFlow);
435 : } else {
436 0 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).PressureEffectiveK = 0.0;
437 : }
438 : }
439 :
440 0 : void UpdatePressureDrop(EnergyPlusData &state, int const LoopNum)
441 : {
442 :
443 : // SUBROUTINE INFORMATION:
444 : // AUTHOR Edwin Lee
445 : // DATE WRITTEN August 2009
446 : // MODIFIED na
447 : // RE-ENGINEERED na
448 :
449 : // PURPOSE OF THIS SUBROUTINE:
450 : // Evaluate the pressure drop across an entire plant loop and places the value
451 : // on the PlantLoop(:) data structure for the pump to use
452 :
453 : // METHODOLOGY EMPLOYED:
454 : // Assumes that the supply inlet is the starting node, which will be set to some standard pressure
455 : // Then we move around the loop backward from this reference point and go until we hit a pump and stop.
456 : // The pressure difference from reference to pump is the new required pump head.
457 :
458 : // Using/Aliasing
459 :
460 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
461 : int BranchNum;
462 : Real64 LoopSidePressureDrop;
463 : Real64 LoopPressureDrop;
464 0 : Array1D<Real64> ParallelBranchPressureDrops;
465 0 : Array1D<Real64> ParallelBranchInletPressures;
466 : int ParallelBranchCounter;
467 : Real64 SplitterInletPressure;
468 : Real64 MixerPressure;
469 : bool FoundAPumpOnBranch;
470 : Real64 EffectiveLoopKValue;
471 : Real64 EffectiveLoopSideKValue;
472 : Real64 TempVal_SumOfOneByRootK;
473 :
474 : // Exit if not needed
475 0 : if (!state.dataPlnt->PlantLoop(LoopNum).HasPressureComponents) return;
476 :
477 : // Now go through and update the pressure drops as needed
478 0 : FoundAPumpOnBranch = false;
479 0 : LoopPressureDrop = 0.0;
480 0 : for (DataPlant::LoopSideLocation LoopSideNum : DataPlant::LoopSideKeys) { // Start at demand side outlet
481 :
482 : // Loop through all branches on this loop side
483 0 : LoopSidePressureDrop = 0.0;
484 0 : int NumBranches = size(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch);
485 :
486 : // Split here based on a single branch loop or a splitter/mixer configuration
487 0 : if (NumBranches == 1) { // Just do the single branch
488 :
489 : //***SINGLE BRANCH***!
490 0 : BranchNum = 1;
491 0 : Real64 BranchPressureDropValue = 0.0;
492 0 : DistributePressureOnBranch(state, LoopNum, LoopSideNum, BranchNum, BranchPressureDropValue, FoundAPumpOnBranch);
493 0 : LoopSidePressureDrop += BranchPressureDropValue;
494 0 : LoopPressureDrop += BranchPressureDropValue;
495 : //*******************!
496 :
497 0 : } else if (NumBranches > 1) { // Loop through all branches on this loop side, mixer/splitter configuration
498 :
499 : //***OUTLET BRANCH***!
500 0 : BranchNum = NumBranches;
501 0 : Real64 BranchPressureDropValue = 0.0;
502 0 : DistributePressureOnBranch(state, LoopNum, LoopSideNum, BranchNum, BranchPressureDropValue, FoundAPumpOnBranch);
503 0 : LoopSidePressureDrop += BranchPressureDropValue;
504 0 : LoopPressureDrop += BranchPressureDropValue;
505 : //*******************!
506 :
507 : //***MIXER SIMULATION***!
508 0 : MixerPressure = state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).NodeNumIn).Press;
509 0 : PassPressureAcrossMixer(state, LoopNum, LoopSideNum, MixerPressure, NumBranches);
510 : //**********************!
511 :
512 : //***PARALLEL BRANCHES***!
513 0 : if (allocated(ParallelBranchPressureDrops)) ParallelBranchPressureDrops.deallocate();
514 0 : ParallelBranchPressureDrops.allocate(NumBranches - 2);
515 0 : if (allocated(ParallelBranchInletPressures)) ParallelBranchInletPressures.deallocate();
516 0 : ParallelBranchInletPressures.allocate(NumBranches - 2);
517 0 : ParallelBranchCounter = 0;
518 :
519 : // Reset Pump found flag to false, to check if actually found on one of the parallel branches
520 0 : FoundAPumpOnBranch = false;
521 0 : for (BranchNum = NumBranches - 1; BranchNum >= 2; --BranchNum) { // Working backward (not necessary, but consistent)
522 0 : ++ParallelBranchCounter;
523 0 : DistributePressureOnBranch(
524 0 : state, LoopNum, LoopSideNum, BranchNum, ParallelBranchPressureDrops(ParallelBranchCounter), FoundAPumpOnBranch);
525 : // Store the branch inlet pressure so we can pass it properly across the splitter
526 0 : ParallelBranchInletPressures(ParallelBranchCounter) =
527 0 : state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).NodeNumIn).Press;
528 : }
529 :
530 : // Now take max inlet pressure to pass across splitter and max branch pressure for bookkeeping
531 0 : SplitterInletPressure = maxval(ParallelBranchInletPressures);
532 0 : BranchPressureDropValue = maxval(ParallelBranchPressureDrops);
533 0 : LoopSidePressureDrop += BranchPressureDropValue;
534 0 : LoopPressureDrop += BranchPressureDropValue;
535 : //**********************!
536 :
537 : // If we found pumps on the parallel branches then we are done,
538 : // If we are on the demand side, we have a common pipe situation and should issue a warning
539 0 : if (FoundAPumpOnBranch) {
540 0 : if (LoopSideNum == DataPlant::LoopSideLocation::Demand) {
541 0 : ShowSevereError(state, "Pressure system information was found in a demand pump (common pipe) simulation");
542 0 : ShowContinueError(state, "Currently the pressure simulation is not set up to handle common pipe simulations");
543 0 : ShowContinueError(state, "Either modify simulation to avoid common pipe, or remove pressure curve information");
544 0 : ShowFatalError(state, "Pressure configuration mismatch causes program termination");
545 : }
546 : // If we are on the supply side, we simply hit the branch pump, so we exit the IF statement as
547 : // we don't need to simulate the splitter or inlet branch
548 : // For now, not doing anything will leave the IF block
549 : }
550 :
551 : // If we haven't found a pump on the parallel branches then we need to go ahead
552 : // and simulate the splitter and inlet branch
553 :
554 : // This may all be superfluous, if we just simulate the splitter and inlet branch we may be fine
555 : // even if there were branch pumps found.
556 0 : if (!FoundAPumpOnBranch) {
557 :
558 : //***SPLITTER SIMULATION***!
559 0 : PassPressureAcrossSplitter(state, LoopNum, LoopSideNum, SplitterInletPressure);
560 : //*************************!
561 :
562 : //***INLET BRANCH***!
563 0 : BranchNum = 1;
564 0 : DistributePressureOnBranch(state, LoopNum, LoopSideNum, BranchNum, BranchPressureDropValue, FoundAPumpOnBranch);
565 0 : LoopSidePressureDrop += BranchPressureDropValue;
566 0 : LoopPressureDrop += BranchPressureDropValue;
567 : //******************!
568 :
569 : //***PLANT INTERFACE***!
570 0 : if (LoopSideNum == DataPlant::LoopSideLocation::Demand) {
571 0 : PassPressureAcrossInterface(state, LoopNum);
572 : }
573 : //*********************!
574 : }
575 : }
576 :
577 0 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).PressureDrop = LoopSidePressureDrop;
578 :
579 : } // LoopSides on this loop
580 :
581 0 : state.dataPlnt->PlantLoop(LoopNum).PressureDrop = LoopPressureDrop;
582 :
583 : // Now do effective K value calculations
584 0 : EffectiveLoopKValue = 0.0;
585 :
586 0 : for (DataPlant::LoopSideLocation LoopSideNum : DataPlant::LoopSideKeys) {
587 :
588 0 : EffectiveLoopSideKValue = 0.0;
589 :
590 : // Always take the first branch K, it may be the only branch on this half loop
591 0 : EffectiveLoopSideKValue += state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(1).PressureEffectiveK;
592 :
593 : // If there is only one branch then move to the other loop side
594 0 : if (size(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch) == 1) continue;
595 :
596 : // Add parallel branches if necessary by adding them as SUM(1/(sqrt(K_i)))
597 0 : TempVal_SumOfOneByRootK = 0.0;
598 0 : for (BranchNum = 2; BranchNum <= isize(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch) - 1; ++BranchNum) {
599 :
600 : // Only add this branch if the K value is non-zero
601 0 : if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).PressureEffectiveK > 0.0) {
602 0 : TempVal_SumOfOneByRootK +=
603 0 : (1.0 / std::sqrt(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).PressureEffectiveK));
604 : }
605 : }
606 :
607 : // Add parallel branches if they are greater than zero, by taking the sum and performing (1/(SUM^2))
608 0 : if (TempVal_SumOfOneByRootK > 0.0) EffectiveLoopSideKValue += (1.0 / pow_2(TempVal_SumOfOneByRootK));
609 :
610 : // Always take the last branch K, it will be in series
611 0 : BranchNum = size(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch);
612 0 : EffectiveLoopSideKValue += state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).PressureEffectiveK;
613 :
614 : // Assign this loop side's K-value
615 0 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).PressureEffectiveK = EffectiveLoopSideKValue;
616 :
617 : // Keep adding the overall loop K-value
618 0 : EffectiveLoopKValue += EffectiveLoopSideKValue;
619 : }
620 :
621 : // Assign this loop's K-value
622 0 : state.dataPlnt->PlantLoop(LoopNum).PressureEffectiveK = EffectiveLoopKValue;
623 0 : }
624 :
625 0 : void DistributePressureOnBranch(EnergyPlusData &state,
626 : int const LoopNum,
627 : const DataPlant::LoopSideLocation LoopSideNum,
628 : int const BranchNum,
629 : Real64 &BranchPressureDrop,
630 : bool &PumpFound)
631 : {
632 :
633 : // SUBROUTINE INFORMATION:
634 : // AUTHOR Edwin Lee
635 : // DATE WRITTEN August 2009
636 : // MODIFIED na
637 : // RE-ENGINEERED na
638 :
639 : // PURPOSE OF THIS SUBROUTINE:
640 : // Apply proper pressure to nodes along branch
641 :
642 : // METHODOLOGY EMPLOYED:
643 : // Move backward through components, passing pressure upstream
644 : // Account for branch pressure drop at branch inlet node
645 : // Update PlantLoop(:)%LoopSide(:)%Branch(:)%PressureDrop Variable
646 :
647 : // Initialize
648 0 : Real64 TempBranchPressureDrop = 0.0;
649 0 : BranchPressureDrop = 0.0;
650 0 : int NumCompsOnBranch = size(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).Comp);
651 :
652 : // Retrieve temporary branch pressure drop
653 0 : if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).HasPressureComponents) {
654 0 : TempBranchPressureDrop = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).PressureDrop;
655 : }
656 :
657 : // If the last component on the branch is the pump, then check if a pressure drop is detected and set the flag and leave
658 0 : if (DataPlant::PlantEquipmentTypeIsPump[static_cast<int>(
659 0 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).Comp(NumCompsOnBranch).Type)]) {
660 0 : PumpFound = true;
661 0 : if (TempBranchPressureDrop != 0.0) {
662 0 : ShowSevereError(state, format("Error in plant pressure simulation for plant loop: {}", state.dataPlnt->PlantLoop(LoopNum).Name));
663 0 : if (LoopSideNum == DataPlant::LoopSideLocation::Demand) {
664 0 : ShowContinueError(
665 : state,
666 0 : format("Occurs for demand side, branch: {}", state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).Name));
667 0 : } else if (LoopSideNum == DataPlant::LoopSideLocation::Supply) {
668 0 : ShowContinueError(
669 : state,
670 0 : format("Occurs for supply side, branch: {}", state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).Name));
671 : }
672 0 : ShowContinueError(state, "Branch contains only a single pump component, yet also a pressure drop component.");
673 0 : ShowContinueError(state, "Either add a second component to this branch after the pump, or move pressure drop data.");
674 0 : ShowFatalError(state, "Preceding pressure drop error causes program termination");
675 : }
676 0 : return;
677 : }
678 :
679 : // Assign official branch pressure drop
680 0 : if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).HasPressureComponents) {
681 0 : BranchPressureDrop = TempBranchPressureDrop;
682 : }
683 :
684 : // Otherwise update the inlet node of the last component on the branch with this corrected pressure
685 : // This essentially sets all the pressure drop on the branch to be accounted for on the last component
686 0 : state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).Comp(NumCompsOnBranch).NodeNumIn).Press =
687 0 : state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).Comp(NumCompsOnBranch).NodeNumOut)
688 0 : .Press +
689 0 : BranchPressureDrop;
690 :
691 : // Then Smear any internal nodes with this new node pressure by working backward through
692 : // all but the last component, and passing node pressure upstream
693 0 : if (NumCompsOnBranch > 1) {
694 0 : for (int CompNum = NumCompsOnBranch - 1; CompNum >= 1; --CompNum) {
695 :
696 : // If this component is a pump, stop passing pressure upstream, and set flag to true for calling routine
697 0 : if (DataPlant::PlantEquipmentTypeIsPump[static_cast<int>(
698 0 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).Comp(CompNum).Type)]) {
699 0 : PumpFound = true;
700 0 : break;
701 : }
702 :
703 : // Otherwise just pass pressure upstream and move on
704 0 : state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).Comp(CompNum).NodeNumIn).Press =
705 0 : state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).Comp(CompNum).NodeNumOut).Press;
706 : }
707 : }
708 : }
709 :
710 0 : void PassPressureAcrossMixer(EnergyPlusData &state,
711 : int const LoopNum,
712 : const DataPlant::LoopSideLocation LoopSideNum,
713 : Real64 const MixerPressure,
714 : int const NumBranchesOnLoopSide)
715 : {
716 :
717 : // SUBROUTINE INFORMATION:
718 : // AUTHOR Edwin Lee
719 : // DATE WRITTEN August 2009
720 : // MODIFIED na
721 : // RE-ENGINEERED na
722 :
723 : // PURPOSE OF THIS SUBROUTINE:
724 : // Set mixer inlet pressures, or in other words, set mixer inlet branch outlet pressures
725 :
726 : // METHODOLOGY EMPLOYED:
727 : // Set outlet node pressures for all parallel branches on this LoopSide
728 : // Note that this is extremely simple, but is set to it's own routine to allow for clarity
729 : // when possible expansion occurs during further development
730 :
731 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
732 : int BranchNum;
733 :
734 0 : for (BranchNum = 2; BranchNum <= NumBranchesOnLoopSide - 1; ++BranchNum) {
735 0 : state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).NodeNumOut).Press = MixerPressure;
736 : }
737 0 : }
738 :
739 0 : void PassPressureAcrossSplitter(EnergyPlusData &state,
740 : int const LoopNum,
741 : const DataPlant::LoopSideLocation LoopSideNum,
742 : Real64 const SplitterInletPressure)
743 : {
744 :
745 : // SUBROUTINE INFORMATION:
746 : // AUTHOR Edwin Lee
747 : // DATE WRITTEN August 2009
748 : // MODIFIED na
749 : // RE-ENGINEERED na
750 :
751 : // PURPOSE OF THIS SUBROUTINE:
752 : // Set the splitter inlet pressure in anticipation of the inlet branch pressure being simulated
753 :
754 : // METHODOLOGY EMPLOYED:
755 : // Set outlet node of LoopSide inlet branch to splitter pressure
756 : // Note that this is extremely simple, but is set to it's own routine to allow for clarity
757 : // when possible expansion occurs during further development
758 :
759 : // SUBROUTINE PARAMETER DEFINITIONS:
760 0 : int constexpr InletBranchNum(1);
761 :
762 0 : state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(InletBranchNum).NodeNumOut).Press =
763 : SplitterInletPressure;
764 0 : }
765 :
766 : //=================================================================================================!
767 :
768 0 : void PassPressureAcrossInterface(EnergyPlusData &state, int const LoopNum)
769 : {
770 :
771 : // SUBROUTINE INFORMATION:
772 : // AUTHOR Edwin Lee
773 : // DATE WRITTEN August 2009
774 : // MODIFIED na
775 : // RE-ENGINEERED na
776 :
777 : // PURPOSE OF THIS SUBROUTINE:
778 : // Pass pressure backward across plant demand inlet/supply outlet interface
779 :
780 : // METHODOLOGY EMPLOYED:
781 : // Set outlet node pressure of supply side equal to inlet node pressure of demand side
782 : // Note that this is extremely simple, but is set to it's own routine to allow for clarity
783 : // when possible expansion occurs during further development
784 :
785 : // Using/Aliasing
786 :
787 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
788 : int DemandInletNodeNum;
789 : int SupplyOutletNodeNum;
790 :
791 0 : DemandInletNodeNum = state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Demand).NodeNumIn;
792 0 : SupplyOutletNodeNum = state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Supply).NodeNumOut;
793 :
794 0 : state.dataLoopNodes->Node(SupplyOutletNodeNum).Press = state.dataLoopNodes->Node(DemandInletNodeNum).Press;
795 0 : }
796 :
797 0 : Real64 ResolveLoopFlowVsPressure(EnergyPlusData &state,
798 : int const LoopNum, // - Index of which plant/condenser loop is being simulated
799 : Real64 const SystemMassFlow, // - Initial "guess" at system mass flow rate [kg/s]
800 : int const PumpCurveNum, // - Pump curve to use when calling the curve manager for psi = f(phi)
801 : Real64 const PumpSpeed, // - Pump rotational speed, [rps] (revs per second)
802 : Real64 const PumpImpellerDia, // - Nominal pump impeller diameter [m]
803 : Real64 const MinPhi, // - Minimum allowable value of phi, requested by the pump manager from curve mgr
804 : Real64 const MaxPhi // - Maximum allowable value of phi, requested by the pump manager from curve mgr
805 : )
806 : {
807 :
808 : // FUNCTION INFORMATION:
809 : // AUTHOR Kaustubh Phalak
810 : // DATE WRITTEN Feb 2010
811 : // MODIFIED na
812 : // RE-ENGINEERED na
813 :
814 : // PURPOSE OF THIS FUNCTION:
815 : // To provide a means to simulate a constant speed pump curve and system curve to
816 : // find a more realistic operating point for the plant.
817 :
818 : // METHODOLOGY EMPLOYED:
819 : // Pressure drop of complete loop is found for a particular flow rate.
820 : // i.e. pressuredrop = K * massflow ^ 2
821 : // System curve is then solved with pump curve already entered
822 : // and flow rate provided by the pump will be calculated.
823 : // This routine does not trap for errors if a pressure simulation is not to be performed.
824 : // Calling routine should only call this if needed.
825 :
826 : // Using/Aliasing
827 : using Curve::CurveValue;
828 :
829 : // Return value
830 : Real64 ResolvedLoopMassFlowRate;
831 :
832 : // FUNCTION PARAMETER DEFINITIONS:
833 : static constexpr std::string_view RoutineName("ResolvedLoopMassFlowRate: ");
834 0 : int constexpr MaxIters(100);
835 0 : Real64 constexpr PressureConvergeCriteria(0.1); // Pa
836 0 : Real64 constexpr ZeroTolerance(0.0001);
837 :
838 : // FUNCTION LOCAL VARIABLE DECLARATIONS:
839 : Real64 PumpPressureRise;
840 : Real64 NodeTemperature;
841 : Real64 NodeDensity;
842 : Real64 SystemPressureDrop;
843 : Real64 PhiPump;
844 : Real64 PhiSystem;
845 : Real64 PsiPump;
846 : int Iteration;
847 : Real64 LocalSystemMassFlow;
848 : Real64 LoopEffectiveK;
849 : bool Converged;
850 0 : Array1D<Real64> MassFlowIterativeHistory(3);
851 : Real64 MdotDeltaLatest;
852 : Real64 MdotDeltaPrevious;
853 : Real64 DampingFactor;
854 :
855 : // Get loop level data
856 0 : LoopEffectiveK = state.dataPlnt->PlantLoop(LoopNum).PressureEffectiveK;
857 0 : SystemPressureDrop = LoopEffectiveK * pow_2(SystemMassFlow);
858 :
859 : // Read data off the node data structure
860 0 : NodeTemperature = state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Supply).NodeNumIn).Temp;
861 0 : NodeDensity = state.dataPlnt->PlantLoop(LoopNum).glycol->getDensity(state, NodeTemperature, RoutineName);
862 :
863 : // Store the passed in (requested, design) flow to the local value for performing iterations
864 0 : LocalSystemMassFlow = SystemMassFlow;
865 :
866 : // Check and warn if invalid condition exists
867 0 : if (LoopEffectiveK <= ZeroTolerance) {
868 0 : ++state.dataPlantPressureSys->ZeroKWarningCounter;
869 0 : if (state.dataPlantPressureSys->ZeroKWarningCounter == 1) {
870 0 : ShowWarningError(state, "Pump pressure-flow resolution attempted, but invalid loop conditions encountered.");
871 0 : ShowContinueError(state, format("Loop being calculated: {}", state.dataPlnt->PlantLoop(LoopNum).Name));
872 0 : ShowContinueError(state, "An invalid pressure/flow condition existed which resulted in the approximation of");
873 0 : ShowContinueError(state, "the pressure coefficient K to be zero. The pressure simulation will use the requested (design)");
874 0 : ShowContinueError(state, "pump flow in order to proceed with the simulation. This warning is only issued once.");
875 : }
876 0 : ResolvedLoopMassFlowRate = SystemMassFlow;
877 0 : return ResolvedLoopMassFlowRate;
878 : }
879 :
880 : // Initialize flag
881 0 : Converged = false;
882 :
883 : // Initialize the mass flow history array and damping factor
884 0 : MassFlowIterativeHistory = LocalSystemMassFlow;
885 0 : DampingFactor = 0.9;
886 :
887 : // Start Convergence Loop
888 0 : for (Iteration = 1; Iteration <= MaxIters; ++Iteration) {
889 :
890 : // Calculate System Mass Flow Rate
891 0 : LocalSystemMassFlow = std::sqrt(SystemPressureDrop / LoopEffectiveK);
892 :
893 0 : MassFlowIterativeHistory = eoshift(MassFlowIterativeHistory, -1, LocalSystemMassFlow);
894 :
895 0 : PhiSystem = LocalSystemMassFlow / (NodeDensity * PumpSpeed * PumpImpellerDia);
896 :
897 : // 4th order polynomial for non-dimensional pump curve
898 0 : PhiPump = PhiSystem;
899 :
900 : // Constrain the value to the valid region
901 0 : PhiPump = max(PhiPump, MinPhi);
902 0 : PhiPump = min(PhiPump, MaxPhi);
903 :
904 : // Get the pump curve value from the curve manager
905 0 : PsiPump = CurveValue(state, PumpCurveNum, PhiPump);
906 :
907 : // Calculate Pump Pressure rise
908 0 : PumpPressureRise = PsiPump * NodeDensity * pow_2(PumpSpeed) * pow_2(PumpImpellerDia);
909 :
910 : // Convergence Criteria Based on Pressure
911 0 : if (std::abs(SystemPressureDrop - PumpPressureRise) < (PressureConvergeCriteria)) {
912 0 : ResolvedLoopMassFlowRate = LocalSystemMassFlow;
913 0 : Converged = true;
914 0 : break;
915 : }
916 :
917 0 : if (Iteration < 2) {
918 : // Don't do anything?
919 : } else {
920 0 : MdotDeltaLatest = std::abs(MassFlowIterativeHistory(1) - MassFlowIterativeHistory(2));
921 0 : MdotDeltaPrevious = std::abs(MassFlowIterativeHistory(2) - MassFlowIterativeHistory(3));
922 0 : if (MdotDeltaLatest < MdotDeltaPrevious) {
923 : // we are converging
924 : // DampingFactor = MIN(DampingFactor * 1.1, 0.9d0)
925 : } else {
926 : // we are stuck or diverging
927 0 : DampingFactor *= 0.9;
928 : }
929 : }
930 :
931 : // Update pressure value with damping factor
932 0 : SystemPressureDrop = DampingFactor * PumpPressureRise + (1.0 - DampingFactor) * SystemPressureDrop;
933 : }
934 :
935 : // Check if we didn't converge
936 0 : if (!Converged) {
937 0 : ++state.dataPlantPressureSys->MaxIterWarningCounter;
938 0 : if (state.dataPlantPressureSys->MaxIterWarningCounter == 1) {
939 0 : ShowWarningError(state, "Pump pressure-flow resolution attempted, but iteration loop did not converge.");
940 0 : ShowContinueError(state, format("Loop being calculated: {}", state.dataPlnt->PlantLoop(LoopNum).Name));
941 0 : ShowContinueError(state, "A mismatch between the pump curve entered and the pressure drop components");
942 0 : ShowContinueError(state, "on the loop may be the cause. The pressure simulation will use the requested (design)");
943 0 : ShowContinueError(state, "pump flow in order to proceed with the simulation. This warning is only issued once.");
944 : }
945 0 : ResolvedLoopMassFlowRate = SystemMassFlow;
946 0 : return ResolvedLoopMassFlowRate;
947 : }
948 :
949 0 : return ResolvedLoopMassFlowRate;
950 0 : }
951 :
952 : //=================================================================================================!
953 :
954 : } // namespace EnergyPlus::PlantPressureSystem
|