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 : #include <ObjexxFCL/member.functions.hh>
49 :
50 : #include <EnergyPlus/Data/EnergyPlusData.hh>
51 : #include <EnergyPlus/DataBranchAirLoopPlant.hh>
52 : #include <EnergyPlus/DataConvergParams.hh>
53 : #include <EnergyPlus/DataHVACGlobals.hh>
54 : #include <EnergyPlus/FluidProperties.hh>
55 : #include <EnergyPlus/General.hh>
56 : #include <EnergyPlus/HVACInterfaceManager.hh>
57 : #include <EnergyPlus/Plant/DataPlant.hh>
58 : #include <EnergyPlus/Plant/LoopSide.hh>
59 : #include <EnergyPlus/PlantCondLoopOperation.hh>
60 : #include <EnergyPlus/PlantPressureSystem.hh>
61 : #include <EnergyPlus/PlantUtilities.hh>
62 : #include <EnergyPlus/Pumps.hh>
63 : #include <EnergyPlus/UtilityRoutines.hh>
64 :
65 : namespace EnergyPlus {
66 : namespace DataPlant {
67 :
68 176418 : void HalfLoopData::solve(EnergyPlusData &state, bool const FirstHVACIteration, bool &ReSimOtherSideNeeded)
69 : {
70 :
71 : // SUBROUTINE INFORMATION:
72 : // AUTHORS: Dan Fisher, Sankaranarayanan K P, Edwin Lee
73 : // DATE WRITTEN: April 1998
74 : // MODIFIED June 2005(Work in the Plant Super Manager Module)
75 : // July 2006
76 : // RE-ENGINEERED July 2010
77 :
78 : // PURPOSE OF THIS SUBROUTINE:
79 : // SimSupplyFlowSolution is the driver routine for plant loops. It performs
80 : // the following tasks for each half loop (supply or demand side):
81 : // 1. Calculate flow request for half loop
82 : // 2. Predict Loop Flow
83 : // 3. Simulate the inlet branch
84 : // 4. Simulate the parallel branches, distributing load if necessary
85 : // 5. Set flow rates on parallel branches
86 : // 6. Simulate outlet branch and update node and report variables
87 :
88 : // METHODOLOGY EMPLOYED:
89 : // The algorithm operates on a predictor/corrector flow setting method by simulating all available loop components
90 : // based on component requested flow rates, then enforcing continuity on all loop branch flows by calling
91 : // the flow resolver and locking those flows down. Available components are then re-simulated using the
92 : // corrected flow rates.
93 :
94 176418 : auto &thisPlantLoop = state.dataPlnt->PlantLoop(this->plantLoc.loopNum);
95 176418 : int ThisSideInletNode = this->NodeNumIn;
96 :
97 176418 : this->InitialDemandToLoopSetPoint = 0.0;
98 176418 : this->CurrentAlterationsToDemand = 0.0;
99 176418 : this->UpdatedDemandToLoopSetPoint = 0.0;
100 :
101 : // The following block is related to validating the flow control paths of the loop side
102 : // Since the control types are scheduled, I think BeginTimeStep should be a decent check frequency
103 176418 : if (state.dataGlobal->BeginTimeStepFlag && this->OncePerTimeStepOperations) {
104 :
105 : // Initialize loop side controls -- could just be done for one loop since this routine inherently
106 : // loops over all plant/condenser loops. Not sure if the penalty is worth investigating.
107 71944 : PlantCondLoopOperation::InitLoadDistribution(state, FirstHVACIteration);
108 :
109 : // Now that the op scheme types are updated, do LoopSide validation
110 71944 : this->ValidateFlowControlPaths(state);
111 :
112 : // Set the flag to false so we won't do these again this time step
113 71944 : this->OncePerTimeStepOperations = false;
114 :
115 : } else {
116 :
117 : // Set the flag to true so that it is activated for the next time step
118 104474 : this->OncePerTimeStepOperations = true;
119 : }
120 :
121 : // Do pressure system initialize if this is the demand side (therefore once per whole loop)
122 176418 : if (this->plantLoc.loopSideNum == DataPlant::LoopSideLocation::Demand) {
123 88210 : PlantPressureSystem::SimPressureDropSystem(state, this->plantLoc.loopNum, FirstHVACIteration, DataPlant::PressureCall::Init);
124 : }
125 :
126 : // Turn on any previously disabled branches due to constant speed branch pump issue
127 176418 : this->TurnOnAllLoopSideBranches();
128 :
129 176418 : LoopSideLocation OtherLoopSide = static_cast<LoopSideLocation>(LoopSideOther[static_cast<int>(this->plantLoc.loopSideNum)]);
130 : // Do the actual simulation here every time
131 176418 : this->DoFlowAndLoadSolutionPass(state, OtherLoopSide, ThisSideInletNode, FirstHVACIteration);
132 :
133 : // On constant speed branch pump loop sides we need to re-simulate
134 176418 : if (this->hasConstSpeedBranchPumps) {
135 : // turn off any pumps connected to unloaded equipment and re-do the flow/load solution pass
136 0 : this->DisableAnyBranchPumpsConnectedToUnloadedEquipment();
137 0 : this->DoFlowAndLoadSolutionPass(state, OtherLoopSide, ThisSideInletNode, FirstHVACIteration);
138 : }
139 :
140 : // A couple things are specific to which LoopSide we are on // TODO: This whole block needs to be moved up to the loop level
141 176418 : if (this->plantLoc.loopSideNum == DataPlant::LoopSideLocation::Demand) {
142 :
143 : // Pass the loop information via the HVAC interface manager
144 264630 : HVACInterfaceManager::UpdatePlantLoopInterface(state,
145 88210 : this->plantLoc,
146 88210 : thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Demand).NodeNumOut,
147 88210 : thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Supply).NodeNumIn,
148 : ReSimOtherSideNeeded,
149 : thisPlantLoop.CommonPipeType);
150 :
151 : } else { // LoopSide == LoopSideLocation::Supply
152 :
153 : // Update pressure drop reporting, calculate total loop pressure drop for use elsewhere
154 88208 : PlantPressureSystem::SimPressureDropSystem(state, this->plantLoc.loopNum, FirstHVACIteration, DataPlant::PressureCall::Update);
155 :
156 : // Pass the loop information via the HVAC interface manager (only the flow)
157 264624 : HVACInterfaceManager::UpdatePlantLoopInterface(state,
158 88208 : this->plantLoc,
159 88208 : thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Supply).NodeNumOut,
160 88208 : thisPlantLoop.LoopSide(DataPlant::LoopSideLocation::Demand).NodeNumIn,
161 : ReSimOtherSideNeeded,
162 : thisPlantLoop.CommonPipeType);
163 :
164 : // Update the loop outlet node conditions
165 88208 : state.dataPlnt->PlantLoop(this->plantLoc.loopNum)
166 88208 : .CheckLoopExitNode(state, FirstHVACIteration); // TODO: This is a loop level check, move out
167 :
168 88208 : state.dataPlnt->PlantLoop(this->plantLoc.loopNum)
169 88208 : .UpdateLoopSideReportVars(state, this->InitialDemandToLoopSetPointSAVED, this->LoadToLoopSetPointThatWasntMet);
170 : }
171 176418 : }
172 :
173 71944 : void HalfLoopData::ValidateFlowControlPaths(EnergyPlusData &state)
174 : {
175 :
176 : // FUNCTION INFORMATION:
177 : // AUTHOR Edwin Lee
178 : // DATE WRITTEN July 2010
179 : // MODIFIED na
180 : // RE-ENGINEERED na
181 :
182 : // PURPOSE OF THIS FUNCTION:
183 : // This routine will scan all the loop side paths and validate the component topology according
184 : // to current topology rules and regulations.
185 :
186 : // METHODOLOGY EMPLOYED:
187 : // Scan this loop side and begin by scanning the first branch, then follow with the remainder of the flow paths
188 : // - this would be from splitter outlet nodes all the way to the loop side outlet node.
189 : // The current rules are that "other types" of components (as defined below in the references) can be placed along each
190 : // flow path as needed. At this point, any number of "load-range based" components can be placed along the flow
191 : // path. After this, the user is allowed to place another set of any number of "other types" of components.
192 : // The key restriction is that an "other type" of component may not be sandwiched by "load-range based" components.
193 : // This is due to the load range based needing to be simulated all at once along each flow path.
194 :
195 : // REFERENCES:
196 : // "other types" of components: basically not load-range based heat transfer components. This would include:
197 : // - demand based components such as coils
198 : // - component setpoint based operating components
199 : // - heat exchanger components including waterside economizers
200 : // "load-range based" components are heat transfer components which are controlled based on a single load range.
201 : // - currently only one load range based scheme is available at a given time, although other control types
202 : // may be enabled, such as component setpoint.
203 : // Pumps are separate components since the pump heat is not accounted for in the flow path order.
204 : // Improvements during the demand side rewrite has allowed pumps to be placed as -not- the first component on a branch
205 : // Pumps can be placed anywhere, even between load-range based components, since they will not affect loop load
206 :
207 : // RETURN VALUE:
208 : // Returns a control validator flow structure, including a flag for successful or not, then if not successful
209 : // the other values are filled in such as location on the loop where the error occurred and a message error description
210 :
211 : // FUNCTION PARAMETER DEFINITIONS:
212 71944 : int constexpr Parallel(1);
213 71944 : int constexpr Outlet(2);
214 :
215 : //~ Initialze
216 71944 : bool EncounteredLRB = false;
217 71944 : bool EncounteredNonLRBAfterLRB = false;
218 71944 : int const NumParallelPaths = this->TotalBranches - 2;
219 :
220 : // We'll start by stepping through the first branch, which may be the only branch
221 : // If we find a load range based, then trip the flag and just keep moving
222 : // If we only have one branch and all is good, then RETURN early
223 : // If we have parallel branches, then start looping through each flow path to
224 : // decide if it is a valid path.
225 : // If any one path is invalid then all is wrong
226 71944 : int firstBranchIndex = 1;
227 143888 : for (int CompIndex = 1; CompIndex <= this->Branch(firstBranchIndex).TotalComponents; ++CompIndex) {
228 :
229 71944 : auto const &this_component(this->Branch(firstBranchIndex).Comp(CompIndex));
230 :
231 : {
232 71944 : switch (this_component.CurOpSchemeType) {
233 0 : case OpScheme::HeatingRB:
234 : case OpScheme::CoolingRB: { //~ load range based
235 0 : if (EncounteredNonLRBAfterLRB) {
236 : // We must have already encountered a LRB, then a non-LRB, and now another LRB, this is bad
237 0 : ShowSevereError(state, "Plant topology problem on \"" + this->loopSideDescription + "\"");
238 0 : ShowContinueError(state, "PlaLoad range based components are separated by other control type components.");
239 0 : ShowContinueError(state, "Load Range Based should be grouped together on each flow path.");
240 0 : ShowFatalError(state, "Plant topology issue causes program termination");
241 : } else {
242 0 : EncounteredLRB = true;
243 : }
244 0 : break;
245 : }
246 35972 : case DataPlant::OpScheme::Pump: { //~ pump
247 : // For now this is just a placeholder, because I think pumps will be available anywhere,
248 : // and they won't affect the load distribution
249 35972 : break;
250 : }
251 35972 : case DataPlant::OpScheme::NoControl: { //~ Such as pipes
252 : // For now this is just a placeholder, because these components shouldn't cause a problem anywhere...
253 35972 : break;
254 : }
255 0 : case DataPlant::OpScheme::Invalid: { //~ Uninitialized, this should be a sufficient place to catch for this on branch 1
256 : // throw fatal
257 0 : ShowSevereError(state,
258 0 : "ValidateFlowControlPaths: Uninitialized operation scheme type for component Name: " + this_component.Name);
259 0 : ShowFatalError(state, "ValidateFlowControlPaths: developer notice, Inlet path validation loop");
260 0 : break;
261 : }
262 0 : default: { //~ Other control type
263 0 : if (EncounteredLRB) {
264 0 : EncounteredNonLRBAfterLRB = true;
265 : } else {
266 : // For now don't do anything, but we'll see...
267 : }
268 : }
269 : }
270 : }
271 : }
272 :
273 : // Return early if we only needed to do the one branch
274 71944 : if (NumParallelPaths <= 0) return;
275 :
276 : // Now, if we have multiple parallel branches, I think the easiest way is to go all the way from the inlet node
277 : // of each parallel branch to the loop outlet node and check the flow path
278 : // This way we don't have to remember the conditions on each of the parallel branches when we would finally move
279 : // to analyzing the outlet node when all done
280 : // This will reduce allocation on the heap because we will keep from storing that array
281 : // For each parallel path, we will need to check two branches: the parallel branch and the LoopSide outlet branch
282 248878 : for (int PathCounter = 1; PathCounter <= NumParallelPaths; ++PathCounter) {
283 530802 : for (int ParallelOrOutletIndex = Parallel; ParallelOrOutletIndex <= Outlet; ++ParallelOrOutletIndex) {
284 : int BranchIndex;
285 353868 : if (ParallelOrOutletIndex == Parallel) {
286 : // The branch index will be the current pathtype + 1 to add the inlet branch
287 176934 : BranchIndex = PathCounter + 1;
288 : } else { // ParallelOrOutletIndex == Outlet
289 : // The branch index will be the LoopSide outlet node
290 176934 : BranchIndex = this->TotalBranches;
291 : }
292 :
293 : // Now that we have the branch index, let's do the control type check over all the components
294 707736 : for (int CompIndex = 1; CompIndex <= this->Branch(BranchIndex).TotalComponents; ++CompIndex) {
295 :
296 353868 : auto const &this_component(this->Branch(BranchIndex).Comp(CompIndex));
297 :
298 : {
299 353868 : switch (this_component.CurOpSchemeType) {
300 27496 : case OpScheme::HeatingRB:
301 : case OpScheme::CoolingRB: { //~ load range based
302 27496 : if (EncounteredNonLRBAfterLRB) {
303 : // We must have already encountered a LRB, then a non-LRB, and now another LRB, this is bad
304 0 : ShowSevereError(state, "Plant topology problem on \"" + this->loopSideDescription + "\"");
305 0 : ShowContinueError(state, "Load range based components are separated by other control type components.");
306 0 : ShowContinueError(state, "Load Range Based should be grouped together on each flow path.");
307 0 : ShowFatalError(state, "Plant topology issue causes program termination");
308 : } else {
309 27496 : EncounteredLRB = true;
310 : }
311 27496 : break;
312 : }
313 :
314 235948 : case DataPlant::OpScheme::NoControl: { //~ Such as pipes
315 : // For now this is just a placeholder, because these components shouldn't cause a problem anywhere...
316 235948 : break;
317 : }
318 0 : case DataPlant::OpScheme::Pump: { //~ pump
319 : // For now this is just a placeholder, because I think pumps will be available anywhere,
320 : // and they won't affect the load distribution
321 0 : break;
322 : }
323 0 : case DataPlant::OpScheme::Invalid: { //~ Uninitialized, this should be sufficient place to
324 : // catch for this on other branches
325 : // throw fatal error
326 0 : ShowSevereError(
327 0 : state, "ValidateFlowControlPaths: Uninitialized operation scheme type for component Name: " + this_component.Name);
328 0 : ShowFatalError(state, "ValidateFlowControlPaths: developer notice, problem in Parallel path validation loop");
329 0 : break;
330 : }
331 90424 : default: { //~ Other control type
332 90424 : if (EncounteredLRB) {
333 0 : EncounteredNonLRBAfterLRB = true;
334 : } else {
335 : // For now don't do anything, but we'll see...
336 : }
337 : }
338 : }
339 : }
340 :
341 : } //~ CompIndex
342 :
343 : } //~ Parallel and Outlet Branches
344 :
345 : } //~ Parallel Paths
346 : }
347 :
348 348720 : bool HalfLoopData::CheckPlantConvergence(bool const FirstHVACIteration)
349 : {
350 :
351 : // FUNCTION INFORMATION:
352 : // AUTHOR Edwin Lee
353 : // DATE WRITTEN Summer 2011
354 : // MODIFIED na
355 : // RE-ENGINEERED na
356 :
357 : // PURPOSE OF THIS FUNCTION:
358 : // This routine checks the history values in the convergence arrays of this loop/LoopSide combination
359 :
360 : // METHODOLOGY EMPLOYED:
361 : // On FirstHVAC, we are not converged yet, thus forcing at least two iterations
362 : // Calculate the average of each related variable history (generalized: could be any number of history terms)
363 : // If any of the history terms do not match this average, then at least one value is different, so not converged
364 : // Although this routine appears to check for convergence, it is also used to check for stuck (max iteration) conditions
365 : // in cases where demand side (air loop, for example) equipment is "fighting" with the plant loop
366 : // The result of this routine can help the plant "lock-in" and take action to stop the iteration
367 :
368 : // Using/Aliasing
369 : using namespace DataPlant;
370 : using namespace DataLoopNode;
371 :
372 : // FUNCTION LOCAL VARIABLE DECLARATIONS:
373 : Real64 InletAvgTemp;
374 : Real64 InletAvgMdot;
375 : Real64 OutletAvgTemp;
376 : Real64 OutletAvgMdot;
377 :
378 348720 : if (FirstHVACIteration) {
379 171321 : return false;
380 : }
381 :
382 177399 : InletAvgTemp = sum(this->InletNode.TemperatureHistory) / size(this->InletNode.TemperatureHistory);
383 177399 : if (any_ne(this->InletNode.TemperatureHistory, InletAvgTemp)) {
384 177391 : return false;
385 : }
386 :
387 8 : InletAvgMdot = sum(this->InletNode.MassFlowRateHistory) / size(this->InletNode.MassFlowRateHistory);
388 8 : if (any_ne(this->InletNode.MassFlowRateHistory, InletAvgMdot)) {
389 1 : return false;
390 : }
391 :
392 7 : OutletAvgTemp = sum(this->OutletNode.TemperatureHistory) / size(this->OutletNode.TemperatureHistory);
393 7 : if (any_ne(this->OutletNode.TemperatureHistory, OutletAvgTemp)) {
394 1 : return false;
395 : }
396 :
397 6 : OutletAvgMdot = sum(this->OutletNode.MassFlowRateHistory) / size(this->OutletNode.MassFlowRateHistory);
398 6 : if (any_ne(this->OutletNode.MassFlowRateHistory, OutletAvgMdot)) {
399 1 : return false;
400 : }
401 :
402 : // If we made it this far, we're good!
403 5 : return true;
404 : }
405 :
406 348710 : void HalfLoopData::PushBranchFlowCharacteristics(EnergyPlusData &state,
407 : int const BranchNum,
408 : Real64 const ValueToPush,
409 : bool const FirstHVACIteration // TRUE if First HVAC iteration of Time step
410 : )
411 : {
412 :
413 : // SUBROUTINE INFORMATION:
414 : // AUTHOR Edwin Lee
415 : // DATE WRITTEN September 2010
416 : // MODIFIED na
417 : // RE-ENGINEERED na
418 :
419 : // PURPOSE OF THIS SUBROUTINE:
420 : // This routine takes the flow resolved flow rate and pushes it
421 : // down a branch. In the process, if an externally connected
422 : // component (air-water coil for example) is found to have a
423 : // differing flow rate, the air sim flag is tripped to true, but
424 : // the flow resolved flow rate is pushed down the loop to allow
425 : // the plant to finish successfully.
426 :
427 : // METHODOLOGY EMPLOYED:
428 : // Push mass flow rate and max avail down each branch. If the component
429 : // is connected (or could be, for now) to an external loop such as
430 : // an air loop, the current component outlet mass flow is checked
431 : // vs the current resolved mass flow. If the mass flow doesn't match,
432 : // the air sim flag is tripped to true.
433 :
434 : // Currently this routine is only performed for starved branches, when
435 : // the coil is requesting too much flow, more than the plant can provide.
436 : // If this were moved to every call type, including a minimum plant flow,
437 : // you would need to provide a mass flow and min/max avail to push
438 : // down the branch as well.
439 :
440 : // Using/Aliasing
441 : using namespace DataPlant; // Use the entire module to allow all TypeOf's, would be a huge ONLY list
442 :
443 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
444 : int CompCounter;
445 : int BranchInletNode;
446 : DataPlant::PlantEquipmentType ComponentType;
447 : Real64 MassFlowRateFound;
448 : Real64 MassFlow;
449 : bool PlantIsRigid;
450 :
451 348710 : auto &this_branch(this->Branch(BranchNum));
452 :
453 348710 : BranchInletNode = this_branch.NodeNumIn;
454 :
455 : //~ Possible error handling if needed
456 348710 : if (ValueToPush != state.dataLoopNodes->Node(BranchInletNode).MassFlowRate) {
457 : // Diagnostic problem, flow resolver isn't calling this routine properly
458 : }
459 :
460 : //~ This section would really be useful more later on if this routine has more logic regarding what to push down the branch
461 348710 : MassFlow = ValueToPush;
462 : // MinAvail = ValueToPush
463 : // MaxAvail = ValueToPush
464 :
465 348710 : PlantIsRigid = this->CheckPlantConvergence(FirstHVACIteration);
466 :
467 : //~ Loop across all component outlet nodes and update their mass flow and max avail
468 697420 : for (CompCounter = 1; CompCounter <= this_branch.TotalComponents; ++CompCounter) {
469 :
470 348710 : auto const &this_comp(this_branch.Comp(CompCounter));
471 :
472 : //~ Pick up some values for convenience
473 348710 : int ComponentInletNode = this_comp.NodeNumIn;
474 348710 : int ComponentOutletNode = this_comp.NodeNumOut;
475 348710 : MassFlowRateFound = state.dataLoopNodes->Node(ComponentOutletNode).MassFlowRate;
476 348710 : ComponentType = this_comp.Type;
477 :
478 : //~ Push the values through
479 348710 : state.dataLoopNodes->Node(ComponentOutletNode).MassFlowRate = MassFlow;
480 :
481 348710 : if (PlantIsRigid) {
482 0 : state.dataLoopNodes->Node(ComponentInletNode).MassFlowRateMinAvail = MassFlow;
483 0 : state.dataLoopNodes->Node(ComponentInletNode).MassFlowRateMaxAvail = MassFlow;
484 0 : state.dataLoopNodes->Node(ComponentOutletNode).MassFlowRateMinAvail = MassFlow;
485 0 : state.dataLoopNodes->Node(ComponentOutletNode).MassFlowRateMaxAvail = MassFlow;
486 : }
487 : // Node(ComponentOutletNode)%MassFlowRateMinAvail = MinAvail
488 : // no this is 2-way valve which messes up flow options
489 : // for demand components Node(ComponentOutletNode)%MassFlowRateMaxAvail = MaxAvail
490 :
491 : //~ If this value matches then we are good to move to the next component
492 348710 : if (std::abs(MassFlow - MassFlowRateFound) < CriteriaDelta_MassFlowRate) continue;
493 : //~ Since there is a difference, we have to decide what to do based on the component type:
494 : //~ For plant connections, don't do anything, it SHOULD work itself out
495 : //~ For air connections, trip the LoopSide air flag
496 : //~ Similar for zone, none zone, and electric load center
497 : {
498 51095 : switch (ComponentType) {
499 :
500 : // possibly air-connected components
501 63 : case DataPlant::PlantEquipmentType::CoilWaterCooling:
502 : case DataPlant::PlantEquipmentType::CoilWaterDetailedFlatCooling:
503 : case DataPlant::PlantEquipmentType::CoilWaterSimpleHeating:
504 : case DataPlant::PlantEquipmentType::CoilSteamAirHeating:
505 : case DataPlant::PlantEquipmentType::CoilWAHPHeatingEquationFit:
506 : case DataPlant::PlantEquipmentType::CoilWAHPCoolingEquationFit:
507 : case DataPlant::PlantEquipmentType::CoilWAHPHeatingParamEst:
508 : case DataPlant::PlantEquipmentType::CoilWAHPCoolingParamEst:
509 : case DataPlant::PlantEquipmentType::CoilUserDefined:
510 : case DataPlant::PlantEquipmentType::CoilVSWAHPCoolingEquationFit:
511 : case DataPlant::PlantEquipmentType::CoilVSWAHPHeatingEquationFit:
512 : case DataPlant::PlantEquipmentType::PackagedTESCoolingCoil: {
513 63 : this->SimAirLoopsNeeded = true;
514 : // sometimes these coils are children in ZoneHVAC equipment
515 : // PlantLoop(LoopNum)%LoopSide(LoopSideNum)%SimZoneEquipNeeded= .TRUE.
516 63 : break;
517 : }
518 :
519 : // zone connected components
520 0 : case DataPlant::PlantEquipmentType::CoolingPanel_Simple:
521 : case DataPlant::PlantEquipmentType::Baseboard_Conv_Water:
522 : case DataPlant::PlantEquipmentType::Baseboard_Rad_Conv_Steam:
523 : case DataPlant::PlantEquipmentType::Baseboard_Rad_Conv_Water:
524 : case DataPlant::PlantEquipmentType::LowTempRadiant_VarFlow:
525 : case DataPlant::PlantEquipmentType::LowTempRadiant_ConstFlow:
526 : case DataPlant::PlantEquipmentType::CooledBeamAirTerminal:
527 : case DataPlant::PlantEquipmentType::ZoneHVACAirUserDefined:
528 : case DataPlant::PlantEquipmentType::AirTerminalUserDefined:
529 : case DataPlant::PlantEquipmentType::FourPipeBeamAirTerminal: {
530 0 : this->SimZoneEquipNeeded = true;
531 0 : break;
532 : }
533 :
534 : // electric center connected components
535 0 : case DataPlant::PlantEquipmentType::Generator_FCExhaust:
536 : case DataPlant::PlantEquipmentType::Generator_FCStackCooler:
537 : case DataPlant::PlantEquipmentType::Generator_MicroCHP:
538 : case DataPlant::PlantEquipmentType::Generator_MicroTurbine:
539 : case DataPlant::PlantEquipmentType::Generator_ICEngine:
540 : case DataPlant::PlantEquipmentType::Generator_CTurbine: {
541 0 : this->SimElectLoadCentrNeeded = true;
542 0 : break;
543 : }
544 :
545 51032 : default:
546 51032 : break;
547 : }
548 : }
549 : }
550 348710 : }
551 :
552 176418 : void HalfLoopData::TurnOnAllLoopSideBranches()
553 : {
554 626986 : for (int branchNum = 2; branchNum <= this->TotalBranches - 1; ++branchNum) {
555 450568 : auto &branch = this->Branch(branchNum);
556 450568 : branch.disableOverrideForCSBranchPumping = false;
557 : }
558 176418 : }
559 :
560 352836 : void HalfLoopData::SimulateAllLoopSideBranches(EnergyPlusData &state,
561 : Real64 const ThisLoopSideFlow,
562 : bool const FirstHVACIteration,
563 : bool &LoopShutDownFlag)
564 : {
565 :
566 : // SUBROUTINE INFORMATION:
567 : // AUTHOR Edwin Lee
568 : // DATE WRITTEN July 2010
569 : // MODIFIED na
570 : // RE-ENGINEERED na
571 :
572 : // PURPOSE OF THIS SUBROUTINE:
573 : // This routine will step through all branch groups (single branch .OR. inlet/parallels/outlet)
574 : // and call the branch group simulation routine. This routine also calls to update the splitter
575 : // and mixer.
576 :
577 : // METHODOLOGY EMPLOYED:
578 : // The number of branch groups is either 1 or 3. 1 would be a single branch half-loop. 3 would
579 : // be the minimum for an inlet/parallels/outlet set. The number of branch groups can then be
580 : // calculated as #BrGrps = 1 + 2*L; where L is zero for single half loop and one for parallel-type set.
581 : // This calculation can be reduced to the logical/integer conversion as shown in the code.
582 : // The simulation then steps through each branch group. If there are parallel branches, the splitter is
583 : // updated on flowlock=0 to pass information through, then after the parallel branches the mixer is always
584 : // updated. The outlet branch "group" is then simulated.
585 :
586 : // SUBROUTINE PARAMETER DEFINITIONS:
587 352836 : int constexpr InletBranchOrOneBranchHalfLoop(1);
588 352836 : int constexpr ParallelBranchSet(2);
589 352836 : int constexpr OutletBranch(3);
590 :
591 352836 : int NumBranchGroups = 1;
592 352836 : if (this->TotalBranches > 1) {
593 352836 : NumBranchGroups = 3;
594 : }
595 :
596 : // reset branch starting component index back to zero before each pass
597 1959644 : for (int BranchCounter = 1; BranchCounter <= this->TotalBranches; ++BranchCounter) {
598 1606808 : this->Branch(BranchCounter).lastComponentSimulated = 0;
599 : }
600 :
601 1411344 : for (int BranchGroup = 1; BranchGroup <= NumBranchGroups; ++BranchGroup) {
602 :
603 1058508 : if ((BranchGroup > 1) && (this->TotalBranches == 1)) break;
604 :
605 1058508 : switch (BranchGroup) {
606 352836 : case InletBranchOrOneBranchHalfLoop:
607 352836 : this->SimulateLoopSideBranchGroup(state, 1, 1, ThisLoopSideFlow, FirstHVACIteration, LoopShutDownFlag);
608 352836 : break;
609 352836 : case ParallelBranchSet:
610 352836 : this->UpdatePlantSplitter(state);
611 352836 : this->SimulateLoopSideBranchGroup(state, 2, this->TotalBranches - 1, ThisLoopSideFlow, FirstHVACIteration, LoopShutDownFlag);
612 352836 : this->UpdatePlantMixer(state);
613 352836 : break;
614 352836 : case OutletBranch:
615 352836 : this->SimulateLoopSideBranchGroup(
616 : state, this->TotalBranches, this->TotalBranches, ThisLoopSideFlow, FirstHVACIteration, LoopShutDownFlag);
617 352836 : break;
618 : }
619 : }
620 352836 : }
621 :
622 264659 : void HalfLoopData::AdjustPumpFlowRequestByEMSControls(int const BranchNum, int const CompNum, Real64 &FlowToRequest)
623 : {
624 :
625 : // SUBROUTINE INFORMATION:
626 : // AUTHOR Brent Griffith
627 : // DATE WRITTEN April 2012
628 : // MODIFIED na
629 : // RE-ENGINEERED na
630 :
631 : // PURPOSE OF THIS SUBROUTINE:
632 : // modify flow request to pump simulation if EMS is overriding pump component
633 :
634 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
635 264659 : auto &this_branch(this->Branch(BranchNum));
636 264659 : auto &this_comp(this_branch.Comp(CompNum));
637 :
638 264659 : if ((this->EMSCtrl) && (this->EMSValue <= 0.0)) {
639 0 : FlowToRequest = 0.0;
640 0 : return;
641 : }
642 :
643 264659 : if ((this_branch.EMSCtrlOverrideOn) && (this_branch.EMSCtrlOverrideValue <= 0.0)) {
644 0 : FlowToRequest = 0.0;
645 0 : return;
646 : }
647 :
648 264659 : if (this_comp.EMSLoadOverrideOn) {
649 0 : if (this_comp.EMSLoadOverrideValue == 0.0) {
650 0 : FlowToRequest = 0.0;
651 : }
652 : }
653 : }
654 :
655 0 : void HalfLoopData::DisableAnyBranchPumpsConnectedToUnloadedEquipment()
656 : {
657 0 : for (int branchNum = 2; branchNum <= this->TotalBranches - 1; ++branchNum) {
658 0 : auto &branch = this->Branch(branchNum);
659 0 : Real64 totalDispatchedLoadOnBranch = 0.0;
660 0 : for (int compNum = 1; compNum <= branch.TotalComponents; ++compNum) {
661 0 : auto const &component = branch.Comp(compNum);
662 0 : auto const &t = component.Type;
663 0 : if (t == DataPlant::PlantEquipmentType::PumpConstantSpeed || t == DataPlant::PlantEquipmentType::PumpBankConstantSpeed ||
664 0 : t == DataPlant::PlantEquipmentType::PumpVariableSpeed || t == DataPlant::PlantEquipmentType::PumpBankVariableSpeed) {
665 : // don't do anything
666 : } else {
667 0 : totalDispatchedLoadOnBranch += component.MyLoad;
668 : }
669 : }
670 0 : if (std::abs(totalDispatchedLoadOnBranch) < 0.001) {
671 0 : branch.disableOverrideForCSBranchPumping = true;
672 : }
673 : }
674 0 : }
675 :
676 176418 : Real64 HalfLoopData::EvaluateLoopSetPointLoad(EnergyPlusData &state, int const FirstBranchNum, int const LastBranchNum, Real64 ThisLoopSideFlow)
677 : {
678 :
679 : // FUNCTION INFORMATION:
680 : // AUTHOR Edwin Lee
681 : // DATE WRITTEN August 2010
682 : // MODIFIED na
683 : // RE-ENGINEERED na
684 :
685 : // Return value
686 176418 : Real64 LoadToLoopSetPoint = 0.0; // function result
687 :
688 : static constexpr std::string_view RoutineName("PlantLoopSolver::EvaluateLoopSetPointLoad");
689 : static constexpr std::string_view RoutineNameAlt("PlantSupplySide:EvaluateLoopSetPointLoad");
690 :
691 : //~ General variables
692 176418 : Real64 SumMdotTimesTemp = 0.0;
693 176418 : Real64 SumMdot = 0.0;
694 :
695 176418 : auto &thisPlantLoop = state.dataPlnt->PlantLoop(this->plantLoc.loopNum);
696 :
697 : // We will place one specialized case in here for common pipe simulations.
698 : // If we are doing a common pipe simulation, and there is greater other-side flow than this side,
699 : // then the "other side" demand needs to include getting the flow through the common pipe to the same setpoint
700 : // as the flow going through the actual supply side
701 176418 : if (this->hasConstSpeedBranchPumps && this->plantLoc.loopSideNum == DataPlant::LoopSideLocation::Supply &&
702 0 : thisPlantLoop.CommonPipeType != DataPlant::CommonPipeType::No) {
703 0 : const DataPlant::LoopSideLocation OtherSide = LoopSideOther[static_cast<int>(this->plantLoc.loopSideNum)];
704 0 : const int otherSideOutletNodeNum = thisPlantLoop.LoopSide(OtherSide).NodeNumOut;
705 0 : Real64 commonPipeFlow = state.dataLoopNodes->Node(otherSideOutletNodeNum).MassFlowRate - ThisLoopSideFlow;
706 0 : Real64 otherSideExitingTemperature = state.dataLoopNodes->Node(otherSideOutletNodeNum).Temp;
707 0 : SumMdotTimesTemp += otherSideExitingTemperature * commonPipeFlow;
708 0 : SumMdot += commonPipeFlow;
709 : }
710 :
711 : // Sweep across flow paths in this group and calculate the deltaT and then the load
712 176418 : int BranchIndex = 0; // ~ This is a 1 - n value within the current branch group
713 352836 : for (int BranchCounter = FirstBranchNum; BranchCounter <= LastBranchNum; ++BranchCounter) {
714 :
715 176418 : ++BranchIndex;
716 :
717 : //~ Always start from the last component we did the last time around + 1 and
718 : //~ try to make it all the way to the end of the loop
719 176418 : int StartingComponent = this->Branch(BranchCounter).lastComponentSimulated + 1;
720 176418 : int EnteringNodeNum = this->Branch(BranchCounter).Comp(StartingComponent).NodeNumIn;
721 :
722 176418 : Real64 EnteringTemperature = state.dataLoopNodes->Node(EnteringNodeNum).Temp;
723 176418 : Real64 MassFlowRate = state.dataLoopNodes->Node(EnteringNodeNum).MassFlowRate;
724 :
725 176418 : SumMdotTimesTemp += EnteringTemperature * MassFlowRate;
726 176418 : SumMdot += MassFlowRate;
727 : }
728 :
729 176418 : if (SumMdot < DataBranchAirLoopPlant::MassFlowTolerance) {
730 82568 : return 0.0;
731 : }
732 :
733 93850 : Real64 WeightedInletTemp = SumMdotTimesTemp / SumMdot;
734 :
735 93850 : if (thisPlantLoop.FluidType == DataLoopNode::NodeFluidType::Water) {
736 :
737 93850 : Real64 Cp = thisPlantLoop.glycol->getSpecificHeat(state, WeightedInletTemp, RoutineName);
738 :
739 : {
740 :
741 93850 : if (thisPlantLoop.LoopDemandCalcScheme == DataPlant::LoopDemandCalcScheme::SingleSetPoint) {
742 :
743 : // Pick up the loop setpoint temperature
744 88088 : Real64 LoopSetPointTemperature = this->TempSetPoint;
745 : // Calculate the delta temperature
746 88088 : Real64 DeltaTemp = LoopSetPointTemperature - WeightedInletTemp;
747 :
748 : // Calculate the demand on the loop
749 88088 : LoadToLoopSetPoint = SumMdot * Cp * DeltaTemp;
750 :
751 5762 : } else if (thisPlantLoop.LoopDemandCalcScheme == DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand) {
752 :
753 : // Get the range of setpoints
754 5762 : Real64 LoopSetPointTemperatureHi = state.dataLoopNodes->Node(thisPlantLoop.TempSetPointNodeNum).TempSetPointHi;
755 5762 : Real64 LoopSetPointTemperatureLo = state.dataLoopNodes->Node(thisPlantLoop.TempSetPointNodeNum).TempSetPointLo;
756 :
757 : // Calculate the demand on the loop
758 5762 : if (SumMdot > 0.0) {
759 5762 : Real64 LoadToHeatingSetPoint = SumMdot * Cp * (LoopSetPointTemperatureLo - WeightedInletTemp);
760 5762 : Real64 LoadToCoolingSetPoint = SumMdot * Cp * (LoopSetPointTemperatureHi - WeightedInletTemp);
761 : // Possible combinations:
762 : // 1 LoadToHeatingSetPoint > 0 & LoadToCoolingSetPoint > 0 --> Heating required
763 : // 2 LoadToHeatingSetPoint < 0 & LoadToCoolingSetPoint < 0 --> Cooling Required
764 : // 3 LoadToHeatingSetPoint <=0 & LoadToCoolingSetPoint >=0 --> Dead Band Operation - includes zero load cases
765 : // 4 LoadToHeatingSetPoint > LoadToCoolingSetPoint --> Not Feasible if LoopSetPointHi >= LoopSetPointLo
766 : // First trap bad set-points
767 5762 : if (LoadToHeatingSetPoint > LoadToCoolingSetPoint) {
768 0 : ShowSevereError(state,
769 : "Plant Loop: the Plant Loop Demand Calculation Scheme is set to DualSetPointDeadBand, but the "
770 : "heating-related low setpoint appears to be above the cooling-related high setpoint.");
771 0 : ShowContinueError(state,
772 : "For example, if using SetpointManager:Scheduled:DualSetpoint, then check that the low setpoint is "
773 : "below the high setpoint.");
774 0 : ShowContinueError(state, "Occurs in PlantLoop=" + thisPlantLoop.Name);
775 0 : ShowContinueError(
776 : state,
777 0 : format("LoadToHeatingSetPoint={:.3R}, LoadToCoolingSetPoint={:.3R}", LoadToHeatingSetPoint, LoadToCoolingSetPoint));
778 0 : ShowContinueError(state, format("Loop Heating Low Setpoint={:.2R}", LoopSetPointTemperatureLo));
779 0 : ShowContinueError(state, format("Loop Cooling High Setpoint={:.2R}", LoopSetPointTemperatureHi));
780 :
781 0 : ShowFatalError(state, "Program terminates due to above conditions.");
782 : }
783 5762 : if (LoadToHeatingSetPoint > 0.0 && LoadToCoolingSetPoint > 0.0) {
784 2872 : LoadToLoopSetPoint = LoadToHeatingSetPoint;
785 2890 : } else if (LoadToHeatingSetPoint < 0.0 && LoadToCoolingSetPoint < 0.0) {
786 2872 : LoadToLoopSetPoint = LoadToCoolingSetPoint;
787 18 : } else if (LoadToHeatingSetPoint <= 0.0 && LoadToCoolingSetPoint >= 0.0) { // deadband includes zero loads
788 18 : LoadToLoopSetPoint = 0.0;
789 : } else {
790 0 : ShowSevereError(state,
791 : "DualSetPointWithDeadBand: Unanticipated combination of heating and cooling loads - report to EnergyPlus "
792 : "Development Team");
793 0 : ShowContinueError(state, "occurs in PlantLoop=" + thisPlantLoop.Name);
794 0 : ShowContinueError(
795 : state,
796 0 : format("LoadToHeatingSetPoint={:.3R}, LoadToCoolingSetPoint={:.3R}", LoadToHeatingSetPoint, LoadToCoolingSetPoint));
797 0 : ShowContinueError(state, format("Loop Heating Setpoint={:.2R}", LoopSetPointTemperatureLo));
798 0 : ShowContinueError(state, format("Loop Cooling Setpoint={:.2R}", LoopSetPointTemperatureHi));
799 0 : ShowFatalError(state, "Program terminates due to above conditions.");
800 : }
801 : } else {
802 0 : LoadToLoopSetPoint = 0.0;
803 : }
804 : }
805 : }
806 :
807 0 : } else if (thisPlantLoop.FluidType == DataLoopNode::NodeFluidType::Steam) {
808 :
809 0 : Real64 Cp = thisPlantLoop.glycol->getSpecificHeat(state, WeightedInletTemp, RoutineName);
810 :
811 : {
812 :
813 0 : if (thisPlantLoop.LoopDemandCalcScheme == DataPlant::LoopDemandCalcScheme::SingleSetPoint) {
814 :
815 : // Pick up the loop setpoint temperature
816 0 : Real64 LoopSetPointTemperature = this->TempSetPoint;
817 :
818 : // Calculate the delta temperature
819 0 : Real64 DeltaTemp = LoopSetPointTemperature - WeightedInletTemp;
820 :
821 0 : auto *steam = Fluid::GetSteam(state);
822 0 : Real64 EnthalpySteamSatVapor = steam->getSatEnthalpy(state, LoopSetPointTemperature, 1.0, RoutineNameAlt);
823 0 : Real64 EnthalpySteamSatLiquid = steam->getSatEnthalpy(state, LoopSetPointTemperature, 0.0, RoutineNameAlt);
824 :
825 0 : Real64 LatentHeatSteam = EnthalpySteamSatVapor - EnthalpySteamSatLiquid;
826 :
827 : // Calculate the demand on the loop
828 0 : LoadToLoopSetPoint = SumMdot * (Cp * DeltaTemp + LatentHeatSteam);
829 : }
830 : }
831 :
832 : } else { // only have two types, water serves for glycol.
833 : }
834 :
835 : // Trim the demand to zero if it is very small
836 93850 : if (std::abs(LoadToLoopSetPoint) < DataPlant::LoopDemandTol) LoadToLoopSetPoint = 0.0;
837 :
838 93850 : return LoadToLoopSetPoint;
839 : }
840 :
841 176418 : Real64 HalfLoopData::CalcOtherSideDemand(EnergyPlusData &state, Real64 ThisLoopSideFlow)
842 : {
843 :
844 : // FUNCTION INFORMATION:
845 : // AUTHOR Edwin Lee
846 : // DATE WRITTEN August 2010
847 : // MODIFIED na
848 : // RE-ENGINEERED na
849 :
850 : // PURPOSE OF THIS FUNCTION:
851 : // To evaluate the demand to hit the loop setpoint based on the loop side inlet conditions
852 :
853 : // METHODOLOGY EMPLOYED:
854 : // This routine will simply call the evaluate loop setpoint routine but call it from
855 : // the very beginning of this loop side, so that it is basically for the entire loop side
856 :
857 : // FUNCTION PARAMETER DEFINITIONS:
858 176418 : return this->EvaluateLoopSetPointLoad(state, 1, 1, ThisLoopSideFlow);
859 : }
860 :
861 176418 : Real64 HalfLoopData::SetupLoopFlowRequest(EnergyPlusData &state, const LoopSideLocation OtherSide)
862 : {
863 :
864 : // FUNCTION INFORMATION:
865 : // AUTHOR: Dan Fisher, Edwin Lee
866 : // DATE WRITTEN: August 2010
867 : // MODIFIED: na
868 : // RE-ENGINEERED: na
869 :
870 : // PURPOSE OF THIS SUBROUTINE:
871 : // This routine sets up the flow request values and sums them up for each loop side
872 : // Then makes a decision on the desired loop flow based on loop configuration
873 :
874 : // METHODOLOGY EMPLOYED:
875 : // Scan through the components on this loop side, and look at the mass flow request
876 : // values on components inlet node.
877 : // Check common pipe/pumping configuration for this loop side and the other loop side
878 : // to determine what the LoopSide should flow
879 :
880 : //~ Initialize
881 176418 : Real64 LoopFlow = 0.0; // Once all flow requests are evaluated, this is the desired flow on this side
882 :
883 : // reference
884 176418 : auto &loop(state.dataPlnt->PlantLoop(this->plantLoc.loopNum));
885 :
886 : //~ First we need to set up the flow requests on each LoopSide
887 529254 : for (DataPlant::LoopSideLocation LoopSideCounter : DataPlant::LoopSideKeys) {
888 : // Clear things out for this LoopSide
889 352836 : Real64 InletBranchRequestNeedAndTurnOn = 0.0;
890 352836 : Real64 InletBranchRequestNeedIfOn = 0.0;
891 352836 : Real64 ParallelBranchRequestsNeedAndTurnOn(0.0);
892 352836 : Real64 ParallelBranchRequestsNeedIfOn(0.0);
893 352836 : Real64 OutletBranchRequestNeedAndTurnOn = 0.0;
894 352836 : Real64 OutletBranchRequestNeedIfOn = 0.0;
895 :
896 : // reference
897 352836 : auto &loop_side(loop.LoopSide(LoopSideCounter));
898 :
899 352836 : loop_side.flowRequestNeedIfOn = 0.0;
900 352836 : loop_side.flowRequestNeedAndTurnOn = 0.0;
901 352836 : loop_side.flowRequestFinal = 0.0;
902 352836 : loop_side.hasConstSpeedBranchPumps = false;
903 :
904 : // Now loop through all the branches on this LoopSide and get flow requests
905 352836 : int const NumBranchesOnThisLoopSide = loop_side.TotalBranches;
906 352836 : int ParallelBranchIndex = 0;
907 1959642 : for (int BranchCounter = 1; BranchCounter <= NumBranchesOnThisLoopSide; ++BranchCounter) {
908 1606806 : Real64 ThisBranchFlowRequestNeedAndTurnOn = 0.0;
909 1606806 : Real64 ThisBranchFlowRequestNeedIfOn = 0.0;
910 :
911 : // reference
912 1606806 : auto &branch(loop_side.Branch(BranchCounter));
913 :
914 1606806 : if (BranchCounter > 1 && BranchCounter < NumBranchesOnThisLoopSide) ++ParallelBranchIndex;
915 :
916 1606806 : if (branch.disableOverrideForCSBranchPumping) {
917 0 : branch.RequestedMassFlow = 0.0;
918 0 : continue;
919 : }
920 :
921 1606806 : int const NumCompsOnThisBranch = branch.TotalComponents;
922 3213612 : for (int CompCounter = 1; CompCounter <= NumCompsOnThisBranch; ++CompCounter) {
923 :
924 : // reference
925 1606806 : auto &component(branch.Comp(CompCounter));
926 :
927 1606806 : int NodeToCheckRequest = component.NodeNumIn;
928 1606806 : LoopFlowStatus FlowPriorityStatus = component.FlowPriority;
929 :
930 : // reference
931 1606806 : auto const &node_with_request = state.dataLoopNodes->Node(NodeToCheckRequest);
932 :
933 1606806 : if (!DataPlant::PlantEquipmentTypeIsPump[static_cast<int>(component.Type)]) {
934 :
935 1430388 : if (FlowPriorityStatus == DataPlant::LoopFlowStatus::Invalid) {
936 : // do nothing
937 1430388 : } else if (FlowPriorityStatus == DataPlant::LoopFlowStatus::NeedyAndTurnsLoopOn) {
938 424680 : ThisBranchFlowRequestNeedAndTurnOn = max(ThisBranchFlowRequestNeedAndTurnOn, node_with_request.MassFlowRateRequest);
939 424680 : ThisBranchFlowRequestNeedIfOn = max(ThisBranchFlowRequestNeedIfOn, node_with_request.MassFlowRateRequest);
940 1005708 : } else if (FlowPriorityStatus == DataPlant::LoopFlowStatus::NeedyIfLoopOn) {
941 30320 : ThisBranchFlowRequestNeedIfOn = max(ThisBranchFlowRequestNeedIfOn, node_with_request.MassFlowRateRequest);
942 : } else if (FlowPriorityStatus == DataPlant::LoopFlowStatus::TakesWhatGets) {
943 : // do nothing
944 : }
945 : } else { // handle pumps differently
946 176418 : if ((BranchCounter == 1) && (LoopSideCounter == DataPlant::LoopSideLocation::Supply) &&
947 176418 : (loop.CommonPipeType == DataPlant::CommonPipeType::TwoWay)) {
948 : // special primary side flow request for two way common pipe
949 0 : int const CompIndex = component.CompNum;
950 0 : switch (component.Type) {
951 : // remove var speed pumps from this case statement if can set MassFlowRateRequest
952 0 : case DataPlant::PlantEquipmentType::PumpConstantSpeed:
953 : case DataPlant::PlantEquipmentType::PumpVariableSpeed:
954 : case DataPlant::PlantEquipmentType::PumpBankVariableSpeed:
955 0 : if (CompIndex > 0) {
956 : ThisBranchFlowRequestNeedIfOn =
957 0 : max(ThisBranchFlowRequestNeedIfOn, state.dataPumps->PumpEquip(CompIndex).MassFlowRateMax);
958 : }
959 0 : break;
960 0 : case DataPlant::PlantEquipmentType::PumpBankConstantSpeed:
961 0 : if (CompIndex > 0) {
962 0 : ThisBranchFlowRequestNeedIfOn = max(ThisBranchFlowRequestNeedIfOn,
963 0 : state.dataPumps->PumpEquip(CompIndex).MassFlowRateMax /
964 0 : state.dataPumps->PumpEquip(CompIndex).NumPumpsInBank);
965 : }
966 0 : break;
967 0 : default:
968 0 : ThisBranchFlowRequestNeedIfOn = max(ThisBranchFlowRequestNeedIfOn, node_with_request.MassFlowRateRequest);
969 0 : break;
970 : }
971 :
972 176418 : } else if ((BranchCounter == 1) && (LoopSideCounter == DataPlant::LoopSideLocation::Supply) &&
973 176418 : (loop.CommonPipeType == DataPlant::CommonPipeType::Single)) {
974 0 : int const CompIndex = component.CompNum;
975 0 : switch (component.Type) {
976 : // remove var speed pumps from this case statement if can set MassFlowRateRequest
977 0 : case DataPlant::PlantEquipmentType::PumpConstantSpeed:
978 : case DataPlant::PlantEquipmentType::PumpVariableSpeed:
979 : case DataPlant::PlantEquipmentType::PumpBankVariableSpeed: {
980 0 : if (CompIndex > 0) {
981 : ThisBranchFlowRequestNeedIfOn =
982 0 : max(ThisBranchFlowRequestNeedIfOn, state.dataPumps->PumpEquip(CompIndex).MassFlowRateMax);
983 : }
984 0 : break;
985 : }
986 0 : case DataPlant::PlantEquipmentType::PumpBankConstantSpeed:
987 0 : if (CompIndex > 0) {
988 0 : ThisBranchFlowRequestNeedIfOn = max(ThisBranchFlowRequestNeedIfOn,
989 0 : state.dataPumps->PumpEquip(CompIndex).MassFlowRateMax /
990 0 : state.dataPumps->PumpEquip(CompIndex).NumPumpsInBank);
991 : }
992 0 : break;
993 0 : default:
994 0 : ThisBranchFlowRequestNeedIfOn = max(ThisBranchFlowRequestNeedIfOn, node_with_request.MassFlowRateRequest);
995 : }
996 0 : } else {
997 176418 : int const CompIndex = component.CompNum;
998 176418 : switch (component.Type) {
999 40104 : case DataPlant::PlantEquipmentType::PumpConstantSpeed:
1000 40104 : if (CompIndex > 0) {
1001 40104 : auto const &this_pump(state.dataPumps->PumpEquip(CompIndex));
1002 40104 : if (ParallelBranchIndex >= 1) { // branch pump
1003 0 : if (branch.max_abs_Comp_MyLoad() > HVAC::SmallLoad) {
1004 0 : ThisBranchFlowRequestNeedIfOn = max(ThisBranchFlowRequestNeedIfOn, this_pump.MassFlowRateMax);
1005 0 : } else if (loop.CommonPipeType != DataPlant::CommonPipeType::No) { // common pipe and constant branch pumps
1006 0 : ThisBranchFlowRequestNeedIfOn = max(ThisBranchFlowRequestNeedIfOn, this_pump.MassFlowRateMax);
1007 : }
1008 0 : loop_side.hasConstSpeedBranchPumps = true;
1009 0 : branch.HasConstantSpeedBranchPump = true;
1010 0 : branch.ConstantSpeedBranchMassFlow = this_pump.MassFlowRateMax;
1011 : } else { // inlet pump
1012 40104 : ThisBranchFlowRequestNeedIfOn = max(ThisBranchFlowRequestNeedIfOn, this_pump.MassFlowRateMax);
1013 : }
1014 : }
1015 40104 : break;
1016 0 : case DataPlant::PlantEquipmentType::PumpBankConstantSpeed:
1017 0 : if (CompIndex > 0) {
1018 0 : auto const &this_pump(state.dataPumps->PumpEquip(CompIndex));
1019 0 : if (ParallelBranchIndex >= 1) { // branch pump
1020 0 : if (branch.max_abs_Comp_MyLoad() > HVAC::SmallLoad) {
1021 : ThisBranchFlowRequestNeedIfOn =
1022 0 : max(ThisBranchFlowRequestNeedIfOn, this_pump.MassFlowRateMax / this_pump.NumPumpsInBank);
1023 0 : } else if (loop.CommonPipeType != DataPlant::CommonPipeType::No) { // common pipe and constant branch pumps
1024 : ThisBranchFlowRequestNeedIfOn =
1025 0 : max(ThisBranchFlowRequestNeedIfOn, this_pump.MassFlowRateMax / this_pump.NumPumpsInBank);
1026 : }
1027 0 : loop_side.hasConstSpeedBranchPumps = true;
1028 0 : branch.HasConstantSpeedBranchPump = true;
1029 0 : branch.ConstantSpeedBranchMassFlow = this_pump.MassFlowRateMax / this_pump.NumPumpsInBank;
1030 : } else { // inlet pump
1031 : ThisBranchFlowRequestNeedIfOn =
1032 0 : max(ThisBranchFlowRequestNeedIfOn, this_pump.MassFlowRateMax / this_pump.NumPumpsInBank);
1033 : }
1034 : }
1035 0 : break;
1036 :
1037 : // overwrite here for branch pumps
1038 136314 : case DataPlant::PlantEquipmentType::PumpVariableSpeed:
1039 : case DataPlant::PlantEquipmentType::PumpBankVariableSpeed:
1040 : case DataPlant::PlantEquipmentType::PumpCondensate:
1041 136314 : if (component.CompNum > 0) {
1042 136314 : auto &this_pump(state.dataPumps->PumpEquip(component.CompNum));
1043 136314 : this_pump.LoopSolverOverwriteFlag = false;
1044 : }
1045 : default:
1046 136314 : break;
1047 : }
1048 : }
1049 : }
1050 : }
1051 1606806 : if (BranchCounter == 1) { // inlet branch
1052 352836 : InletBranchRequestNeedAndTurnOn = ThisBranchFlowRequestNeedAndTurnOn;
1053 352836 : InletBranchRequestNeedIfOn = ThisBranchFlowRequestNeedIfOn;
1054 1253970 : } else if (BranchCounter < NumBranchesOnThisLoopSide) { // branchcounter = 1 is already caught
1055 901134 : ParallelBranchRequestsNeedAndTurnOn += ThisBranchFlowRequestNeedAndTurnOn;
1056 901134 : ParallelBranchRequestsNeedIfOn += ThisBranchFlowRequestNeedIfOn;
1057 352836 : } else if (BranchCounter == NumBranchesOnThisLoopSide) { // outlet branch
1058 352836 : OutletBranchRequestNeedAndTurnOn = ThisBranchFlowRequestNeedAndTurnOn;
1059 352836 : OutletBranchRequestNeedIfOn = ThisBranchFlowRequestNeedIfOn;
1060 : }
1061 :
1062 1606806 : branch.RequestedMassFlow = max(ThisBranchFlowRequestNeedIfOn, ThisBranchFlowRequestNeedAndTurnOn);
1063 : }
1064 352836 : loop_side.flowRequestNeedAndTurnOn =
1065 352836 : max(InletBranchRequestNeedAndTurnOn, ParallelBranchRequestsNeedAndTurnOn, OutletBranchRequestNeedAndTurnOn);
1066 352836 : loop_side.flowRequestNeedIfOn = max(InletBranchRequestNeedIfOn, ParallelBranchRequestsNeedIfOn, OutletBranchRequestNeedIfOn);
1067 : }
1068 :
1069 176418 : auto &this_loop_side(loop.LoopSide(this->plantLoc.loopSideNum));
1070 176418 : auto &other_loop_side(loop.LoopSide(OtherSide));
1071 :
1072 : //~ Now that we have calculated each sides different status's requests, process to find final
1073 176418 : if ((this_loop_side.flowRequestNeedAndTurnOn + other_loop_side.flowRequestNeedAndTurnOn) < DataBranchAirLoopPlant::MassFlowTolerance) {
1074 80911 : this_loop_side.flowRequestFinal = 0.0;
1075 80911 : other_loop_side.flowRequestFinal = 0.0;
1076 : } else { // some flow is needed and loop should try to run
1077 95507 : this_loop_side.flowRequestFinal = max(this_loop_side.flowRequestNeedAndTurnOn, this_loop_side.flowRequestNeedIfOn);
1078 95507 : other_loop_side.flowRequestFinal = max(other_loop_side.flowRequestNeedAndTurnOn, other_loop_side.flowRequestNeedIfOn);
1079 : }
1080 : // now store final flow requests on each loop side data structure
1081 176418 : this_loop_side.FlowRequest = this_loop_side.flowRequestFinal;
1082 176418 : other_loop_side.FlowRequest = other_loop_side.flowRequestFinal;
1083 :
1084 176418 : if (loop.CommonPipeType == DataPlant::CommonPipeType::No) {
1085 : // we may or may not have a pump on this side, but the flow request is the larger of the two side's final
1086 176418 : if ((!this_loop_side.hasConstSpeedBranchPumps) && (!other_loop_side.hasConstSpeedBranchPumps)) {
1087 176418 : LoopFlow = max(this_loop_side.flowRequestFinal, other_loop_side.flowRequestFinal);
1088 : } else { // account for stepped loop flow rates required of branch pumps
1089 :
1090 : // rules for setting flow when there are constant speed branch pumps.
1091 : // 1. Check if above routines already selected a loop flow rate based on the constant speed branches, if so then just use it
1092 0 : if (this_loop_side.hasConstSpeedBranchPumps && (this_loop_side.flowRequestFinal >= other_loop_side.flowRequestFinal)) {
1093 : // okay, just use basic logic
1094 0 : LoopFlow = max(this_loop_side.flowRequestFinal, other_loop_side.flowRequestFinal);
1095 0 : } else if (other_loop_side.hasConstSpeedBranchPumps && (this_loop_side.flowRequestFinal <= other_loop_side.flowRequestFinal)) {
1096 : // okay, just use basic logic
1097 0 : LoopFlow = max(this_loop_side.flowRequestFinal, other_loop_side.flowRequestFinal);
1098 : } else { // not okay, we have a case that will likely need special correcting
1099 : // 2. determine which loop side has the stepped data
1100 0 : DataPlant::LoopSideLocation LoopSideIndex = DataPlant::LoopSideLocation::Invalid;
1101 0 : if (this_loop_side.hasConstSpeedBranchPumps && (this_loop_side.flowRequestFinal < other_loop_side.flowRequestFinal)) {
1102 0 : LoopSideIndex = this->plantLoc.loopSideNum;
1103 0 : } else if (other_loop_side.hasConstSpeedBranchPumps && (other_loop_side.flowRequestFinal < this_loop_side.flowRequestFinal)) {
1104 0 : LoopSideIndex = OtherSide;
1105 : }
1106 0 : auto &loop_side(loop.LoopSide(LoopSideIndex));
1107 :
1108 : // 3. step through and find out needed information
1109 : // 3a. search the loop side with branch pumps and find the steps available with non-zero Myloads
1110 : // 3b. search the loop side with branch pumps and find the steps available with zero Myloads
1111 : // LoadedConstantSpeedBranchFlowRateSteps = 0.0;
1112 0 : Real64 LoadedConstantSpeedBranchFlowRateSteps_sum = 0.0;
1113 0 : this_loop_side.noLoadConstantSpeedBranchFlowRateSteps = 0.0;
1114 0 : Real64 NoLoadConstantSpeedBranchFlowRateSteps_sum = 0.0;
1115 0 : int ParallelBranchIndex = 0;
1116 0 : int const NumBranchesOnThisLoopSide = loop_side.TotalBranches;
1117 0 : auto const &loop_branches(loop_side.Branch);
1118 0 : for (int BranchCounter = 1; BranchCounter <= NumBranchesOnThisLoopSide; ++BranchCounter) {
1119 0 : auto const &loop_branch(loop_branches(BranchCounter));
1120 0 : if (BranchCounter > 1 && BranchCounter < NumBranchesOnThisLoopSide) ++ParallelBranchIndex;
1121 0 : if (loop_branch.HasConstantSpeedBranchPump) {
1122 0 : Real64 const branch_mass_flow(loop_branch.ConstantSpeedBranchMassFlow);
1123 0 : if (loop_branch.max_abs_Comp_MyLoad() > HVAC::SmallLoad) {
1124 0 : LoadedConstantSpeedBranchFlowRateSteps_sum += branch_mass_flow;
1125 : } else {
1126 0 : this_loop_side.noLoadConstantSpeedBranchFlowRateSteps(ParallelBranchIndex) = branch_mass_flow;
1127 0 : NoLoadConstantSpeedBranchFlowRateSteps_sum += branch_mass_flow;
1128 : }
1129 : }
1130 : }
1131 :
1132 : // 4. allocate which branches to use,
1133 0 : Real64 tmpLoopFlow = max(this_loop_side.flowRequestFinal, other_loop_side.flowRequestFinal);
1134 0 : Real64 MaxBranchPumpLoopSideFlow = LoadedConstantSpeedBranchFlowRateSteps_sum + NoLoadConstantSpeedBranchFlowRateSteps_sum;
1135 0 : tmpLoopFlow = min(tmpLoopFlow, MaxBranchPumpLoopSideFlow);
1136 : // 4b. first use all the branches with non-zero MyLoad
1137 0 : if (tmpLoopFlow > LoadedConstantSpeedBranchFlowRateSteps_sum) {
1138 0 : Real64 AccumFlowSteps = LoadedConstantSpeedBranchFlowRateSteps_sum;
1139 0 : ParallelBranchIndex = 0;
1140 0 : for (int BranchCounter = 1; BranchCounter <= NumBranchesOnThisLoopSide; ++BranchCounter) {
1141 0 : if (BranchCounter > 1 && BranchCounter < NumBranchesOnThisLoopSide) {
1142 0 : ++ParallelBranchIndex;
1143 : } else {
1144 0 : continue;
1145 : }
1146 0 : Real64 const steps = this_loop_side.noLoadConstantSpeedBranchFlowRateSteps(ParallelBranchIndex);
1147 0 : if (steps > 0.0) { // add in branches with zero MyLoad in branch input order until satisfied
1148 0 : if (tmpLoopFlow > AccumFlowSteps) {
1149 0 : if (tmpLoopFlow <= AccumFlowSteps + steps) { // found it set requests and exit
1150 0 : tmpLoopFlow = AccumFlowSteps + steps;
1151 0 : loop_side.Branch(BranchCounter).RequestedMassFlow = steps;
1152 0 : LoopFlow = tmpLoopFlow;
1153 0 : break;
1154 : } else {
1155 0 : AccumFlowSteps += steps;
1156 0 : loop_side.Branch(BranchCounter).RequestedMassFlow = steps;
1157 : }
1158 : }
1159 : }
1160 : }
1161 : }
1162 : }
1163 : }
1164 0 : } else if (loop.CommonPipeType == DataPlant::CommonPipeType::TwoWay) {
1165 0 : LoopFlow = this_loop_side.flowRequestFinal;
1166 0 : } else if (loop.CommonPipeType == DataPlant::CommonPipeType::Single) {
1167 0 : LoopFlow = this_loop_side.flowRequestFinal;
1168 : }
1169 :
1170 : // overrides the loop solver flow request to allow loop pump to turn off when not in use
1171 176418 : if (this_loop_side.TotalPumps == 1) {
1172 88208 : if (LoopFlow < HVAC::VerySmallMassFlow) { // Update from dataconvergetols...
1173 197652 : for (int BranchCounter = 1; BranchCounter <= this_loop_side.TotalBranches; ++BranchCounter) {
1174 : // reference
1175 157528 : auto &branch(this_loop_side.Branch(BranchCounter));
1176 157528 : int const NumCompsOnThisBranch = branch.TotalComponents;
1177 315056 : for (int CompCounter = 1; CompCounter <= NumCompsOnThisBranch; ++CompCounter) {
1178 157528 : auto const &component(branch.Comp(CompCounter));
1179 157528 : switch (component.Type) {
1180 32004 : case DataPlant::PlantEquipmentType::PumpVariableSpeed:
1181 : case DataPlant::PlantEquipmentType::PumpBankVariableSpeed:
1182 : case DataPlant::PlantEquipmentType::PumpCondensate:
1183 32004 : if (component.CompNum > 0) {
1184 32004 : auto &this_pump(state.dataPumps->PumpEquip(component.CompNum));
1185 32004 : this_pump.LoopSolverOverwriteFlag = true;
1186 : }
1187 : default:
1188 157528 : break;
1189 : }
1190 : }
1191 : }
1192 : }
1193 : }
1194 :
1195 176418 : return LoopFlow;
1196 : }
1197 :
1198 176418 : void HalfLoopData::DoFlowAndLoadSolutionPass(EnergyPlusData &state, LoopSideLocation OtherSide, int ThisSideInletNode, bool FirstHVACIteration)
1199 : {
1200 :
1201 : // This is passed in-out deep down into the depths where the load op manager calls EMS and EMS can shut down pumps
1202 176418 : bool LoopShutDownFlag = false;
1203 :
1204 : // First thing is to setup mass flow request information
1205 176418 : Real64 ThisLoopSideFlowRequest = this->SetupLoopFlowRequest(state, OtherSide);
1206 :
1207 : // Now we know what the loop would "like" to run at, let's see the pump
1208 : // operation range (min/max avail) to see whether it is possible this time around
1209 176418 : Real64 ThisLoopSideFlow = this->DetermineLoopSideFlowRate(state, ThisSideInletNode, ThisLoopSideFlowRequest);
1210 :
1211 979822 : for (auto &branch : this->Branch) {
1212 803404 : branch.lastComponentSimulated = 0;
1213 : }
1214 :
1215 : // We also need to establish a baseline "other-side-based" loop demand based on this possible flow rate
1216 176418 : this->InitialDemandToLoopSetPoint = this->CalcOtherSideDemand(state, ThisLoopSideFlow);
1217 176418 : this->UpdatedDemandToLoopSetPoint = this->InitialDemandToLoopSetPoint;
1218 176418 : this->LoadToLoopSetPointThatWasntMet = 0.0;
1219 :
1220 : // We now have a loop side flow request, along with inlet min/max avails.
1221 : // We can now make a first pass through the component simulation, requesting flow as necessary.
1222 : // Normal "supply side" components will set a mass flow rate on their outlet node to request flow,
1223 : // while "Demand side" components will set a a mass flow request on their inlet node to request flow.
1224 176418 : this->FlowLock = DataPlant::FlowLock::Unlocked;
1225 176418 : this->SimulateAllLoopSideBranches(state, ThisLoopSideFlow, FirstHVACIteration, LoopShutDownFlag);
1226 :
1227 : // discussion/comments about loop solver/flow resolver interaction
1228 : // At this point, the components have been simulated. They should have either:
1229 : // - logged a massflowrequest
1230 : // - or logged a MassFlowRate
1231 : // We need to decide what the components are going to do on FlowLock=0.
1232 : // If we want all control here at the solver level, the components just need to
1233 : // log their MassFlowRate on their outlet nodes, or some other mechanism.
1234 : // Then the loop solver can scan the branch and get the max, and this will be the requested
1235 : // flow rate for the branch.
1236 : // The loop solver will then set this as the branch outlet mass flow rate in preparation
1237 : // for the flow resolver.
1238 : // The loop solver may need to do something to the inlet/outlet branch, but I'm not sure yet.
1239 : // The following comment block is what I had already thought of, and it may still make sense.
1240 :
1241 : // Now that all the flow requests have been logged, we need to prepare them for the
1242 : // flow resolver. This will just take the requests and determine the desired flow
1243 : // request for that branch according to pump placement, pump type, and other component
1244 : // conditions. In many cases, this will just be to simply take the max request from
1245 : // the branch, which will already be within pumping limits for that flow path.
1246 : // We can then call the flow resolver to lock down branch inlet flow rates.
1247 :
1248 : // The flow resolver takes information such as requested flows and min/max available flows and
1249 : // sets the corrected flow on the inlet to each parallel branch
1250 176418 : this->ResolveParallelFlows(state, ThisLoopSideFlow, FirstHVACIteration);
1251 :
1252 : // Re-Initialize variables for this next pass
1253 176418 : this->InitialDemandToLoopSetPointSAVED = this->InitialDemandToLoopSetPoint;
1254 176418 : this->CurrentAlterationsToDemand = 0.0;
1255 176418 : this->UpdatedDemandToLoopSetPoint = this->InitialDemandToLoopSetPoint;
1256 :
1257 : // Now that flow rates have been resolved, we just need to set the flow lock status
1258 : // flag, and resimulate. During this simulation each component will still use the
1259 : // SetFlowRequest routine, but this routine will also set the outlet flow rate
1260 : // equal to the inlet flow rate, according to flowlock logic.
1261 176418 : this->FlowLock = DataPlant::FlowLock::Locked;
1262 176418 : this->SimulateAllLoopSideBranches(state, ThisLoopSideFlow, FirstHVACIteration, LoopShutDownFlag);
1263 176418 : }
1264 :
1265 176418 : void HalfLoopData::ResolveParallelFlows(EnergyPlusData &state,
1266 : Real64 const ThisLoopSideFlow, // [kg/s] total flow to be split
1267 : bool const FirstHVACIteration // TRUE if First HVAC iteration of Time step
1268 : )
1269 : {
1270 :
1271 : // SUBROUTINE INFORMATION:
1272 : // AUTHOR Brandon Anderson, Dan Fisher
1273 : // DATE WRITTEN October 1999
1274 : // MODIFIED May 2005 Sankaranarayanan K P, Rich Liesen
1275 : // RE-ENGINEERED Sept 2010 Dan Fisher, Brent Griffith for demand side update
1276 :
1277 : // PURPOSE OF THIS SUBROUTINE:
1278 : // This subroutine takes the overall loop side flow and distributes
1279 : // it among parallel branches. this is the main implementation of
1280 : // flow splitting for plant splitter/mixer
1281 :
1282 : // METHODOLOGY EMPLOYED:
1283 : // Flow through the branches is currently determined by
1284 : // the active component on the branch, as well as the
1285 : // order of the branches following the splitter.
1286 : // SimPlantEquipment is run first, and the active components
1287 : // request their flow. These flows are compared and a simple
1288 : // algorithm balances flow in the branches. The flow in these
1289 : // branches is then locked down, via MassFlowRateMaxAvail and MinAvail
1290 : // SimPlant Equipment is then run again in order to get correct
1291 : // properties. Finally, Max/MinAvail are reset for the next time step.
1292 :
1293 : // Using/Aliasing
1294 :
1295 : // SUBROUTINE PARAMETER DEFINITIONS:
1296 176418 : int constexpr LoopSideSingleBranch(1); // For readability
1297 :
1298 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1299 : Real64 ActiveFlowRate; // The flow available when cycling through branches
1300 : Real64 PassiveFlowRate; // The flow available when cycling through branches
1301 : Real64 FracFlow; // The flow available when cycling through branches
1302 : Real64 ThisBranchRequestFrac; // The request ratio
1303 : Real64 totalMax; // The flow available when cycling through branches
1304 : Real64 FlowRemaining; // The flow available when cycling through branches
1305 : int LastNodeOnBranch; // intermediate value used for better readabilty
1306 : int FirstNodeOnBranch; // intermediate value used for better readabilty
1307 : Real64 BranchFlowReq;
1308 : Real64 BranchMinAvail;
1309 : Real64 BranchMaxAvail;
1310 : Real64 ParallelBranchMaxAvail;
1311 : Real64 ParallelBranchMinAvail;
1312 : Real64 TotParallelBranchFlowReq;
1313 : Real64 StartingFlowRate;
1314 : Real64 ThisBranchRequest;
1315 :
1316 : // If there is no splitter then there is no continuity to enforce.
1317 176418 : if (!this->Splitter.Exists) {
1318 :
1319 : // If there's only one branch, then RETURN
1320 0 : if (this->TotalBranches == 1) {
1321 : // The branch should just try to meet the request previously calculated. This should be good,
1322 : // just need to make sure that during FlowUnlocked, no one constrained Min/Max farther.
1323 : // This would have been propagated down the branch, so we can check the outlet node min/max avail for this.
1324 0 : auto const &this_single_branch(this->Branch(LoopSideSingleBranch));
1325 0 : LastNodeOnBranch = this_single_branch.NodeNumOut;
1326 0 : FirstNodeOnBranch = this_single_branch.NodeNumIn;
1327 0 : BranchMinAvail = state.dataLoopNodes->Node(LastNodeOnBranch).MassFlowRateMinAvail;
1328 0 : BranchMaxAvail = state.dataLoopNodes->Node(LastNodeOnBranch).MassFlowRateMaxAvail;
1329 0 : state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate = min(max(ThisLoopSideFlow, BranchMinAvail), BranchMaxAvail);
1330 : // now with flow locked, this single branch will just ran at the specified flow rate, so we are done
1331 152002 : return;
1332 : } else {
1333 0 : ShowSevereError(state, "Plant topology problem on \"" + this->loopSideDescription + "\"");
1334 0 : ShowContinueError(state, "There are multiple branches, yet no splitter. This is an invalid configuration.");
1335 0 : ShowContinueError(state, "Add a set of connectors, use put components on a single branch.");
1336 0 : ShowFatalError(state, "Invalid plant topology causes program termination.");
1337 0 : return;
1338 : }
1339 : }
1340 :
1341 : // If a splitter/mixer combination exist on the loop
1342 176418 : if (this->Splitter.Exists && this->Mixer.Exists) {
1343 :
1344 : // Zero out local variables
1345 176418 : TotParallelBranchFlowReq = 0.0;
1346 176418 : int NumSplitOutlets = this->Splitter.TotalOutletNodes;
1347 176418 : if (NumSplitOutlets < 1) {
1348 0 : ShowSevereError(state, "Plant topology problem on \"" + this->loopSideDescription + "\"");
1349 0 : ShowContinueError(state, "Diagnostic error in PlantLoopSolver::ResolveParallelFlows.");
1350 0 : ShowContinueError(state, "Splitter improperly specified, no splitter outlets.");
1351 0 : ShowFatalError(state, "Invalid plant topology causes program termination.");
1352 : }
1353 :
1354 176418 : int NumActiveBranches = 0;
1355 176418 : ParallelBranchMaxAvail = 0.0;
1356 176418 : ParallelBranchMinAvail = 0.0;
1357 619474 : for (int iBranch = 1; iBranch <= NumSplitOutlets; ++iBranch) {
1358 :
1359 443056 : int BranchNum = this->Splitter.BranchNumOut(iBranch);
1360 443056 : auto &this_branch(this->Branch(BranchNum));
1361 443056 : int SplitterBranchOut = this->Splitter.BranchNumOut(iBranch);
1362 443056 : auto const &this_splitter_outlet_branch(this->Branch(SplitterBranchOut));
1363 443056 : LastNodeOnBranch = this_branch.NodeNumOut;
1364 443056 : FirstNodeOnBranch = this_branch.NodeNumIn;
1365 443056 : BranchFlowReq = this_branch.DetermineBranchFlowRequest(state);
1366 443056 : this_branch.RequestedMassFlow = BranchFlowReq; // store this for later use in logic for remaining flow allocations
1367 : // now, if we are have branch pumps, here is the situation:
1368 : // constant speed pumps lock in a flow request on the inlet node
1369 : // variable speed pumps which have other components on the branch do not log a request themselves
1370 : // the DetermineBranchFlowRequest routine only looks at the branch inlet node
1371 : // for variable speed branch pumps then, this won't work because the branch will be requesting zero
1372 : // so let's adjust for this here to make sure these branches get good representation
1373 : // This comment above is not true, for series active branches, DetermineBranchFlowRequest does scan down the branch's
1374 : // components already, no need to loop over components
1375 443056 : BranchMinAvail = state.dataLoopNodes->Node(LastNodeOnBranch).MassFlowRateMinAvail;
1376 443056 : BranchMaxAvail = state.dataLoopNodes->Node(LastNodeOnBranch).MassFlowRateMaxAvail;
1377 : // !sum the branch flow requests to a total parallel branch flow request
1378 443056 : bool activeBranch = this_splitter_outlet_branch.controlType == DataBranchAirLoopPlant::ControlType::Active;
1379 443056 : bool isSeriesActiveAndRequesting =
1380 443056 : (this_splitter_outlet_branch.controlType == DataBranchAirLoopPlant::ControlType::SeriesActive) && (BranchFlowReq > 0.0);
1381 443056 : if (activeBranch || isSeriesActiveAndRequesting) { // revised logic for series active
1382 300476 : TotParallelBranchFlowReq += BranchFlowReq;
1383 300476 : ++NumActiveBranches;
1384 : }
1385 443056 : state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate = BranchFlowReq;
1386 443056 : state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRateMinAvail = BranchMinAvail;
1387 443056 : state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRateMaxAvail = BranchMaxAvail;
1388 443056 : ParallelBranchMaxAvail += BranchMaxAvail;
1389 443056 : ParallelBranchMinAvail += BranchMinAvail;
1390 : }
1391 : // ! Find branch number and flow rates at splitter inlet
1392 176418 : int SplitterBranchIn = this->Splitter.BranchNumIn;
1393 176418 : int FirstNodeOnBranchIn = this->Branch(SplitterBranchIn).NodeNumIn;
1394 : // ! Find branch number and flow rates at mixer outlet
1395 176418 : int MixerBranchOut = this->Mixer.BranchNumOut;
1396 176418 : LastNodeOnBranch = this->Branch(MixerBranchOut).NodeNumOut;
1397 176418 : int FirstNodeOnBranchOut = this->Branch(MixerBranchOut).NodeNumIn;
1398 :
1399 176418 : auto &first_branch_inlet_node(state.dataLoopNodes->Node(FirstNodeOnBranchIn));
1400 176418 : auto &last_branch_inlet_node(state.dataLoopNodes->Node(FirstNodeOnBranchOut));
1401 :
1402 : // Reset branch inlet node flow rates for the first and last branch on loop
1403 176418 : first_branch_inlet_node.MassFlowRate = ThisLoopSideFlow;
1404 176418 : last_branch_inlet_node.MassFlowRate = ThisLoopSideFlow;
1405 :
1406 : // Reset branch inlet node Min/MaxAvails for the first and last branch on loop
1407 176418 : first_branch_inlet_node.MassFlowRateMaxAvail = min(first_branch_inlet_node.MassFlowRateMaxAvail, ParallelBranchMaxAvail);
1408 176418 : first_branch_inlet_node.MassFlowRateMaxAvail =
1409 176418 : min(first_branch_inlet_node.MassFlowRateMaxAvail, last_branch_inlet_node.MassFlowRateMaxAvail);
1410 176418 : first_branch_inlet_node.MassFlowRateMinAvail = max(first_branch_inlet_node.MassFlowRateMinAvail, ParallelBranchMinAvail);
1411 176418 : first_branch_inlet_node.MassFlowRateMinAvail =
1412 176418 : max(first_branch_inlet_node.MassFlowRateMinAvail, last_branch_inlet_node.MassFlowRateMinAvail);
1413 176418 : last_branch_inlet_node.MassFlowRateMinAvail = first_branch_inlet_node.MassFlowRateMinAvail;
1414 176418 : last_branch_inlet_node.MassFlowRateMaxAvail = first_branch_inlet_node.MassFlowRateMaxAvail;
1415 :
1416 : // Initialize the remaining flow variable
1417 176418 : FlowRemaining = ThisLoopSideFlow;
1418 :
1419 : // Initialize flow on passive, bypass and uncontrolled parallel branches to zero. For these branches
1420 : // MinAvail is not enforced
1421 619474 : for (int OutletNum = 1; OutletNum <= NumSplitOutlets; ++OutletNum) {
1422 443056 : int SplitterBranchOut = this->Splitter.BranchNumOut(OutletNum);
1423 443056 : FirstNodeOnBranch = this->Branch(SplitterBranchOut).NodeNumIn;
1424 585636 : if (this->Branch(SplitterBranchOut).controlType != DataBranchAirLoopPlant::ControlType::Active &&
1425 142580 : this->Branch(SplitterBranchOut).controlType != DataBranchAirLoopPlant::ControlType::SeriesActive) {
1426 142580 : state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate = 0.0;
1427 285160 : this->PushBranchFlowCharacteristics(
1428 142580 : state, SplitterBranchOut, state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate, FirstHVACIteration);
1429 : }
1430 : }
1431 :
1432 : // IF SUFFICIENT FLOW TO MEET ALL PARALLEL BRANCH FLOW REQUESTS
1433 176418 : if (FlowRemaining < DataBranchAirLoopPlant::MassFlowTolerance) { // no flow available at all for splitter
1434 290148 : for (int OutletNum = 1; OutletNum <= NumSplitOutlets; ++OutletNum) {
1435 207580 : int SplitterBranchOut = this->Splitter.BranchNumOut(OutletNum);
1436 415160 : for (int CompCounter = 1; CompCounter <= this->Branch(SplitterBranchOut).TotalComponents; ++CompCounter) {
1437 :
1438 207580 : FirstNodeOnBranch = this->Branch(SplitterBranchOut).NodeNumIn;
1439 207580 : int CompInletNode = this->Branch(SplitterBranchOut).Comp(CompCounter).NodeNumIn;
1440 207580 : int CompOutletNode = this->Branch(SplitterBranchOut).Comp(CompCounter).NodeNumOut;
1441 207580 : state.dataLoopNodes->Node(CompInletNode).MassFlowRate = 0.0;
1442 207580 : state.dataLoopNodes->Node(CompInletNode).MassFlowRateMaxAvail = 0.0;
1443 207580 : state.dataLoopNodes->Node(CompOutletNode).MassFlowRate = 0.0;
1444 207580 : state.dataLoopNodes->Node(CompOutletNode).MassFlowRateMaxAvail = 0.0;
1445 : }
1446 : }
1447 82568 : return;
1448 93850 : } else if (FlowRemaining >= TotParallelBranchFlowReq) {
1449 :
1450 : // 1) Satisfy flow demand of ACTIVE splitter outlet branches
1451 261979 : for (int OutletNum = 1; OutletNum <= NumSplitOutlets; ++OutletNum) {
1452 192545 : int SplitterBranchOut = this->Splitter.BranchNumOut(OutletNum);
1453 192545 : FirstNodeOnBranch = this->Branch(SplitterBranchOut).NodeNumIn;
1454 249624 : if (this->Branch(SplitterBranchOut).controlType == DataBranchAirLoopPlant::ControlType::Active ||
1455 57079 : this->Branch(SplitterBranchOut).controlType == DataBranchAirLoopPlant::ControlType::SeriesActive) {
1456 : // branch flow is min of requested flow and remaining flow
1457 135466 : state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate =
1458 135466 : min(state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate, FlowRemaining);
1459 135466 : if (state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate < DataBranchAirLoopPlant::MassFlowTolerance)
1460 28390 : state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate = 0.0;
1461 270932 : this->PushBranchFlowCharacteristics(
1462 135466 : state, SplitterBranchOut, state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate, FirstHVACIteration);
1463 135466 : FlowRemaining -= state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate;
1464 135466 : if (FlowRemaining < DataBranchAirLoopPlant::MassFlowTolerance) FlowRemaining = 0.0;
1465 : }
1466 : }
1467 : // IF the active branches take the entire loop flow, return
1468 69434 : if (FlowRemaining == 0.0) return;
1469 :
1470 : // 2) Distribute remaining flow to PASSIVE branches
1471 16034 : totalMax = 0.0;
1472 86651 : for (int OutletNum = 1; OutletNum <= NumSplitOutlets; ++OutletNum) {
1473 70617 : int SplitterBranchOut = this->Splitter.BranchNumOut(OutletNum);
1474 70617 : FirstNodeOnBranch = this->Branch(SplitterBranchOut).NodeNumIn;
1475 70617 : if (this->Branch(SplitterBranchOut).controlType == DataBranchAirLoopPlant::ControlType::Passive) {
1476 : // Calculate the total max available
1477 0 : totalMax += state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRateMaxAvail;
1478 : }
1479 : }
1480 :
1481 16034 : if (totalMax > 0) {
1482 0 : for (int OutletNum = 1; OutletNum <= NumSplitOutlets; ++OutletNum) {
1483 0 : int SplitterBranchOut = this->Splitter.BranchNumOut(OutletNum);
1484 0 : FirstNodeOnBranch = this->Branch(SplitterBranchOut).NodeNumIn;
1485 0 : if (this->Branch(SplitterBranchOut).controlType == DataBranchAirLoopPlant::ControlType::Passive) {
1486 0 : FracFlow = FlowRemaining / totalMax;
1487 0 : if (FracFlow <= 1.0) { // the passive branches will take all the flow
1488 0 : PassiveFlowRate = FracFlow * state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRateMaxAvail;
1489 : // Check against FlowRemaining
1490 0 : PassiveFlowRate = min(FlowRemaining, PassiveFlowRate);
1491 : // Allow FlowRequest to be increased to meet minimum on branch
1492 0 : PassiveFlowRate = max(PassiveFlowRate, state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRateMinAvail);
1493 0 : FlowRemaining = max((FlowRemaining - PassiveFlowRate), 0.0);
1494 0 : state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate = PassiveFlowRate;
1495 : } else { // Each Branch receives maximum flow and BYPASS must be used
1496 0 : state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate =
1497 0 : min(state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRateMaxAvail, FlowRemaining);
1498 0 : FlowRemaining -= state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate;
1499 : }
1500 0 : this->PushBranchFlowCharacteristics(
1501 0 : state, SplitterBranchOut, state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate, FirstHVACIteration);
1502 : }
1503 : }
1504 : } // totalMax <=0 and flow should be assigned to active branches
1505 : // IF the passive branches take the remaining loop flow, return
1506 16034 : if (FlowRemaining == 0.0) return;
1507 :
1508 : // 3) Distribute remaining flow to the BYPASS
1509 86651 : for (int OutletNum = 1; OutletNum <= this->Splitter.TotalOutletNodes; ++OutletNum) {
1510 70617 : int SplitterBranchOut = this->Splitter.BranchNumOut(OutletNum);
1511 70617 : FirstNodeOnBranch = this->Branch(SplitterBranchOut).NodeNumIn;
1512 70617 : if (this->Branch(SplitterBranchOut).controlType == DataBranchAirLoopPlant::ControlType::Bypass) {
1513 12951 : state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate =
1514 12951 : min(FlowRemaining, state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRateMaxAvail);
1515 25902 : this->PushBranchFlowCharacteristics(
1516 12951 : state, SplitterBranchOut, state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate, FirstHVACIteration);
1517 12951 : FlowRemaining -= state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate;
1518 : }
1519 : }
1520 : // IF the bypass take the remaining loop flow, return
1521 16034 : if (FlowRemaining == 0.0) return;
1522 :
1523 : // 4) If PASSIVE branches and BYPASS are at max and there's still flow, distribute remaining flow to ACTIVE branches but only those
1524 : // that had a non-zero flow request. Try to leave branches off that wanted to be off.
1525 3083 : if (NumActiveBranches > 0) {
1526 3083 : ActiveFlowRate = FlowRemaining / NumActiveBranches; // denominator now only includes active branches that wanted to be "on"
1527 5321 : for (int OutletNum = 1; OutletNum <= NumSplitOutlets; ++OutletNum) {
1528 4235 : int SplitterBranchOut = this->Splitter.BranchNumOut(OutletNum);
1529 4235 : FirstNodeOnBranch = this->Branch(SplitterBranchOut).NodeNumIn;
1530 4235 : bool branchIsActive = this->Branch(SplitterBranchOut).controlType == DataBranchAirLoopPlant::ControlType::Active;
1531 : bool branchIsSeriesActiveAndRequesting =
1532 4235 : this->Branch(SplitterBranchOut).controlType == DataBranchAirLoopPlant::ControlType::SeriesActive &&
1533 0 : this->Branch(SplitterBranchOut).RequestedMassFlow > 0.0;
1534 4235 : if (branchIsActive || branchIsSeriesActiveAndRequesting) { // only series active branches that want to be "on"
1535 : // check Remaining flow (should be correct!)
1536 4235 : ActiveFlowRate = min(ActiveFlowRate, FlowRemaining);
1537 : // set the flow rate to the MIN((MassFlowRate+AvtiveFlowRate), MaxAvail)
1538 4235 : StartingFlowRate = state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate;
1539 4235 : state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate =
1540 4235 : min((state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate + ActiveFlowRate),
1541 4235 : state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRateMaxAvail);
1542 8470 : this->PushBranchFlowCharacteristics(
1543 4235 : state, SplitterBranchOut, state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate, FirstHVACIteration);
1544 : // adjust the remaining flow
1545 4235 : FlowRemaining -= (state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate - StartingFlowRate);
1546 : }
1547 4235 : if (FlowRemaining == 0) break;
1548 : }
1549 : // IF the active branches take the remaining loop flow, return
1550 3083 : if (FlowRemaining == 0.0) return;
1551 :
1552 : // 5) Step 4) could have left ACTIVE branches < MaxAvail. Check to makes sure all ACTIVE branches are at MaxAvail
1553 2514 : for (int OutletNum = 1; OutletNum <= NumSplitOutlets; ++OutletNum) {
1554 1428 : int SplitterBranchOut = this->Splitter.BranchNumOut(OutletNum);
1555 1428 : FirstNodeOnBranch = this->Branch(SplitterBranchOut).NodeNumIn;
1556 1428 : if (this->Branch(SplitterBranchOut).controlType == DataBranchAirLoopPlant::ControlType::Active ||
1557 0 : this->Branch(SplitterBranchOut).controlType == DataBranchAirLoopPlant::ControlType::SeriesActive) {
1558 1428 : StartingFlowRate = state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate;
1559 : ActiveFlowRate =
1560 1428 : min(FlowRemaining, (state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRateMaxAvail - StartingFlowRate));
1561 1428 : FlowRemaining -= ActiveFlowRate;
1562 1428 : state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate = StartingFlowRate + ActiveFlowRate;
1563 2856 : this->PushBranchFlowCharacteristics(
1564 1428 : state, SplitterBranchOut, state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate, FirstHVACIteration);
1565 : }
1566 : }
1567 : }
1568 : // IF the active branches take the remaining loop flow, return
1569 1086 : if (FlowRemaining == 0.0) return;
1570 :
1571 : // 6) Adjust Inlet branch and outlet branch flow rates to match parallel branch rate
1572 744 : TotParallelBranchFlowReq = 0.0;
1573 1488 : for (int iBranch = 1; iBranch <= NumSplitOutlets; ++iBranch) {
1574 744 : int BranchNum = this->Splitter.BranchNumOut(iBranch);
1575 744 : FirstNodeOnBranch = this->Branch(BranchNum).NodeNumIn;
1576 : // calculate parallel branch flow rate
1577 744 : TotParallelBranchFlowReq += state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate;
1578 : }
1579 : // Reset the flow on the splitter inlet branch
1580 744 : SplitterBranchIn = this->Splitter.BranchNumIn;
1581 744 : FirstNodeOnBranchIn = this->Branch(SplitterBranchIn).NodeNumIn;
1582 744 : state.dataLoopNodes->Node(FirstNodeOnBranchIn).MassFlowRate = TotParallelBranchFlowReq;
1583 1488 : this->PushBranchFlowCharacteristics(
1584 744 : state, SplitterBranchIn, state.dataLoopNodes->Node(FirstNodeOnBranchIn).MassFlowRate, FirstHVACIteration);
1585 : // Reset the flow on the Mixer outlet branch
1586 744 : MixerBranchOut = this->Mixer.BranchNumOut;
1587 744 : FirstNodeOnBranchOut = this->Branch(MixerBranchOut).NodeNumIn;
1588 744 : state.dataLoopNodes->Node(FirstNodeOnBranchOut).MassFlowRate = TotParallelBranchFlowReq;
1589 1488 : this->PushBranchFlowCharacteristics(
1590 744 : state, MixerBranchOut, state.dataLoopNodes->Node(FirstNodeOnBranchOut).MassFlowRate, FirstHVACIteration);
1591 744 : return;
1592 :
1593 : // IF INSUFFICIENT FLOW TO MEET ALL PARALLEL BRANCH FLOW REQUESTS
1594 : } else {
1595 :
1596 : // 1) apportion flow based on requested fraction of total
1597 67347 : for (int OutletNum = 1; OutletNum <= NumSplitOutlets; ++OutletNum) {
1598 :
1599 42931 : int SplitterBranchOut = this->Splitter.BranchNumOut(OutletNum);
1600 42931 : ThisBranchRequest = this->Branch(SplitterBranchOut).DetermineBranchFlowRequest(state);
1601 42931 : FirstNodeOnBranch = this->Branch(SplitterBranchOut).NodeNumIn;
1602 42931 : auto &this_splitter_outlet_branch(this->Branch(SplitterBranchOut));
1603 :
1604 42931 : if ((this_splitter_outlet_branch.controlType == DataBranchAirLoopPlant::ControlType::Active) ||
1605 16785 : (this_splitter_outlet_branch.controlType == DataBranchAirLoopPlant::ControlType::SeriesActive)) {
1606 :
1607 : // since we are calculating this fraction based on the total parallel request calculated above, we must mimic the logic to
1608 : // make sure the math works every time that means we must make the variable speed pump correction here as well.
1609 52292 : for (int CompCounter = 1; CompCounter <= this_splitter_outlet_branch.TotalComponents; ++CompCounter) {
1610 :
1611 26146 : auto const &this_comp(this_splitter_outlet_branch.Comp(CompCounter));
1612 :
1613 : // if this isn't a variable speed pump then just keep cycling
1614 26146 : if ((this_comp.Type != PlantEquipmentType::PumpVariableSpeed) &&
1615 26146 : (this_comp.Type != PlantEquipmentType::PumpBankVariableSpeed)) {
1616 26146 : continue;
1617 : }
1618 :
1619 0 : int CompInletNode = this_comp.NodeNumIn;
1620 0 : ThisBranchRequest = max(ThisBranchRequest, state.dataLoopNodes->Node(CompInletNode).MassFlowRateRequest);
1621 : }
1622 :
1623 26146 : ThisBranchRequestFrac = ThisBranchRequest / TotParallelBranchFlowReq;
1624 : // FracFlow = state.dataLoopNodes->Node(FirstNodeOnBranch)%MassFlowRate/TotParallelBranchFlowReq
1625 : // state.dataLoopNodes->Node(FirstNodeOnBranch)%MassFlowRate = MIN((FracFlow *
1626 : // state.dataLoopNodes->Node(FirstNodeOnBranch)%MassFlowRate),FlowRemaining)
1627 26146 : state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate = ThisBranchRequestFrac * ThisLoopSideFlow;
1628 52292 : this->PushBranchFlowCharacteristics(
1629 26146 : state, SplitterBranchOut, state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate, FirstHVACIteration);
1630 26146 : FlowRemaining -= state.dataLoopNodes->Node(FirstNodeOnBranch).MassFlowRate;
1631 : }
1632 : }
1633 :
1634 : // 1b) check if flow all apportioned
1635 24416 : if (FlowRemaining > DataBranchAirLoopPlant::MassFlowTolerance) {
1636 : // Call fatal diagnostic error. !The math should work out!
1637 0 : ShowSevereError(state, "ResolveParallelFlows: Dev note, failed to redistribute restricted flow");
1638 0 : ShowContinueErrorTimeStamp(state, "");
1639 0 : ShowContinueError(state, format("Loop side flow = {:.8R} (kg/s)", ThisLoopSideFlow));
1640 0 : ShowContinueError(state, format("Flow Remaining = {:.8R} (kg/s)", FlowRemaining));
1641 0 : ShowContinueError(state, format("Parallel Branch requests = {:.8R} (kg/s)", TotParallelBranchFlowReq));
1642 : }
1643 :
1644 : // 2) ! Reset the flow on the Mixer outlet branch
1645 24416 : MixerBranchOut = this->Mixer.BranchNumOut;
1646 24416 : FirstNodeOnBranchOut = this->Branch(MixerBranchOut).NodeNumIn;
1647 24416 : state.dataLoopNodes->Node(FirstNodeOnBranchOut).MassFlowRate = TotParallelBranchFlowReq;
1648 48832 : this->PushBranchFlowCharacteristics(
1649 24416 : state, MixerBranchOut, state.dataLoopNodes->Node(FirstNodeOnBranchOut).MassFlowRate, FirstHVACIteration);
1650 :
1651 : } // Total flow requested >= or < Total parallel request
1652 :
1653 : } // Splitter/Mixer exists
1654 : }
1655 :
1656 1058508 : void HalfLoopData::SimulateLoopSideBranchGroup(EnergyPlusData &state,
1657 : int const FirstBranchNum,
1658 : int const LastBranchNum,
1659 : Real64 FlowRequest,
1660 : bool const FirstHVACIteration,
1661 : bool &LoopShutDownFlag)
1662 : {
1663 :
1664 : // SUBROUTINE INFORMATION:
1665 : // AUTHOR Edwin Lee
1666 : // DATE WRITTEN July 2010
1667 : // MODIFIED na
1668 : // RE-ENGINEERED na
1669 :
1670 : // PURPOSE OF THIS SUBROUTINE:
1671 : // This routine will manage the component simulation on a single set of parallel branches
1672 : // This routine also reverts to a single branch simulation if there isn't a set of parallel branches
1673 :
1674 : // METHODOLOGY EMPLOYED:
1675 : // Loop through all components, and simulate first the non-load range based on each branch.
1676 : // When a load-range based (LRB) is encountered, the simulation moves to the next branch to do non-LRB components.
1677 : // When all paths are exhausted the simulation begins simulating LRB components. Before each comp, the load distribution
1678 : // engine is called to handle the load distribution for this current pass. If load is successfully distributed, this is
1679 : // flagged, and not called again. If load is not distributed (i.e. this component isn't ON right now), then the
1680 : // load distribution engine will be called again before the next component.
1681 : // After all load distribution is done and those components are complete, the simulation moves back to do any
1682 : // remaining components that may be downstream.
1683 :
1684 : //~ Flags
1685 : bool LoadDistributionWasPerformed;
1686 :
1687 : //~ General variables
1688 : Real64 LoadToLoopSetPoint;
1689 1058508 : PlantLocation PumpLocation;
1690 1058508 : LoadToLoopSetPoint = 0.0;
1691 :
1692 : // We now know what plant simulation region is available to us, let's simulate this group
1693 1058508 : bool EncounteredLRBObjDuringPass1(false);
1694 2665316 : for (int BranchCounter = FirstBranchNum; BranchCounter <= LastBranchNum; ++BranchCounter) {
1695 1606808 : auto &branch(this->Branch(BranchCounter));
1696 :
1697 : //~ Always start from the last component we did the last time around + 1 and
1698 : //~ try to make it all the way to the end of the loop
1699 1606808 : int const StartingComponent = branch.lastComponentSimulated + 1;
1700 1606808 : int const EndingComponent = branch.TotalComponents;
1701 3074248 : for (int CompCounter = StartingComponent; CompCounter <= EndingComponent; ++CompCounter) {
1702 :
1703 1606808 : auto &this_comp(branch.Comp(CompCounter));
1704 1606808 : PlantLocation this_plantLoc = {this->plantLoc.loopNum, this->plantLoc.loopSideNum, BranchCounter, CompCounter};
1705 1606808 : DataPlant::OpScheme const CurOpSchemeType(this_comp.CurOpSchemeType);
1706 :
1707 1606808 : switch (CurOpSchemeType) {
1708 0 : case DataPlant::OpScheme::WSEcon: //~ coils
1709 0 : this_comp.MyLoad = UpdatedDemandToLoopSetPoint;
1710 0 : branch.Comp(CompCounter).simulate(state, FirstHVACIteration);
1711 0 : break;
1712 176416 : case DataPlant::OpScheme::Pump: //~ pump
1713 176416 : if (this->BranchPumpsExist) {
1714 0 : SimulateSinglePump(state, this_comp.location, branch.RequestedMassFlow);
1715 : } else {
1716 176416 : SimulateSinglePump(state, this_comp.location, FlowRequest);
1717 : }
1718 176416 : break;
1719 11624 : case DataPlant::OpScheme::CompSetPtBased:
1720 11624 : PlantCondLoopOperation::ManagePlantLoadDistribution(state,
1721 : this_plantLoc,
1722 : LoadToLoopSetPoint,
1723 11624 : LoadToLoopSetPointThatWasntMet,
1724 : FirstHVACIteration,
1725 : LoopShutDownFlag,
1726 : LoadDistributionWasPerformed);
1727 11624 : branch.Comp(CompCounter).simulate(state, FirstHVACIteration);
1728 11624 : break;
1729 0 : case DataPlant::OpScheme::EMS:
1730 0 : if (this->plantLoc.loopSideNum == DataPlant::LoopSideLocation::Supply) {
1731 0 : int const curCompOpSchemePtr = this_comp.CurCompLevelOpNum;
1732 0 : int const OpSchemePtr = this_comp.OpScheme(curCompOpSchemePtr).OpSchemePtr;
1733 0 : state.dataPlnt->PlantLoop(this->plantLoc.loopNum).OpScheme(OpSchemePtr).EMSIntVarLoopDemandRate = InitialDemandToLoopSetPoint;
1734 : }
1735 0 : PlantCondLoopOperation::ManagePlantLoadDistribution(state,
1736 : this_plantLoc,
1737 : UpdatedDemandToLoopSetPoint,
1738 0 : LoadToLoopSetPointThatWasntMet,
1739 : FirstHVACIteration,
1740 : LoopShutDownFlag,
1741 : LoadDistributionWasPerformed);
1742 0 : branch.Comp(CompCounter).simulate(state, FirstHVACIteration);
1743 0 : break;
1744 139368 : case OpScheme::WetBulbRB:
1745 : case OpScheme::DryBulbRB:
1746 : case OpScheme::DewPointRB:
1747 : case OpScheme::RelHumRB:
1748 : case OpScheme::DryBulbTDB:
1749 : case OpScheme::WetBulbTDB:
1750 : case OpScheme::DewPointTDB:
1751 : case OpScheme::HeatingRB:
1752 : case OpScheme::CoolingRB: { //~ load range based
1753 139368 : EncounteredLRBObjDuringPass1 = true;
1754 139368 : goto components_end; // don't do any more components on this branch
1755 : }
1756 1279400 : default: // demand, etc.
1757 1279400 : branch.Comp(CompCounter).simulate(state, FirstHVACIteration);
1758 : }
1759 :
1760 : // Update loop demand as needed for changes this component may have made
1761 1467440 : this->UpdateAnyLoopDemandAlterations(state, BranchCounter, CompCounter);
1762 :
1763 : //~ If we didn't EXIT early, we must have simulated, so update array
1764 1467440 : branch.lastComponentSimulated = CompCounter;
1765 :
1766 : } //~ CompCounter
1767 1467440 : components_end:;
1768 :
1769 1606808 : if (this->FlowLock == DataPlant::FlowLock::Locked) {
1770 803404 : PlantPressureSystem::SimPressureDropSystem(
1771 : state, this->plantLoc.loopNum, FirstHVACIteration, DataPlant::PressureCall::Calc, this->plantLoc.loopSideNum, BranchCounter);
1772 : }
1773 :
1774 : } //~ BranchCounter
1775 :
1776 : // So now we have made one pass through all of the available components on these branches, skipping load based
1777 : // If we didn't encounter any load based objects during the first pass, then we must be done!
1778 1197876 : if (!EncounteredLRBObjDuringPass1) return;
1779 :
1780 : // If we have load based now, we should go ahead and distribute the load
1781 : // If not then this branch group is done, since flow path validation was previously done
1782 139368 : LoadToLoopSetPoint = UpdatedDemandToLoopSetPoint;
1783 139368 : LoadDistributionWasPerformed = false;
1784 :
1785 : // The way the load distribution is set up, I think I should call this for every load range based component
1786 : // encountered until distribution is actually performed. If we don't call for each component then we may
1787 : // call for a component that is not on the current equip list and then nothing would come on.
1788 139368 : bool EncounteredNonLBObjDuringPass2(false);
1789 411952 : for (int BranchCounter = FirstBranchNum; BranchCounter <= LastBranchNum; ++BranchCounter) {
1790 272584 : auto &branch(this->Branch(BranchCounter));
1791 :
1792 : //~ Always start from the last component we did the last time around + 1 and
1793 : //~ try to make it all the way to the end of the loop
1794 272584 : int const StartingComponent = branch.lastComponentSimulated + 1;
1795 272584 : int const EndingComponent = branch.TotalComponents;
1796 411952 : for (int CompCounter = StartingComponent; CompCounter <= EndingComponent; ++CompCounter) {
1797 139368 : PlantLocation this_plantLoc = {this->plantLoc.loopNum, this->plantLoc.loopSideNum, BranchCounter, CompCounter};
1798 :
1799 139368 : DataPlant::OpScheme const CurOpSchemeType(branch.Comp(CompCounter).CurOpSchemeType);
1800 :
1801 139368 : switch (CurOpSchemeType) {
1802 0 : case DataPlant::OpScheme::NoControl: //~ pipes, for example
1803 0 : branch.Comp(CompCounter).simulate(state, FirstHVACIteration);
1804 0 : break;
1805 0 : case DataPlant::OpScheme::Demand:
1806 : case DataPlant::OpScheme::CompSetPtBased:
1807 : case DataPlant::OpScheme::FreeRejection: //~ other control types
1808 0 : EncounteredNonLBObjDuringPass2 = true;
1809 0 : goto components2_end; // don't do anymore components on this branch
1810 0 : case DataPlant::OpScheme::Pump: //~ pump
1811 0 : PumpLocation.loopNum = this->plantLoc.loopNum;
1812 0 : PumpLocation.loopSideNum = this->plantLoc.loopSideNum;
1813 0 : PumpLocation.branchNum = BranchCounter;
1814 0 : PumpLocation.compNum = CompCounter;
1815 0 : if (this->BranchPumpsExist) {
1816 0 : SimulateSinglePump(state, PumpLocation, branch.RequestedMassFlow);
1817 : } else {
1818 0 : SimulateSinglePump(state, PumpLocation, FlowRequest);
1819 : }
1820 0 : break;
1821 139368 : case OpScheme::WetBulbRB:
1822 : case OpScheme::DryBulbRB:
1823 : case OpScheme::DewPointRB:
1824 : case OpScheme::RelHumRB:
1825 : case OpScheme::DryBulbTDB:
1826 : case OpScheme::WetBulbTDB:
1827 : case OpScheme::DewPointTDB:
1828 : case OpScheme::HeatingRB:
1829 : case OpScheme::CoolingRB: { //~ load range based
1830 139368 : if (!LoadDistributionWasPerformed) { //~ Still need to distribute load among load range based components
1831 139368 : PlantCondLoopOperation::ManagePlantLoadDistribution(state,
1832 : this_plantLoc,
1833 : LoadToLoopSetPoint,
1834 139368 : LoadToLoopSetPointThatWasntMet,
1835 : FirstHVACIteration,
1836 : LoopShutDownFlag,
1837 : LoadDistributionWasPerformed);
1838 : }
1839 139368 : branch.Comp(CompCounter).simulate(state, FirstHVACIteration);
1840 139368 : break;
1841 : }
1842 0 : default:
1843 0 : break;
1844 : }
1845 :
1846 : //~ If we didn't EXIT early, we must have simulated, so update array
1847 139368 : branch.lastComponentSimulated = CompCounter;
1848 :
1849 : } //~ CompCounter
1850 272584 : components2_end:;
1851 :
1852 : //~ If we are locked, go ahead and simulate the pressure components on this branch
1853 272584 : if (this->FlowLock == DataPlant::FlowLock::Locked) {
1854 136292 : PlantPressureSystem::SimPressureDropSystem(
1855 : state, this->plantLoc.loopNum, FirstHVACIteration, DataPlant::PressureCall::Calc, this->plantLoc.loopSideNum, BranchCounter);
1856 : }
1857 :
1858 : } //~ BranchCounter
1859 :
1860 : // So now we have made the load range based pass through all the components on each branch
1861 : // If we didn't see any other component types, then we are done, go away
1862 139368 : if (!EncounteredNonLBObjDuringPass2) return;
1863 :
1864 : // If we did encounter other objects than we just need to go back through and simulate them
1865 0 : for (int BranchCounter = FirstBranchNum; BranchCounter <= LastBranchNum; ++BranchCounter) {
1866 0 : auto &branch(this->Branch(BranchCounter));
1867 :
1868 : //~ Always start from the last component we did the last time around + 1 and
1869 : //~ try to make it all the way to the end of the loop
1870 0 : int const StartingComponent = branch.lastComponentSimulated + 1;
1871 0 : int const EndingComponent = branch.TotalComponents;
1872 0 : for (int CompCounter = StartingComponent; CompCounter <= EndingComponent; ++CompCounter) {
1873 :
1874 0 : DataPlant::OpScheme const CurOpSchemeType(branch.Comp(CompCounter).CurOpSchemeType);
1875 :
1876 0 : switch (CurOpSchemeType) {
1877 0 : case DataPlant::OpScheme::Demand: //~ coils
1878 0 : branch.Comp(CompCounter).simulate(state, FirstHVACIteration);
1879 0 : break;
1880 0 : case DataPlant::OpScheme::Pump: //~ pump
1881 0 : PumpLocation.loopNum = this->plantLoc.loopNum;
1882 0 : PumpLocation.loopSideNum = this->plantLoc.loopSideNum;
1883 0 : PumpLocation.branchNum = BranchCounter;
1884 0 : PumpLocation.compNum = CompCounter;
1885 0 : if (this->BranchPumpsExist) {
1886 0 : SimulateSinglePump(state, PumpLocation, branch.RequestedMassFlow);
1887 : } else {
1888 0 : SimulateSinglePump(state, PumpLocation, FlowRequest);
1889 : }
1890 0 : break;
1891 0 : case OpScheme::HeatingRB:
1892 : case OpScheme::CoolingRB: { //~ load range based
1893 0 : ShowFatalError(state, "Encountered Load Based Object after other components, invalid.");
1894 0 : break;
1895 : }
1896 0 : default:
1897 : //~ Typical control equipment
1898 0 : branch.Comp(CompCounter).simulate(state, FirstHVACIteration);
1899 : }
1900 :
1901 : //~ If we didn't EXIT early, we must have simulated, so update array
1902 0 : branch.lastComponentSimulated = CompCounter;
1903 :
1904 : } //~ CompCounter
1905 :
1906 0 : if (this->FlowLock == DataPlant::FlowLock::Locked) {
1907 0 : PlantPressureSystem::SimPressureDropSystem(
1908 : state, this->plantLoc.loopNum, FirstHVACIteration, DataPlant::PressureCall::Calc, this->plantLoc.loopSideNum, BranchCounter);
1909 : }
1910 :
1911 : } //~ BranchCounter
1912 :
1913 : // I suppose I could do a check on the last component simulated to make sure we actually exhausted all branches
1914 : // This would be the "THIRD" check on flow validation, but would be OK
1915 : }
1916 :
1917 1467440 : void HalfLoopData::UpdateAnyLoopDemandAlterations(EnergyPlusData &state, int const BranchNum, int const CompNum)
1918 : {
1919 :
1920 : // SUBROUTINE INFORMATION:
1921 : // AUTHOR Edwin Lee
1922 : // DATE WRITTEN August 2010
1923 : // MODIFIED na
1924 : // RE-ENGINEERED na
1925 :
1926 : // PURPOSE OF THIS SUBROUTINE:
1927 : // This routine will analyze the given component and determine if any
1928 : // alterations need to be made to the current loop demand value. If so,
1929 : // it will make the changes to the module level loop demand variables.
1930 :
1931 : // METHODOLOGY EMPLOYED:
1932 : // Components will always supply a useful delta T, even if it happens to be zero
1933 : // For flow rate, make decisions based on the component's current operating scheme type:
1934 : // Demand based: these components will have a flow request on their inlet node
1935 : // Pump: these components will not be included, as they no longer include heat at the pump
1936 : // component setpoint: these components will have a flow request
1937 :
1938 : // on their outlet node corresponding to their calculated delta T
1939 : // load range based: these components do not 'alter' the load, they reject the load
1940 : // Therefore they are not included
1941 :
1942 : // SUBROUTINE PARAMETER DEFINITIONS:
1943 : static constexpr std::string_view RoutineName("PlantLoopSolver::UpdateAnyLoopDemandAlterations");
1944 :
1945 : // Init to zero, so that if we don't find anything, we exit early
1946 1467440 : Real64 ComponentMassFlowRate(0.0);
1947 :
1948 1467440 : auto const &this_comp(this->Branch(BranchNum).Comp(CompNum));
1949 :
1950 : // Get information
1951 1467440 : int const InletNode(this_comp.NodeNumIn);
1952 1467440 : int const OutletNode(this_comp.NodeNumOut);
1953 :
1954 1467440 : if (this->FlowLock == DataPlant::FlowLock::Unlocked) {
1955 :
1956 733720 : switch (this_comp.CurOpSchemeType) {
1957 0 : case OpScheme::HeatingRB:
1958 : case OpScheme::CoolingRB: { //~ load range based
1959 0 : break; // Don't do anything for load based components
1960 : }
1961 :
1962 733720 : default: {
1963 : // pumps pipes, etc. will be lumped in here with other component types, but they will have no delta T anyway
1964 733720 : ComponentMassFlowRate = state.dataLoopNodes->Node(InletNode).MassFlowRateRequest;
1965 : // make sure components like economizers use the mass flow request
1966 733720 : break;
1967 : }
1968 : }
1969 :
1970 733720 : } else if (this->FlowLock == DataPlant::FlowLock::Locked) {
1971 :
1972 : // For locked flow just use the mass flow rate
1973 :
1974 733720 : switch (this_comp.CurOpSchemeType) {
1975 0 : case OpScheme::HeatingRB:
1976 : case OpScheme::CoolingRB: { //~ load range based
1977 0 : break; // Don't do anything for load based components
1978 : }
1979 733720 : default: {
1980 : // pumps pipes, etc. will be lumped in here with other component types, but they will have no delta T anyway
1981 733720 : ComponentMassFlowRate = state.dataLoopNodes->Node(OutletNode).MassFlowRate;
1982 : }
1983 : }
1984 :
1985 : } else { // flow pump query? problem?
1986 : }
1987 :
1988 : // Leave early if there wasn't a mass flow rate or request
1989 1467440 : if (ComponentMassFlowRate < DataBranchAirLoopPlant::MassFlowTolerance) return;
1990 :
1991 : // Get an average temperature for the property call
1992 450587 : Real64 const InletTemp(state.dataLoopNodes->Node(InletNode).Temp);
1993 450587 : Real64 const OutletTemp(state.dataLoopNodes->Node(OutletNode).Temp);
1994 450587 : Real64 const AverageTemp((InletTemp + OutletTemp) / 2.0);
1995 450587 : Real64 const ComponentCp(state.dataPlnt->PlantLoop(this->plantLoc.loopNum).glycol->getSpecificHeat(state, AverageTemp, RoutineName));
1996 :
1997 : // Calculate the load altered by this component
1998 450587 : Real64 const LoadAlteration(ComponentMassFlowRate * ComponentCp * (OutletTemp - InletTemp));
1999 :
2000 : // Now alter the module level variables
2001 450587 : this->CurrentAlterationsToDemand += LoadAlteration;
2002 450587 : this->UpdatedDemandToLoopSetPoint = this->InitialDemandToLoopSetPoint - this->CurrentAlterationsToDemand;
2003 : }
2004 :
2005 176416 : void HalfLoopData::SimulateSinglePump(EnergyPlusData &state, PlantLocation const SpecificPumpLocation, Real64 &SpecificPumpFlowRate)
2006 : {
2007 :
2008 : // SUBROUTINE INFORMATION:
2009 : // AUTHOR Edwin Lee
2010 : // DATE WRITTEN July 2010
2011 : // MODIFIED na
2012 : // RE-ENGINEERED na
2013 :
2014 176416 : auto &loop(state.dataPlnt->PlantLoop(SpecificPumpLocation.loopNum));
2015 176416 : auto &loop_side(loop.LoopSide(SpecificPumpLocation.loopSideNum));
2016 176416 : auto &loop_side_branch(loop_side.Branch(SpecificPumpLocation.branchNum));
2017 176416 : auto &comp(loop_side_branch.Comp(SpecificPumpLocation.compNum));
2018 176416 : int const PumpIndex = comp.IndexInLoopSidePumps;
2019 176416 : auto &pump(loop_side.Pumps(PumpIndex));
2020 :
2021 176416 : this->AdjustPumpFlowRequestByEMSControls(SpecificPumpLocation.branchNum, SpecificPumpLocation.compNum, SpecificPumpFlowRate);
2022 :
2023 : // Call SimPumps, routine takes a flow request, and returns some info about the status of the pump
2024 : bool DummyThisPumpRunning;
2025 176416 : Pumps::SimPumps(state,
2026 176416 : pump.PumpName,
2027 176416 : SpecificPumpLocation.loopNum,
2028 : SpecificPumpFlowRate,
2029 : DummyThisPumpRunning,
2030 176416 : loop_side_branch.PumpIndex,
2031 176416 : pump.PumpHeatToFluid);
2032 :
2033 : //~ Pull some state information from the pump outlet node
2034 176416 : pump.CurrentMinAvail = state.dataLoopNodes->Node(pump.PumpOutletNode).MassFlowRateMinAvail;
2035 176416 : pump.CurrentMaxAvail = state.dataLoopNodes->Node(pump.PumpOutletNode).MassFlowRateMaxAvail;
2036 :
2037 : //~ Update the LoopSide pump heat totality here
2038 176416 : if (loop_side.TotalPumps > 0) {
2039 176416 : loop_side.TotalPumpHeat = sum(loop_side.Pumps, &DataPlant::LoopSidePumpInformation::PumpHeatToFluid);
2040 : }
2041 176416 : }
2042 :
2043 88278 : void HalfLoopData::SimulateAllLoopSidePumps(EnergyPlusData &state,
2044 : ObjexxFCL::Optional<PlantLocation const> SpecificPumpLocation,
2045 : ObjexxFCL::Optional<Real64 const> SpecificPumpFlowRate)
2046 : {
2047 :
2048 : // SUBROUTINE INFORMATION:
2049 : // AUTHOR Edwin Lee
2050 : // DATE WRITTEN July 2010
2051 : // MODIFIED na
2052 : // RE-ENGINEERED na
2053 :
2054 : int PumpIndexStart;
2055 : int PumpIndexEnd;
2056 : int PumpLoopNum;
2057 : DataPlant::LoopSideLocation PumpLoopSideNum;
2058 :
2059 : // If we have a specific loop/side/br/comp, then find the index and only do that one, otherwise do all pumps on the loop side
2060 88278 : if (present(SpecificPumpLocation)) {
2061 0 : PumpLoopNum = SpecificPumpLocation().loopNum;
2062 0 : PumpLoopSideNum = SpecificPumpLocation().loopSideNum;
2063 0 : int const PumpBranchNum = SpecificPumpLocation().branchNum;
2064 0 : int const PumpCompNum = SpecificPumpLocation().compNum;
2065 0 : PumpIndexStart =
2066 0 : state.dataPlnt->PlantLoop(PumpLoopNum).LoopSide(PumpLoopSideNum).Branch(PumpBranchNum).Comp(PumpCompNum).IndexInLoopSidePumps;
2067 0 : PumpIndexEnd = PumpIndexStart;
2068 : } else {
2069 88278 : PumpLoopNum = this->plantLoc.loopNum;
2070 88278 : PumpLoopSideNum = this->plantLoc.loopSideNum;
2071 88278 : PumpIndexStart = 1;
2072 88278 : PumpIndexEnd = this->TotalPumps;
2073 : }
2074 :
2075 : // If we have a flow rate to hit, then go for it, otherwise, just operate in request mode with zero flow
2076 : Real64 FlowToRequest;
2077 88278 : if (present(SpecificPumpFlowRate)) {
2078 0 : FlowToRequest = SpecificPumpFlowRate;
2079 : } else {
2080 88278 : FlowToRequest = 0.0;
2081 : }
2082 :
2083 : //~ Now loop through all the pumps and simulate them, keeping track of their status
2084 88278 : auto &loop_side(state.dataPlnt->PlantLoop(PumpLoopNum).LoopSide(PumpLoopSideNum));
2085 88278 : auto &loop_side_branch(loop_side.Branch);
2086 176521 : for (int PumpCounter = PumpIndexStart; PumpCounter <= PumpIndexEnd; ++PumpCounter) {
2087 :
2088 : //~ Set some variables
2089 88243 : auto &pump(loop_side.Pumps(PumpCounter));
2090 88243 : int const PumpBranchNum = pump.BranchNum;
2091 88243 : int const PumpCompNum = pump.CompNum;
2092 88243 : int const PumpOutletNode = pump.PumpOutletNode;
2093 :
2094 88243 : this->AdjustPumpFlowRequestByEMSControls(PumpBranchNum, PumpCompNum, FlowToRequest);
2095 :
2096 : // Call SimPumps, routine takes a flow request, and returns some info about the status of the pump
2097 : bool DummyThisPumpRunning;
2098 88243 : Pumps::SimPumps(state,
2099 88243 : pump.PumpName,
2100 : PumpLoopNum,
2101 : FlowToRequest,
2102 : DummyThisPumpRunning,
2103 88243 : loop_side_branch(PumpBranchNum).PumpIndex,
2104 88243 : pump.PumpHeatToFluid);
2105 :
2106 : //~ Pull some state information from the pump outlet node
2107 88243 : Real64 const ThisPumpMinAvail = state.dataLoopNodes->Node(PumpOutletNode).MassFlowRateMinAvail;
2108 88243 : Real64 const ThisPumpMaxAvail = state.dataLoopNodes->Node(PumpOutletNode).MassFlowRateMaxAvail;
2109 :
2110 : //~ Now update the data structure
2111 88243 : pump.CurrentMinAvail = ThisPumpMinAvail;
2112 88243 : pump.CurrentMaxAvail = ThisPumpMaxAvail;
2113 : }
2114 :
2115 : //~ Update the LoopSide pump heat totality here
2116 88278 : if (loop_side.TotalPumps > 0) {
2117 88243 : loop_side.TotalPumpHeat = sum(loop_side.Pumps, &DataPlant::LoopSidePumpInformation::PumpHeatToFluid);
2118 : }
2119 88278 : }
2120 :
2121 176418 : Real64 HalfLoopData::DetermineLoopSideFlowRate(EnergyPlusData &state, int ThisSideInletNode, Real64 ThisSideLoopFlowRequest)
2122 : {
2123 176418 : Real64 ThisLoopSideFlow = ThisSideLoopFlowRequest;
2124 176418 : Real64 TotalPumpMinAvailFlow = 0.0;
2125 176418 : Real64 TotalPumpMaxAvailFlow = 0.0;
2126 176418 : if (allocated(this->Pumps)) {
2127 :
2128 : //~ Initialize pump values
2129 176416 : for (auto &e : this->Pumps) {
2130 88208 : e.CurrentMinAvail = 0.0;
2131 88208 : e.CurrentMaxAvail = 0.0;
2132 : }
2133 88208 : this->FlowLock = DataPlant::FlowLock::PumpQuery;
2134 :
2135 : //~ Simulate pumps
2136 88208 : this->SimulateAllLoopSidePumps(state);
2137 :
2138 : //~ Calculate totals
2139 176416 : for (auto const &e : this->Pumps) {
2140 88208 : TotalPumpMinAvailFlow += e.CurrentMinAvail;
2141 88208 : TotalPumpMaxAvailFlow += e.CurrentMaxAvail;
2142 : }
2143 :
2144 : // Use the pump min/max avail to attempt to constrain the loop side flow
2145 88208 : ThisLoopSideFlow = PlantUtilities::BoundValueToWithinTwoValues(ThisLoopSideFlow, TotalPumpMinAvailFlow, TotalPumpMaxAvailFlow);
2146 : }
2147 :
2148 : // Now we check flow restriction from the other side, both min and max avail.
2149 : // Doing this last basically means it wins, so the pump should pull down to meet the flow restriction
2150 176418 : ThisLoopSideFlow = PlantUtilities::BoundValueToNodeMinMaxAvail(state, ThisLoopSideFlow, ThisSideInletNode);
2151 :
2152 : // Final preparation of loop inlet min/max avail if pumps exist
2153 176418 : if (allocated(this->Pumps)) {
2154 : // At this point, the pump limits should have been obeyed unless a flow restriction was encountered from the other side
2155 : // The pump may, however, have even tighter constraints than the other side
2156 : // At this point, the inlet node doesn't know anything about those limits
2157 : // Since we have already honored the other side flow restriction, try to honor the pump limits here
2158 88208 : PlantUtilities::TightenNodeMinMaxAvails(state, ThisSideInletNode, TotalPumpMinAvailFlow, TotalPumpMaxAvailFlow);
2159 : }
2160 :
2161 : // Now reset the entering mass flow rate to the decided-upon flow rate
2162 176418 : state.dataLoopNodes->Node(ThisSideInletNode).MassFlowRate = ThisLoopSideFlow;
2163 176418 : return ThisLoopSideFlow;
2164 : }
2165 :
2166 352836 : void HalfLoopData::UpdatePlantMixer(EnergyPlusData &state)
2167 : {
2168 :
2169 : // SUBROUTINE INFORMATION:
2170 : // AUTHOR Brandon Anderson, Dan Fisher
2171 : // DATE WRITTEN October 1999
2172 : // MODIFIED na
2173 : // RE-ENGINEERED na
2174 :
2175 : // PURPOSE OF THIS SUBROUTINE:
2176 : // calculate the outlet conditions at the mixer
2177 : // this is expected to only be called for loops with a mixer
2178 :
2179 : // Find mixer outlet node number
2180 352836 : int const MixerOutletNode = this->Mixer.NodeNumOut;
2181 :
2182 : // Find corresponding splitter inlet node number--correspondence, but currently
2183 : // hard code things to a single split/mix setting it to the mixer number
2184 352836 : int const SplitterInNode = this->Splitter.NodeNumIn;
2185 : // Initialize Mixer outlet temp and mass flow rate
2186 352836 : Real64 MixerOutletTemp = 0.0;
2187 352836 : Real64 MixerOutletMassFlow = 0.0;
2188 352836 : Real64 MixerOutletMassFlowMaxAvail = 0.0;
2189 352836 : Real64 MixerOutletMassFlowMinAvail = 0.0;
2190 352836 : Real64 MixerOutletPress = 0.0;
2191 352836 : Real64 MixerOutletQuality = 0.0;
2192 :
2193 : // Calculate Mixer outlet mass flow rate
2194 1238948 : for (int InletNodeNum = 1; InletNodeNum <= this->Mixer.TotalInletNodes; ++InletNodeNum) {
2195 886112 : int const MixerInletNode = this->Mixer.NodeNumIn(InletNodeNum);
2196 886112 : MixerOutletMassFlow += state.dataLoopNodes->Node(MixerInletNode).MassFlowRate;
2197 : }
2198 :
2199 : // Calculate Mixer outlet temperature
2200 829734 : for (int InletNodeNum = 1; InletNodeNum <= this->Mixer.TotalInletNodes; ++InletNodeNum) {
2201 638800 : int const MixerInletNode = this->Mixer.NodeNumIn(InletNodeNum);
2202 638800 : if (MixerOutletMassFlow > 0.0) {
2203 476898 : Real64 const MixerInletMassFlow = state.dataLoopNodes->Node(MixerInletNode).MassFlowRate;
2204 476898 : Real64 const MassFrac = MixerInletMassFlow / MixerOutletMassFlow;
2205 : // mass flow weighted temp and enthalpy for each mixer inlet
2206 476898 : MixerOutletTemp += MassFrac * state.dataLoopNodes->Node(MixerInletNode).Temp;
2207 476898 : MixerOutletQuality += MassFrac * state.dataLoopNodes->Node(MixerInletNode).Quality;
2208 476898 : MixerOutletMassFlowMaxAvail += state.dataLoopNodes->Node(MixerInletNode).MassFlowRateMaxAvail;
2209 476898 : MixerOutletMassFlowMinAvail += state.dataLoopNodes->Node(MixerInletNode).MassFlowRateMinAvail;
2210 476898 : MixerOutletPress = max(MixerOutletPress, state.dataLoopNodes->Node(MixerInletNode).Press);
2211 : } else { // MixerOutletMassFlow <=0, then perform the 'no flow' update.
2212 161902 : MixerOutletTemp = state.dataLoopNodes->Node(SplitterInNode).Temp;
2213 161902 : MixerOutletQuality = state.dataLoopNodes->Node(SplitterInNode).Quality;
2214 161902 : MixerOutletMassFlowMaxAvail = state.dataLoopNodes->Node(SplitterInNode).MassFlowRateMaxAvail;
2215 161902 : MixerOutletMassFlowMinAvail = state.dataLoopNodes->Node(SplitterInNode).MassFlowRateMinAvail;
2216 161902 : MixerOutletPress = state.dataLoopNodes->Node(SplitterInNode).Press;
2217 161902 : break;
2218 : }
2219 : }
2220 :
2221 352836 : state.dataLoopNodes->Node(MixerOutletNode).MassFlowRate = MixerOutletMassFlow;
2222 352836 : state.dataLoopNodes->Node(MixerOutletNode).Temp = MixerOutletTemp;
2223 352836 : if (state.dataPlnt->PlantLoop(this->plantLoc.loopNum).HasPressureComponents) {
2224 : // Don't update pressure, let pressure system handle this...
2225 : } else {
2226 : // Go ahead and update!
2227 352836 : state.dataLoopNodes->Node(MixerOutletNode).Press = MixerOutletPress;
2228 : }
2229 352836 : state.dataLoopNodes->Node(MixerOutletNode).Quality = MixerOutletQuality;
2230 :
2231 : // set max/min avails on mixer outlet to be consistent with the following rules
2232 : // 1. limited by the max/min avails on splitter inlet
2233 : // 2. limited by the sum of max/min avails for each branch's mixer inlet node
2234 :
2235 352836 : state.dataLoopNodes->Node(MixerOutletNode).MassFlowRateMaxAvail =
2236 352836 : min(MixerOutletMassFlowMaxAvail, state.dataLoopNodes->Node(SplitterInNode).MassFlowRateMaxAvail);
2237 352836 : state.dataLoopNodes->Node(MixerOutletNode).MassFlowRateMinAvail =
2238 352836 : max(MixerOutletMassFlowMinAvail, state.dataLoopNodes->Node(SplitterInNode).MassFlowRateMinAvail);
2239 352836 : }
2240 :
2241 352836 : void HalfLoopData::UpdatePlantSplitter(EnergyPlusData &state)
2242 : {
2243 :
2244 : // SUBROUTINE INFORMATION:
2245 : // AUTHOR Brandon Anderson, Dan Fisher
2246 : // DATE WRITTEN October 1999
2247 : // MODIFIED na
2248 : // RE-ENGINEERED na
2249 :
2250 : // PURPOSE OF THIS SUBROUTINE:
2251 : // Set the outlet conditions of the splitter
2252 :
2253 : // Update Temperatures across splitter
2254 352836 : if (this->Splitter.Exists) {
2255 :
2256 : // Set branch number at splitter inlet
2257 352836 : int const SplitterInletNode = this->Splitter.NodeNumIn;
2258 :
2259 : // Loop over outlet nodes
2260 1238948 : for (int CurNode = 1; CurNode <= this->Splitter.TotalOutletNodes; ++CurNode) {
2261 886112 : int const SplitterOutletNode = this->Splitter.NodeNumOut(CurNode);
2262 :
2263 : // Inlet Temp equals exit Temp to all outlet branches
2264 886112 : state.dataLoopNodes->Node(SplitterOutletNode).Temp = state.dataLoopNodes->Node(SplitterInletNode).Temp;
2265 886112 : state.dataLoopNodes->Node(SplitterOutletNode).TempMin = state.dataLoopNodes->Node(SplitterInletNode).TempMin;
2266 886112 : state.dataLoopNodes->Node(SplitterOutletNode).TempMax = state.dataLoopNodes->Node(SplitterInletNode).TempMax;
2267 886112 : if (state.dataPlnt->PlantLoop(this->plantLoc.loopNum).HasPressureComponents) {
2268 : // Don't update pressure, let pressure system handle this...
2269 : } else {
2270 : // Go ahead and update!
2271 886112 : state.dataLoopNodes->Node(SplitterOutletNode).Press = state.dataLoopNodes->Node(SplitterInletNode).Press;
2272 : }
2273 886112 : state.dataLoopNodes->Node(SplitterOutletNode).Quality = state.dataLoopNodes->Node(SplitterInletNode).Quality;
2274 :
2275 : // These two blocks and the following one which I added need to be cleaned up
2276 : // I think we will always pass maxavail down the splitter, min avail is the issue.
2277 : // Changed to include hardware max in next line
2278 886112 : state.dataLoopNodes->Node(SplitterOutletNode).MassFlowRateMaxAvail = min(
2279 886112 : state.dataLoopNodes->Node(SplitterInletNode).MassFlowRateMaxAvail, state.dataLoopNodes->Node(SplitterOutletNode).MassFlowRateMax);
2280 886112 : state.dataLoopNodes->Node(SplitterOutletNode).MassFlowRateMinAvail = 0.0;
2281 :
2282 : // Not sure about passing min avail if it is nonzero. I am testing a pump with nonzero
2283 : // min flow rate, and it is causing problems because this routine passes zero down. Perhaps if
2284 : // it is a single parallel branch, we are safe to assume we need to just pass it down.
2285 : // But need to test for multiple branches (or at least think about it), to see what we need to do...
2286 886112 : if (this->Splitter.TotalOutletNodes == 1) {
2287 56048 : state.dataLoopNodes->Node(SplitterOutletNode).MassFlowRateMinAvail =
2288 56048 : state.dataLoopNodes->Node(SplitterInletNode).MassFlowRateMinAvail;
2289 : }
2290 : }
2291 : }
2292 352836 : }
2293 :
2294 : } // namespace DataPlant
2295 : } // namespace EnergyPlus
|