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 318899170 : 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 355367680 : if ((state.dataPlnt->PlantLoop(LoopNum).PressureSimType == DataPlant::PressSimType::NoPressure) &&
125 36468510 : ((CallType == DataPlant::PressureCall::Calc) || (CallType == DataPlant::PressureCall::Update))) {
126 300559346 : return;
127 : }
128 :
129 : // Pass to another routine based on calling flag
130 18339824 : switch (CallType) {
131 18249734 : case DataPlant::PressureCall::Init: {
132 18249734 : InitPressureDrop(state, LoopNum, FirstHVACIteration);
133 18249734 : } break;
134 79560 : case DataPlant::PressureCall::Calc: {
135 79560 : BranchPressureDrop(state, LoopNum, LoopSideNum, BranchNum); // Autodesk:OPTIONAL LoopSideNum, BranchNum used without PRESENT check
136 79560 : } break;
137 10530 : case DataPlant::PressureCall::Update: {
138 10530 : UpdatePressureDrop(state, LoopNum);
139 10530 : } break;
140 0 : default: {
141 : // Calling routines should only use the three possible keywords here
142 0 : } break;
143 : }
144 : }
145 :
146 18249734 : void InitPressureDrop(EnergyPlusData &state, int const LoopNum, bool const FirstHVACIteration)
147 : {
148 :
149 : // SUBROUTINE INFORMATION:
150 : // AUTHOR Edwin Lee
151 : // DATE WRITTEN August 2009
152 : // MODIFIED na
153 : // RE-ENGINEERED na
154 :
155 : // PURPOSE OF THIS SUBROUTINE:
156 : // Initializes output variables and data structure
157 : // On FirstHVAC, updates the demand inlet node pressure
158 :
159 18249734 : if (state.dataPlantPressureSys->InitPressureDropOneTimeInit) {
160 : // First allocate the initialization array to each plant loop
161 462 : state.dataPlantPressureSys->LoopInit.allocate(size(state.dataPlnt->PlantLoop));
162 462 : state.dataPlantPressureSys->LoopInit = true;
163 462 : state.dataPlantPressureSys->InitPressureDropOneTimeInit = false;
164 : }
165 :
166 18249734 : auto &loop(state.dataPlnt->PlantLoop(LoopNum));
167 :
168 : // CurrentModuleObject='Curve:Functional:PressureDrop'
169 18249734 : if (state.dataPlantPressureSys->LoopInit(LoopNum)) {
170 :
171 : // Initialize
172 1157 : bool ErrorsFound(false);
173 :
174 : // Need to go along plant loop and set up component pressure drop data structure!
175 3471 : for (DataPlant::LoopSideLocation LoopSideNum : DataPlant::LoopSideKeys) {
176 2314 : auto &loop_side(loop.LoopSide(LoopSideNum));
177 :
178 : // Loop through all branches on this loop side
179 16262 : for (int BranchNum = 1; BranchNum <= isize(loop_side.Branch); ++BranchNum) {
180 13948 : auto &branch(loop_side.Branch(BranchNum));
181 :
182 : // If this branch has valid pressure drop data
183 13948 : if (branch.PressureCurveIndex > 0) {
184 :
185 : // Update flags for higher level structure
186 4 : branch.HasPressureComponents = true;
187 4 : loop_side.HasPressureComponents = true;
188 4 : loop.HasPressureComponents = true;
189 :
190 : // Setup output variable
191 8 : SetupOutputVariable(state,
192 : "Plant Branch Pressure Difference",
193 : Constant::Units::Pa,
194 4 : branch.PressureDrop,
195 : OutputProcessor::TimeStepType::System,
196 : OutputProcessor::StoreType::Average,
197 4 : branch.Name);
198 : }
199 : }
200 :
201 : // Set up LoopSide level variables if applicable
202 2314 : if (loop_side.HasPressureComponents) {
203 4 : if (LoopSideNum == DataPlant::LoopSideLocation::Demand) {
204 :
205 2 : SetupOutputVariable(state,
206 : "Plant Demand Side Loop Pressure Difference",
207 : Constant::Units::Pa,
208 1 : loop_side.PressureDrop,
209 : OutputProcessor::TimeStepType::System,
210 : OutputProcessor::StoreType::Average,
211 1 : loop.Name);
212 :
213 3 : } else if (LoopSideNum == DataPlant::LoopSideLocation::Supply) {
214 :
215 6 : SetupOutputVariable(state,
216 : "Plant Supply Side Loop Pressure Difference",
217 : Constant::Units::Pa,
218 3 : loop_side.PressureDrop,
219 : OutputProcessor::TimeStepType::System,
220 : OutputProcessor::StoreType::Average,
221 3 : loop.Name);
222 : }
223 : }
224 : }
225 :
226 1157 : if (loop.HasPressureComponents) {
227 3 : bool SeriesPressureComponentFound = false;
228 3 : state.dataPlantPressureSys->FullParallelBranchSetFound[static_cast<int>(DataPlant::LoopSideLocation::Demand)] =
229 3 : state.dataPlantPressureSys->FullParallelBranchSetFound[static_cast<int>(DataPlant::LoopSideLocation::Supply)] = false;
230 :
231 : // Set up loop level variables if applicable
232 :
233 6 : SetupOutputVariable(state,
234 : "Plant Loop Pressure Difference",
235 : Constant::Units::Pa,
236 3 : loop.PressureDrop,
237 : OutputProcessor::TimeStepType::System,
238 : OutputProcessor::StoreType::Average,
239 3 : loop.Name);
240 :
241 : // Check for illegal configurations on this plant loop
242 9 : for (DataPlant::LoopSideLocation LoopSideNum : DataPlant::LoopSideKeys) {
243 : // Check for illegal parallel branch setups
244 6 : auto &loop_side(loop.LoopSide(LoopSideNum));
245 6 : int BranchPressureTally = 0;
246 6 : int NumBranches = size(loop_side.Branch);
247 6 : if (NumBranches > 2) {
248 13 : for (int BranchNum = 2; BranchNum <= NumBranches - 1; ++BranchNum) {
249 7 : if (loop_side.Branch(BranchNum).HasPressureComponents) {
250 1 : loop_side.HasParallelPressComps = true;
251 1 : ++BranchPressureTally;
252 : }
253 : }
254 : }
255 6 : if (BranchPressureTally == 0) {
256 : // no parallel branches, ok for this check
257 1 : } else if (BranchPressureTally == isize(loop_side.Branch) - 2) {
258 : // all parallel branches have pressure components
259 1 : state.dataPlantPressureSys->FullParallelBranchSetFound[static_cast<int>(LoopSideNum)] = true;
260 : } else {
261 : // we aren't ok
262 0 : ShowSevereError(state, format("Pressure drop component configuration error detected on loop: {}", loop.Name));
263 0 : ShowContinueError(state, "Pressure drop components must be on ALL or NONE of the parallel branches.");
264 0 : ShowContinueError(state, "Partial distribution is not allowed.");
265 0 : ErrorsFound = true;
266 : }
267 6 : if (loop_side.Branch(1).HasPressureComponents || loop_side.Branch(NumBranches).HasPressureComponents) {
268 : // we have a series component pressure branch (whether a single branch half loop or mixer/splitter setup
269 3 : SeriesPressureComponentFound = true;
270 : }
271 : }
272 :
273 : // Check for full path pressure data
274 6 : if (state.dataPlantPressureSys->FullParallelBranchSetFound[static_cast<int>(DataPlant::LoopSideLocation::Demand)] ||
275 6 : state.dataPlantPressureSys->FullParallelBranchSetFound[static_cast<int>(DataPlant::LoopSideLocation::Supply)] ||
276 : SeriesPressureComponentFound) {
277 : // we are fine, either way we will always have a path with at least one pressure component hit
278 : } else {
279 0 : ShowSevereError(state, format("Pressure drop component configuration error detected on loop: {}", loop.Name));
280 0 : ShowContinueError(state, "The loop has at least one fluid path which does not encounter a pressure component.");
281 0 : ShowContinueError(state, "Either use at least one serial component for pressure drop OR all possible parallel paths");
282 0 : ShowContinueError(state, "must be pressure drop components.");
283 0 : ErrorsFound = true;
284 : } // valid pressure path
285 :
286 : } // Has pressure components
287 :
288 1157 : if (ErrorsFound) {
289 0 : ShowFatalError(state, "Preceding errors cause program termination");
290 : }
291 :
292 : // Also issue one time warning if there is a mismatch between plant loop simulation type and whether objects were entered
293 1157 : if (loop.HasPressureComponents && (loop.PressureSimType == DataPlant::PressSimType::NoPressure)) {
294 : // Then we found pressure components on the branches, but the plant loop said it didn't want to do pressure simulation
295 0 : ShowWarningError(state, format("Error for pressure simulation on plant loop: {}", loop.Name));
296 0 : ShowContinueError(state, "Plant loop contains pressure simulation components on the branches,");
297 0 : ShowContinueError(state, " yet in the PlantLoop object, there is no pressure simulation specified.");
298 0 : ShowContinueError(state, "Simulation continues, ignoring pressure simulation data.");
299 1157 : } else if ((!loop.HasPressureComponents) && (loop.PressureSimType != DataPlant::PressSimType::NoPressure)) {
300 : // Then we don't have any pressure components on the branches, yet the plant loop wants to do some sort of pressure simulation
301 0 : ShowWarningError(state, format("Error for pressure simulation on plant loop: {}", loop.Name));
302 0 : ShowContinueError(state, "Plant loop is requesting a pressure simulation,");
303 0 : ShowContinueError(state, " yet there are no pressure simulation components detected on any of the branches in that loop.");
304 0 : ShowContinueError(state, "Simulation continues, ignoring pressure simulation data.");
305 : }
306 :
307 1157 : state.dataPlantPressureSys->LoopInit(LoopNum) = false;
308 :
309 : } // LoopInit = TRUE
310 :
311 : // Initialize the entire plant loop to the outdoor pressure if that loop has data
312 : // This value at the demand side outlet node will be used as a starting reference point
313 : // for pressure calcs
314 : // The value is smeared across the loop, however, so that any nodes before a pump will
315 : // have a proper value for pressure
316 18249734 : if (loop.HasPressureComponents && FirstHVACIteration) {
317 15795 : for (DataPlant::LoopSideLocation LoopSideNum : DataPlant::LoopSideKeys) {
318 10530 : auto const &loop_side(loop.LoopSide(LoopSideNum));
319 45045 : for (int BranchNum = 1, BranchNum_end = isize(loop_side.Branch); BranchNum <= BranchNum_end; ++BranchNum) {
320 34515 : auto const &branch(loop_side.Branch(BranchNum));
321 69030 : for (int CompNum = 1, CompNum_end = isize(branch.Comp); CompNum <= CompNum_end; ++CompNum) {
322 34515 : auto const &component(branch.Comp(CompNum));
323 34515 : state.dataLoopNodes->Node(component.NodeNumIn).Press = state.dataEnvrn->StdBaroPress;
324 34515 : state.dataLoopNodes->Node(component.NodeNumOut).Press = state.dataEnvrn->StdBaroPress;
325 : }
326 : }
327 : }
328 : }
329 :
330 : // Now tell the pump routine whether or not to use the pressure data to calculate power
331 18249734 : if (loop.HasPressureComponents) {
332 10530 : loop.UsePressureForPumpCalcs = !FirstHVACIteration;
333 : } else { // No Pressure Components
334 18239204 : loop.UsePressureForPumpCalcs = false;
335 : }
336 :
337 : // Before we leave, override any settings in case we are doing common pipe simulation
338 18249734 : if (loop.HasPressureComponents) {
339 : // We need to make sure we aren't doing an invalid configuration here
340 10530 : if (loop.CommonPipeType != DataPlant::CommonPipeType::No) {
341 : // There is a common pipe!
342 0 : if (!state.dataPlantPressureSys->CommonPipeErrorEncountered) {
343 0 : ShowSevereError(state, format("Invalid pressure simulation configuration for Plant Loop={}", loop.Name));
344 0 : ShowContinueError(state, "Currently pressure simulations cannot be performed for loops with common pipes.");
345 0 : ShowContinueError(state, "To repair, either remove the common pipe simulation, or remove the pressure simulation.");
346 0 : ShowContinueError(state, "The simulation will continue, but the pump power is not updated with pressure drop data.");
347 0 : ShowContinueError(state, "Check all results including node pressures to ensure proper simulation.");
348 0 : ShowContinueError(state, "This message is reported once, but may have been encountered in multiple loops.");
349 0 : state.dataPlantPressureSys->CommonPipeErrorEncountered = true;
350 : }
351 0 : loop.UsePressureForPumpCalcs = false;
352 : }
353 : }
354 18249734 : }
355 :
356 79560 : void BranchPressureDrop(EnergyPlusData &state,
357 : int const LoopNum, // Plant Loop Index
358 : const DataPlant::LoopSideLocation LoopSideNum, // LoopSide on Plant Loop LoopNum
359 : int const BranchNum // Branch Index on LoopSide LoopSideNum
360 : )
361 : {
362 :
363 : // SUBROUTINE INFORMATION:
364 : // AUTHOR Edwin Lee
365 : // DATE WRITTEN August 2009
366 : // MODIFIED na
367 : // RE-ENGINEERED na
368 :
369 : // PURPOSE OF THIS SUBROUTINE:
370 : // This will choose an appropriate pressure drop calculation routine based on structure flags
371 :
372 : // Using/Aliasing
373 : using Curve::CurveValue;
374 : using Curve::PressureCurveValue;
375 :
376 : // SUBROUTINE PARAMETER DEFINITIONS:
377 : static constexpr std::string_view RoutineName("CalcPlantPressureSystem");
378 :
379 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
380 : int InletNodeNum; // Component inlet node number
381 : DataBranchAirLoopPlant::PressureCurveType pressureCurveType; // Type of curve used to evaluate pressure drop
382 : int PressureCurveIndex; // Curve index for PerfCurve structure
383 : Real64 NodeMassFlow; // Nodal mass flow rate {kg/s}
384 : Real64 NodeTemperature; // Nodal temperature {C}
385 : Real64 NodeDensity; // Nodal density {kg/m3}
386 : Real64 NodeViscosity; // Nodal viscosity, assuming mu here (dynamic viscosity)
387 79560 : Real64 BranchDeltaPress(0.0); // Pressure drop for component, {Pa}
388 :
389 : // Exit early if need be
390 79560 : if (!state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).HasPressureComponents) {
391 64350 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).PressureDrop = 0.0;
392 64350 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).PressureEffectiveK = 0.0;
393 64350 : return;
394 : }
395 :
396 : // Get data from data structure
397 15210 : InletNodeNum = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).NodeNumIn;
398 15210 : pressureCurveType = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).PressureCurveType;
399 15210 : PressureCurveIndex = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).PressureCurveIndex;
400 :
401 : // Get nodal conditions
402 15210 : NodeMassFlow = state.dataLoopNodes->Node(InletNodeNum).MassFlowRate;
403 15210 : NodeTemperature = state.dataLoopNodes->Node(InletNodeNum).Temp;
404 15210 : NodeDensity = state.dataPlnt->PlantLoop(LoopNum).glycol->getDensity(state, NodeTemperature, RoutineName);
405 15210 : NodeViscosity = state.dataPlnt->PlantLoop(LoopNum).glycol->getViscosity(state, NodeTemperature, RoutineName);
406 :
407 : // Call the appropriate pressure calculation routine
408 15210 : switch (pressureCurveType) {
409 12870 : case DataBranchAirLoopPlant::PressureCurveType::Pressure: {
410 : // DeltaP = [f*(L/D) + K] * (rho * V^2) / 2
411 12870 : BranchDeltaPress = PressureCurveValue(state, PressureCurveIndex, NodeMassFlow, NodeDensity, NodeViscosity);
412 12870 : } break;
413 2340 : case DataBranchAirLoopPlant::PressureCurveType::Generic: {
414 : // DeltaP = func(mdot)
415 : // Generic curve, only pass V1=mass flow rate
416 2340 : BranchDeltaPress = CurveValue(state, PressureCurveIndex, NodeMassFlow);
417 2340 : } break;
418 0 : default: {
419 : // Shouldn't end up here, but just in case
420 0 : ++state.dataPlantPressureSys->ErrorCounter;
421 0 : if (state.dataPlantPressureSys->ErrorCounter == 1) {
422 0 : ShowSevereError(state, "Plant pressure simulation encountered a branch which contains invalid branch pressure curve type.");
423 0 : ShowContinueError(state,
424 0 : format("Occurs for branch: {}", state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).Name));
425 0 : ShowContinueError(state, "This error will be issued only once, although other branches may encounter the same problem");
426 0 : ShowContinueError(state, "For now, pressure drop on this branch will be set to zero.");
427 0 : ShowContinueError(state, "Verify all pressure inputs and pressure drop output variables to ensure proper simulation");
428 : }
429 0 : } break;
430 : }
431 :
432 : // Log this pressure in the data structure to be handled by the update routine later
433 15210 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).PressureDrop = BranchDeltaPress;
434 :
435 : // Update the effective K-value for this branch
436 15210 : if (NodeMassFlow > 0.0) {
437 14550 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).PressureEffectiveK = BranchDeltaPress / pow_2(NodeMassFlow);
438 : } else {
439 660 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).PressureEffectiveK = 0.0;
440 : }
441 : }
442 :
443 10530 : void UpdatePressureDrop(EnergyPlusData &state, int const LoopNum)
444 : {
445 :
446 : // SUBROUTINE INFORMATION:
447 : // AUTHOR Edwin Lee
448 : // DATE WRITTEN August 2009
449 : // MODIFIED na
450 : // RE-ENGINEERED na
451 :
452 : // PURPOSE OF THIS SUBROUTINE:
453 : // Evaluate the pressure drop across an entire plant loop and places the value
454 : // on the PlantLoop(:) data structure for the pump to use
455 :
456 : // METHODOLOGY EMPLOYED:
457 : // Assumes that the supply inlet is the starting node, which will be set to some standard pressure
458 : // Then we move around the loop backward from this reference point and go until we hit a pump and stop.
459 : // The pressure difference from reference to pump is the new required pump head.
460 :
461 : // Using/Aliasing
462 :
463 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
464 : int BranchNum;
465 : Real64 LoopSidePressureDrop;
466 : Real64 LoopPressureDrop;
467 10530 : Array1D<Real64> ParallelBranchPressureDrops;
468 10530 : Array1D<Real64> ParallelBranchInletPressures;
469 : int ParallelBranchCounter;
470 : Real64 SplitterInletPressure;
471 : Real64 MixerPressure;
472 : bool FoundAPumpOnBranch;
473 : Real64 EffectiveLoopKValue;
474 : Real64 EffectiveLoopSideKValue;
475 : Real64 TempVal_SumOfOneByRootK;
476 :
477 : // Exit if not needed
478 10530 : if (!state.dataPlnt->PlantLoop(LoopNum).HasPressureComponents) {
479 0 : return;
480 : }
481 :
482 : // Now go through and update the pressure drops as needed
483 10530 : FoundAPumpOnBranch = false;
484 10530 : LoopPressureDrop = 0.0;
485 31590 : for (DataPlant::LoopSideLocation LoopSideNum : DataPlant::LoopSideKeys) { // Start at demand side outlet
486 :
487 : // Loop through all branches on this loop side
488 21060 : LoopSidePressureDrop = 0.0;
489 21060 : int NumBranches = size(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch);
490 :
491 : // Split here based on a single branch loop or a splitter/mixer configuration
492 21060 : if (NumBranches == 1) { // Just do the single branch
493 :
494 : //***SINGLE BRANCH***!
495 0 : BranchNum = 1;
496 0 : Real64 BranchPressureDropValue = 0.0;
497 0 : DistributePressureOnBranch(state, LoopNum, LoopSideNum, BranchNum, BranchPressureDropValue, FoundAPumpOnBranch);
498 0 : LoopSidePressureDrop += BranchPressureDropValue;
499 0 : LoopPressureDrop += BranchPressureDropValue;
500 : //*******************!
501 :
502 21060 : } else if (NumBranches > 1) { // Loop through all branches on this loop side, mixer/splitter configuration
503 :
504 : //***OUTLET BRANCH***!
505 21060 : BranchNum = NumBranches;
506 21060 : Real64 BranchPressureDropValue = 0.0;
507 21060 : DistributePressureOnBranch(state, LoopNum, LoopSideNum, BranchNum, BranchPressureDropValue, FoundAPumpOnBranch);
508 21060 : LoopSidePressureDrop += BranchPressureDropValue;
509 21060 : LoopPressureDrop += BranchPressureDropValue;
510 : //*******************!
511 :
512 : //***MIXER SIMULATION***!
513 21060 : MixerPressure = state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).NodeNumIn).Press;
514 21060 : PassPressureAcrossMixer(state, LoopNum, LoopSideNum, MixerPressure, NumBranches);
515 : //**********************!
516 :
517 : //***PARALLEL BRANCHES***!
518 21060 : if (allocated(ParallelBranchPressureDrops)) {
519 10530 : ParallelBranchPressureDrops.deallocate();
520 : }
521 21060 : ParallelBranchPressureDrops.allocate(NumBranches - 2);
522 21060 : if (allocated(ParallelBranchInletPressures)) {
523 10530 : ParallelBranchInletPressures.deallocate();
524 : }
525 21060 : ParallelBranchInletPressures.allocate(NumBranches - 2);
526 21060 : ParallelBranchCounter = 0;
527 :
528 : // Reset Pump found flag to false, to check if actually found on one of the parallel branches
529 21060 : FoundAPumpOnBranch = false;
530 47970 : for (BranchNum = NumBranches - 1; BranchNum >= 2; --BranchNum) { // Working backward (not necessary, but consistent)
531 26910 : ++ParallelBranchCounter;
532 26910 : DistributePressureOnBranch(
533 26910 : state, LoopNum, LoopSideNum, BranchNum, ParallelBranchPressureDrops(ParallelBranchCounter), FoundAPumpOnBranch);
534 : // Store the branch inlet pressure so we can pass it properly across the splitter
535 26910 : ParallelBranchInletPressures(ParallelBranchCounter) =
536 26910 : state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).NodeNumIn).Press;
537 : }
538 :
539 : // Now take max inlet pressure to pass across splitter and max branch pressure for bookkeeping
540 21060 : SplitterInletPressure = maxval(ParallelBranchInletPressures);
541 21060 : BranchPressureDropValue = maxval(ParallelBranchPressureDrops);
542 21060 : LoopSidePressureDrop += BranchPressureDropValue;
543 21060 : LoopPressureDrop += BranchPressureDropValue;
544 : //**********************!
545 :
546 : // If we found pumps on the parallel branches then we are done,
547 : // If we are on the demand side, we have a common pipe situation and should issue a warning
548 21060 : if (FoundAPumpOnBranch) {
549 0 : if (LoopSideNum == DataPlant::LoopSideLocation::Demand) {
550 0 : ShowSevereError(state, "Pressure system information was found in a demand pump (common pipe) simulation");
551 0 : ShowContinueError(state, "Currently the pressure simulation is not set up to handle common pipe simulations");
552 0 : ShowContinueError(state, "Either modify simulation to avoid common pipe, or remove pressure curve information");
553 0 : ShowFatalError(state, "Pressure configuration mismatch causes program termination");
554 : }
555 : // If we are on the supply side, we simply hit the branch pump, so we exit the IF statement as
556 : // we don't need to simulate the splitter or inlet branch
557 : // For now, not doing anything will leave the IF block
558 : }
559 :
560 : // If we haven't found a pump on the parallel branches then we need to go ahead
561 : // and simulate the splitter and inlet branch
562 :
563 : // This may all be superfluous, if we just simulate the splitter and inlet branch we may be fine
564 : // even if there were branch pumps found.
565 21060 : if (!FoundAPumpOnBranch) {
566 :
567 : //***SPLITTER SIMULATION***!
568 21060 : PassPressureAcrossSplitter(state, LoopNum, LoopSideNum, SplitterInletPressure);
569 : //*************************!
570 :
571 : //***INLET BRANCH***!
572 21060 : BranchNum = 1;
573 21060 : DistributePressureOnBranch(state, LoopNum, LoopSideNum, BranchNum, BranchPressureDropValue, FoundAPumpOnBranch);
574 21060 : LoopSidePressureDrop += BranchPressureDropValue;
575 21060 : LoopPressureDrop += BranchPressureDropValue;
576 : //******************!
577 :
578 : //***PLANT INTERFACE***!
579 21060 : if (LoopSideNum == DataPlant::LoopSideLocation::Demand) {
580 10530 : PassPressureAcrossInterface(state, LoopNum);
581 : }
582 : //*********************!
583 : }
584 : }
585 :
586 21060 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).PressureDrop = LoopSidePressureDrop;
587 :
588 : } // LoopSides on this loop
589 :
590 10530 : state.dataPlnt->PlantLoop(LoopNum).PressureDrop = LoopPressureDrop;
591 :
592 : // Now do effective K value calculations
593 10530 : EffectiveLoopKValue = 0.0;
594 :
595 31590 : for (DataPlant::LoopSideLocation LoopSideNum : DataPlant::LoopSideKeys) {
596 :
597 21060 : EffectiveLoopSideKValue = 0.0;
598 :
599 : // Always take the first branch K, it may be the only branch on this half loop
600 21060 : EffectiveLoopSideKValue += state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(1).PressureEffectiveK;
601 :
602 : // If there is only one branch then move to the other loop side
603 21060 : if (size(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch) == 1) {
604 0 : continue;
605 : }
606 :
607 : // Add parallel branches if necessary by adding them as SUM(1/(sqrt(K_i)))
608 21060 : TempVal_SumOfOneByRootK = 0.0;
609 47970 : for (BranchNum = 2; BranchNum <= isize(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch) - 1; ++BranchNum) {
610 :
611 : // Only add this branch if the K value is non-zero
612 26910 : if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).PressureEffectiveK > 0.0) {
613 2328 : TempVal_SumOfOneByRootK +=
614 2328 : (1.0 / std::sqrt(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).PressureEffectiveK));
615 : }
616 : }
617 :
618 : // Add parallel branches if they are greater than zero, by taking the sum and performing (1/(SUM^2))
619 21060 : if (TempVal_SumOfOneByRootK > 0.0) {
620 2328 : EffectiveLoopSideKValue += (1.0 / pow_2(TempVal_SumOfOneByRootK));
621 : }
622 :
623 : // Always take the last branch K, it will be in series
624 21060 : BranchNum = size(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch);
625 21060 : EffectiveLoopSideKValue += state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).PressureEffectiveK;
626 :
627 : // Assign this loop side's K-value
628 21060 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).PressureEffectiveK = EffectiveLoopSideKValue;
629 :
630 : // Keep adding the overall loop K-value
631 21060 : EffectiveLoopKValue += EffectiveLoopSideKValue;
632 : }
633 :
634 : // Assign this loop's K-value
635 10530 : state.dataPlnt->PlantLoop(LoopNum).PressureEffectiveK = EffectiveLoopKValue;
636 10530 : }
637 :
638 69030 : void DistributePressureOnBranch(EnergyPlusData &state,
639 : int const LoopNum,
640 : const DataPlant::LoopSideLocation LoopSideNum,
641 : int const BranchNum,
642 : Real64 &BranchPressureDrop,
643 : bool &PumpFound)
644 : {
645 :
646 : // SUBROUTINE INFORMATION:
647 : // AUTHOR Edwin Lee
648 : // DATE WRITTEN August 2009
649 : // MODIFIED na
650 : // RE-ENGINEERED na
651 :
652 : // PURPOSE OF THIS SUBROUTINE:
653 : // Apply proper pressure to nodes along branch
654 :
655 : // METHODOLOGY EMPLOYED:
656 : // Move backward through components, passing pressure upstream
657 : // Account for branch pressure drop at branch inlet node
658 : // Update PlantLoop(:)%LoopSide(:)%Branch(:)%PressureDrop Variable
659 :
660 : // Initialize
661 69030 : Real64 TempBranchPressureDrop = 0.0;
662 69030 : BranchPressureDrop = 0.0;
663 69030 : int NumCompsOnBranch = size(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).Comp);
664 :
665 : // Retrieve temporary branch pressure drop
666 69030 : if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).HasPressureComponents) {
667 12870 : TempBranchPressureDrop = state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).PressureDrop;
668 : }
669 :
670 : // If the last component on the branch is the pump, then check if a pressure drop is detected and set the flag and leave
671 69030 : if (DataPlant::PlantEquipmentTypeIsPump[static_cast<int>(
672 69030 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).Comp(NumCompsOnBranch).Type)]) {
673 10530 : PumpFound = true;
674 10530 : if (TempBranchPressureDrop != 0.0) {
675 0 : ShowSevereError(state, format("Error in plant pressure simulation for plant loop: {}", state.dataPlnt->PlantLoop(LoopNum).Name));
676 0 : if (LoopSideNum == DataPlant::LoopSideLocation::Demand) {
677 0 : ShowContinueError(
678 : state,
679 0 : format("Occurs for demand side, branch: {}", state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).Name));
680 0 : } else if (LoopSideNum == DataPlant::LoopSideLocation::Supply) {
681 0 : ShowContinueError(
682 : state,
683 0 : format("Occurs for supply side, branch: {}", state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).Name));
684 : }
685 0 : ShowContinueError(state, "Branch contains only a single pump component, yet also a pressure drop component.");
686 0 : ShowContinueError(state, "Either add a second component to this branch after the pump, or move pressure drop data.");
687 0 : ShowFatalError(state, "Preceding pressure drop error causes program termination");
688 : }
689 10530 : return;
690 : }
691 :
692 : // Assign official branch pressure drop
693 58500 : if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).HasPressureComponents) {
694 12870 : BranchPressureDrop = TempBranchPressureDrop;
695 : }
696 :
697 : // Otherwise update the inlet node of the last component on the branch with this corrected pressure
698 : // This essentially sets all the pressure drop on the branch to be accounted for on the last component
699 58500 : state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).Comp(NumCompsOnBranch).NodeNumIn).Press =
700 58500 : state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).Comp(NumCompsOnBranch).NodeNumOut)
701 58500 : .Press +
702 58500 : BranchPressureDrop;
703 :
704 : // Then Smear any internal nodes with this new node pressure by working backward through
705 : // all but the last component, and passing node pressure upstream
706 58500 : if (NumCompsOnBranch > 1) {
707 0 : for (int CompNum = NumCompsOnBranch - 1; CompNum >= 1; --CompNum) {
708 :
709 : // If this component is a pump, stop passing pressure upstream, and set flag to true for calling routine
710 0 : if (DataPlant::PlantEquipmentTypeIsPump[static_cast<int>(
711 0 : state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).Comp(CompNum).Type)]) {
712 0 : PumpFound = true;
713 0 : break;
714 : }
715 :
716 : // Otherwise just pass pressure upstream and move on
717 0 : state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).Comp(CompNum).NodeNumIn).Press =
718 0 : state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).Comp(CompNum).NodeNumOut).Press;
719 : }
720 : }
721 : }
722 :
723 21060 : void PassPressureAcrossMixer(EnergyPlusData &state,
724 : int const LoopNum,
725 : const DataPlant::LoopSideLocation LoopSideNum,
726 : Real64 const MixerPressure,
727 : int const NumBranchesOnLoopSide)
728 : {
729 :
730 : // SUBROUTINE INFORMATION:
731 : // AUTHOR Edwin Lee
732 : // DATE WRITTEN August 2009
733 : // MODIFIED na
734 : // RE-ENGINEERED na
735 :
736 : // PURPOSE OF THIS SUBROUTINE:
737 : // Set mixer inlet pressures, or in other words, set mixer inlet branch outlet pressures
738 :
739 : // METHODOLOGY EMPLOYED:
740 : // Set outlet node pressures for all parallel branches on this LoopSide
741 : // Note that this is extremely simple, but is set to it's own routine to allow for clarity
742 : // when possible expansion occurs during further development
743 :
744 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
745 : int BranchNum;
746 :
747 47970 : for (BranchNum = 2; BranchNum <= NumBranchesOnLoopSide - 1; ++BranchNum) {
748 26910 : state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(BranchNum).NodeNumOut).Press = MixerPressure;
749 : }
750 21060 : }
751 :
752 21060 : void PassPressureAcrossSplitter(EnergyPlusData &state,
753 : int const LoopNum,
754 : const DataPlant::LoopSideLocation LoopSideNum,
755 : Real64 const SplitterInletPressure)
756 : {
757 :
758 : // SUBROUTINE INFORMATION:
759 : // AUTHOR Edwin Lee
760 : // DATE WRITTEN August 2009
761 : // MODIFIED na
762 : // RE-ENGINEERED na
763 :
764 : // PURPOSE OF THIS SUBROUTINE:
765 : // Set the splitter inlet pressure in anticipation of the inlet branch pressure being simulated
766 :
767 : // METHODOLOGY EMPLOYED:
768 : // Set outlet node of LoopSide inlet branch to splitter pressure
769 : // Note that this is extremely simple, but is set to it's own routine to allow for clarity
770 : // when possible expansion occurs during further development
771 :
772 : // SUBROUTINE PARAMETER DEFINITIONS:
773 21060 : int constexpr InletBranchNum(1);
774 :
775 21060 : state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).Branch(InletBranchNum).NodeNumOut).Press =
776 : SplitterInletPressure;
777 21060 : }
778 :
779 : //=================================================================================================!
780 :
781 10530 : void PassPressureAcrossInterface(EnergyPlusData &state, int const LoopNum)
782 : {
783 :
784 : // SUBROUTINE INFORMATION:
785 : // AUTHOR Edwin Lee
786 : // DATE WRITTEN August 2009
787 : // MODIFIED na
788 : // RE-ENGINEERED na
789 :
790 : // PURPOSE OF THIS SUBROUTINE:
791 : // Pass pressure backward across plant demand inlet/supply outlet interface
792 :
793 : // METHODOLOGY EMPLOYED:
794 : // Set outlet node pressure of supply side equal to inlet node pressure of demand side
795 : // Note that this is extremely simple, but is set to it's own routine to allow for clarity
796 : // when possible expansion occurs during further development
797 :
798 : // Using/Aliasing
799 :
800 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
801 : int DemandInletNodeNum;
802 : int SupplyOutletNodeNum;
803 :
804 10530 : DemandInletNodeNum = state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Demand).NodeNumIn;
805 10530 : SupplyOutletNodeNum = state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Supply).NodeNumOut;
806 :
807 10530 : state.dataLoopNodes->Node(SupplyOutletNodeNum).Press = state.dataLoopNodes->Node(DemandInletNodeNum).Press;
808 10530 : }
809 :
810 4074 : Real64 ResolveLoopFlowVsPressure(EnergyPlusData &state,
811 : int const LoopNum, // - Index of which plant/condenser loop is being simulated
812 : Real64 const SystemMassFlow, // - Initial "guess" at system mass flow rate [kg/s]
813 : int const PumpCurveNum, // - Pump curve to use when calling the curve manager for psi = f(phi)
814 : Real64 const PumpSpeed, // - Pump rotational speed, [rps] (revs per second)
815 : Real64 const PumpImpellerDia, // - Nominal pump impeller diameter [m]
816 : Real64 const MinPhi, // - Minimum allowable value of phi, requested by the pump manager from curve mgr
817 : Real64 const MaxPhi // - Maximum allowable value of phi, requested by the pump manager from curve mgr
818 : )
819 : {
820 :
821 : // FUNCTION INFORMATION:
822 : // AUTHOR Kaustubh Phalak
823 : // DATE WRITTEN Feb 2010
824 : // MODIFIED na
825 : // RE-ENGINEERED na
826 :
827 : // PURPOSE OF THIS FUNCTION:
828 : // To provide a means to simulate a constant speed pump curve and system curve to
829 : // find a more realistic operating point for the plant.
830 :
831 : // METHODOLOGY EMPLOYED:
832 : // Pressure drop of complete loop is found for a particular flow rate.
833 : // i.e. pressuredrop = K * massflow ^ 2
834 : // System curve is then solved with pump curve already entered
835 : // and flow rate provided by the pump will be calculated.
836 : // This routine does not trap for errors if a pressure simulation is not to be performed.
837 : // Calling routine should only call this if needed.
838 :
839 : // Using/Aliasing
840 : using Curve::CurveValue;
841 :
842 : // Return value
843 : Real64 ResolvedLoopMassFlowRate;
844 :
845 : // FUNCTION PARAMETER DEFINITIONS:
846 : static constexpr std::string_view RoutineName("ResolvedLoopMassFlowRate: ");
847 4074 : int constexpr MaxIters(100);
848 4074 : Real64 constexpr PressureConvergeCriteria(0.1); // Pa
849 4074 : Real64 constexpr ZeroTolerance(0.0001);
850 :
851 : // FUNCTION LOCAL VARIABLE DECLARATIONS:
852 : Real64 PumpPressureRise;
853 : Real64 NodeTemperature;
854 : Real64 NodeDensity;
855 : Real64 SystemPressureDrop;
856 : Real64 PhiPump;
857 : Real64 PhiSystem;
858 : Real64 PsiPump;
859 : int Iteration;
860 : Real64 LocalSystemMassFlow;
861 : Real64 LoopEffectiveK;
862 : bool Converged;
863 4074 : Array1D<Real64> MassFlowIterativeHistory(3);
864 : Real64 MdotDeltaLatest;
865 : Real64 MdotDeltaPrevious;
866 : Real64 DampingFactor;
867 :
868 : // Get loop level data
869 4074 : LoopEffectiveK = state.dataPlnt->PlantLoop(LoopNum).PressureEffectiveK;
870 4074 : SystemPressureDrop = LoopEffectiveK * pow_2(SystemMassFlow);
871 :
872 : // Read data off the node data structure
873 4074 : NodeTemperature = state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(LoopNum).LoopSide(DataPlant::LoopSideLocation::Supply).NodeNumIn).Temp;
874 4074 : NodeDensity = state.dataPlnt->PlantLoop(LoopNum).glycol->getDensity(state, NodeTemperature, RoutineName);
875 :
876 : // Store the passed in (requested, design) flow to the local value for performing iterations
877 4074 : LocalSystemMassFlow = SystemMassFlow;
878 :
879 : // Check and warn if invalid condition exists
880 4074 : if (LoopEffectiveK <= ZeroTolerance) {
881 0 : ++state.dataPlantPressureSys->ZeroKWarningCounter;
882 0 : if (state.dataPlantPressureSys->ZeroKWarningCounter == 1) {
883 0 : ShowWarningError(state, "Pump pressure-flow resolution attempted, but invalid loop conditions encountered.");
884 0 : ShowContinueError(state, format("Loop being calculated: {}", state.dataPlnt->PlantLoop(LoopNum).Name));
885 0 : ShowContinueError(state, "An invalid pressure/flow condition existed which resulted in the approximation of");
886 0 : ShowContinueError(state, "the pressure coefficient K to be zero. The pressure simulation will use the requested (design)");
887 0 : ShowContinueError(state, "pump flow in order to proceed with the simulation. This warning is only issued once.");
888 : }
889 0 : ResolvedLoopMassFlowRate = SystemMassFlow;
890 0 : return ResolvedLoopMassFlowRate;
891 : }
892 :
893 : // Initialize flag
894 4074 : Converged = false;
895 :
896 : // Initialize the mass flow history array and damping factor
897 4074 : MassFlowIterativeHistory = LocalSystemMassFlow;
898 4074 : DampingFactor = 0.9;
899 :
900 : // Start Convergence Loop
901 29682 : for (Iteration = 1; Iteration <= MaxIters; ++Iteration) {
902 :
903 : // Calculate System Mass Flow Rate
904 29682 : LocalSystemMassFlow = std::sqrt(SystemPressureDrop / LoopEffectiveK);
905 :
906 29682 : MassFlowIterativeHistory = eoshift(MassFlowIterativeHistory, -1, LocalSystemMassFlow);
907 :
908 29682 : PhiSystem = LocalSystemMassFlow / (NodeDensity * PumpSpeed * PumpImpellerDia);
909 :
910 : // 4th order polynomial for non-dimensional pump curve
911 29682 : PhiPump = PhiSystem;
912 :
913 : // Constrain the value to the valid region
914 29682 : PhiPump = max(PhiPump, MinPhi);
915 29682 : PhiPump = min(PhiPump, MaxPhi);
916 :
917 : // Get the pump curve value from the curve manager
918 29682 : PsiPump = CurveValue(state, PumpCurveNum, PhiPump);
919 :
920 : // Calculate Pump Pressure rise
921 29682 : PumpPressureRise = PsiPump * NodeDensity * pow_2(PumpSpeed) * pow_2(PumpImpellerDia);
922 :
923 : // Convergence Criteria Based on Pressure
924 29682 : if (std::abs(SystemPressureDrop - PumpPressureRise) < (PressureConvergeCriteria)) {
925 4074 : ResolvedLoopMassFlowRate = LocalSystemMassFlow;
926 4074 : Converged = true;
927 4074 : break;
928 : }
929 :
930 25608 : if (Iteration < 2) {
931 : // Don't do anything?
932 : } else {
933 21534 : MdotDeltaLatest = std::abs(MassFlowIterativeHistory(1) - MassFlowIterativeHistory(2));
934 21534 : MdotDeltaPrevious = std::abs(MassFlowIterativeHistory(2) - MassFlowIterativeHistory(3));
935 21534 : if (MdotDeltaLatest < MdotDeltaPrevious) {
936 : // we are converging
937 : // DampingFactor = MIN(DampingFactor * 1.1, 0.9d0)
938 : } else {
939 : // we are stuck or diverging
940 4074 : DampingFactor *= 0.9;
941 : }
942 : }
943 :
944 : // Update pressure value with damping factor
945 25608 : SystemPressureDrop = DampingFactor * PumpPressureRise + (1.0 - DampingFactor) * SystemPressureDrop;
946 : }
947 :
948 : // Check if we didn't converge
949 4074 : if (!Converged) {
950 0 : ++state.dataPlantPressureSys->MaxIterWarningCounter;
951 0 : if (state.dataPlantPressureSys->MaxIterWarningCounter == 1) {
952 0 : ShowWarningError(state, "Pump pressure-flow resolution attempted, but iteration loop did not converge.");
953 0 : ShowContinueError(state, format("Loop being calculated: {}", state.dataPlnt->PlantLoop(LoopNum).Name));
954 0 : ShowContinueError(state, "A mismatch between the pump curve entered and the pressure drop components");
955 0 : ShowContinueError(state, "on the loop may be the cause. The pressure simulation will use the requested (design)");
956 0 : ShowContinueError(state, "pump flow in order to proceed with the simulation. This warning is only issued once.");
957 : }
958 0 : ResolvedLoopMassFlowRate = SystemMassFlow;
959 0 : return ResolvedLoopMassFlowRate;
960 : }
961 :
962 4074 : return ResolvedLoopMassFlowRate;
963 4074 : }
964 :
965 : //=================================================================================================!
966 :
967 : } // namespace EnergyPlus::PlantPressureSystem
|