Line data Source code
1 : // EnergyPlus, Copyright (c) 1996-2025, The Board of Trustees of the University of Illinois,
2 : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
3 : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
4 : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
5 : // contributors. All rights reserved.
6 : //
7 : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
8 : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
9 : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
10 : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
11 : // derivative works, and perform publicly and display publicly, and to permit others to do so.
12 : //
13 : // Redistribution and use in source and binary forms, with or without modification, are permitted
14 : // provided that the following conditions are met:
15 : //
16 : // (1) Redistributions of source code must retain the above copyright notice, this list of
17 : // conditions and the following disclaimer.
18 : //
19 : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
20 : // conditions and the following disclaimer in the documentation and/or other materials
21 : // provided with the distribution.
22 : //
23 : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
24 : // the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
25 : // used to endorse or promote products derived from this software without specific prior
26 : // written permission.
27 : //
28 : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
29 : // without changes from the version obtained under this License, or (ii) Licensee makes a
30 : // reference solely to the software portion of its product, Licensee must refer to the
31 : // software as "EnergyPlus version X" software, where "X" is the version number Licensee
32 : // obtained under this License and may not use a different name for the software. Except as
33 : // specifically required in this Section (4), Licensee shall not use in a company name, a
34 : // product name, in advertising, publicity, or other promotional activities any name, trade
35 : // name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
36 : // similar designation, without the U.S. Department of Energy's prior written consent.
37 : //
38 : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
39 : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
40 : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
41 : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
43 : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44 : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
45 : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46 : // POSSIBILITY OF SUCH DAMAGE.
47 :
48 : // C++ Headers
49 : #include <cassert>
50 : #include <cmath>
51 : #include <cstdlib>
52 :
53 : // ObjexxFCL Headers
54 : #include <ObjexxFCL/string.functions.hh>
55 :
56 : // EnergyPlus Headers
57 : #include <EnergyPlus/Data/EnergyPlusData.hh>
58 : #include <EnergyPlus/DataHVACGlobals.hh>
59 : #include <EnergyPlus/DataLoopNode.hh>
60 : #include <EnergyPlus/General.hh>
61 : #include <EnergyPlus/PlantUtilities.hh>
62 : #include <EnergyPlus/Psychrometrics.hh>
63 : #include <EnergyPlus/SZVAVModel.hh>
64 : #include <EnergyPlus/UnitarySystem.hh>
65 : #include <EnergyPlus/UtilityRoutines.hh>
66 :
67 : namespace EnergyPlus {
68 :
69 : namespace SZVAVModel {
70 :
71 : // Module containing routines for general use
72 :
73 : // Data
74 : // This module should not contain variables in the module sense as it is
75 : // intended strictly to provide "interfaces" to routines used by other
76 : // parts of the simulation.
77 :
78 : // MODULE PARAMETER DEFINITIONS
79 :
80 : // Functions
81 :
82 11 : void calcSZVAVModel(EnergyPlusData &state,
83 : FanCoilUnits::FanCoilData &SZVAVModel,
84 : int const SysIndex,
85 : bool const FirstHVACIteration,
86 : bool const CoolingLoad,
87 : bool const HeatingLoad,
88 : Real64 const ZoneLoad,
89 : [[maybe_unused]] Real64 &OnOffAirFlowRatio,
90 : [[maybe_unused]] bool const HXUnitOn,
91 : [[maybe_unused]] int const AirLoopNum,
92 : Real64 &PartLoadRatio,
93 : [[maybe_unused]] HVAC::CompressorOp const CompressorONFlag)
94 : {
95 :
96 11 : int constexpr MaxIter(100); // maximum number of iterations
97 11 : int SolFlag(0); // return flag from RegulaFalsi for sensible load
98 11 : std::string MessagePrefix; // label for warning reporting
99 :
100 11 : Real64 lowBoundaryLoad(0.0);
101 11 : Real64 highBoundaryLoad(0.0);
102 11 : Real64 minHumRat(0.0);
103 11 : Real64 outletTemp(0.0);
104 11 : bool coilActive(false);
105 11 : Real64 AirMassFlow(0.0);
106 :
107 11 : Real64 maxCoilFluidFlow(0.0);
108 11 : Real64 maxOutletTemp(0.0);
109 11 : Real64 minAirMassFlow(0.0);
110 11 : Real64 maxAirMassFlow(0.0);
111 11 : Real64 lowSpeedFanRatio(0.0);
112 11 : int coilFluidInletNode(0);
113 11 : int coilFluidOutletNode(0);
114 11 : PlantLocation coilPlantLoc{};
115 11 : int coilAirInletNode(0);
116 11 : int coilAirOutletNode(0);
117 :
118 : Real64 TempSensOutput; // iterative sensible capacity [W]
119 : // Real64 TempLatOutput; // iterative latent capacity [W]
120 :
121 : // set up mode specific variables to use in common function calls
122 11 : if (CoolingLoad) {
123 4 : maxCoilFluidFlow = SZVAVModel.MaxCoolCoilFluidFlow;
124 4 : maxOutletTemp = SZVAVModel.DesignMinOutletTemp;
125 4 : minAirMassFlow = SZVAVModel.MaxNoCoolHeatAirMassFlow;
126 4 : maxAirMassFlow = SZVAVModel.MaxCoolAirMassFlow;
127 4 : lowSpeedFanRatio = SZVAVModel.LowSpeedCoolFanRatio;
128 4 : coilFluidInletNode = SZVAVModel.CoolCoilFluidInletNode;
129 4 : coilFluidOutletNode = SZVAVModel.CoolCoilFluidOutletNodeNum;
130 4 : coilPlantLoc = SZVAVModel.CoolCoilPlantLoc;
131 4 : coilAirInletNode = SZVAVModel.CoolCoilInletNodeNum;
132 4 : coilAirOutletNode = SZVAVModel.CoolCoilOutletNodeNum;
133 7 : } else if (HeatingLoad) {
134 7 : maxCoilFluidFlow = SZVAVModel.MaxHeatCoilFluidFlow;
135 7 : maxOutletTemp = SZVAVModel.DesignMaxOutletTemp;
136 7 : minAirMassFlow = SZVAVModel.MaxNoCoolHeatAirMassFlow;
137 7 : maxAirMassFlow = SZVAVModel.MaxHeatAirMassFlow;
138 7 : lowSpeedFanRatio = SZVAVModel.LowSpeedHeatFanRatio;
139 7 : coilFluidInletNode = SZVAVModel.HeatCoilFluidInletNode;
140 7 : coilFluidOutletNode = SZVAVModel.HeatCoilFluidOutletNodeNum;
141 7 : coilPlantLoc = SZVAVModel.HeatCoilPlantLoc;
142 7 : coilAirInletNode = SZVAVModel.HeatCoilInletNodeNum;
143 7 : coilAirOutletNode = SZVAVModel.HeatCoilOutletNodeNum;
144 : } else { // should never get here, protect against uninitialized variables
145 0 : maxCoilFluidFlow = 0.0;
146 0 : maxOutletTemp = 0.0;
147 0 : minAirMassFlow = 0.0;
148 0 : maxAirMassFlow = 0.0;
149 0 : lowSpeedFanRatio = 0.0;
150 0 : coilFluidInletNode = 0;
151 0 : coilFluidOutletNode = 0;
152 0 : coilPlantLoc = {0, DataPlant::LoopSideLocation::Invalid, 0, 0};
153 0 : coilAirInletNode = 0;
154 0 : coilAirOutletNode = 0;
155 : }
156 11 : int InletNode = SZVAVModel.AirInNode;
157 11 : Real64 InletTemp = state.dataLoopNodes->Node(InletNode).Temp;
158 11 : int OutletNode = SZVAVModel.AirOutNode;
159 11 : Real64 ZoneTemp = state.dataLoopNodes->Node(SZVAVModel.NodeNumOfControlledZone).Temp;
160 11 : Real64 ZoneHumRat = state.dataLoopNodes->Node(SZVAVModel.NodeNumOfControlledZone).HumRat;
161 : // initialize flow variables to 0
162 11 : Real64 lowWaterMdot = 0.0;
163 : // Real64 SupHeaterLoad = 0.0;
164 :
165 : // model attempts to control air flow rate and coil capacity in specific operating regions:
166 : // Region 1 (R1) - minimum air flow rate at modulated coil capacity (up to min/max temperature limits)
167 : // Region 2 (R2) - modulated air flow rate and coil capacity (up to max air flow rate while maintaining min/max temperature limits)
168 : // Region 3 (R3) - maximum air flow rate and modulated/increased coil capacity (allow increased capacity at full air flow rate to meet
169 : // remaining load)
170 : //
171 : // | | | | ^ ^ = supply air temperature
172 : // | | | | ^ * = supply air flow rate
173 : // | | |^^^^| <--- maximum supply air temperature
174 : // | | ^ | |
175 : // | | ^ | |
176 : // ***********| | ^ | |************** <-- max unit air flow rate
177 : // |* | ^ | *|
178 : // | * | ^ | * |
179 : // | * | ^ | * |
180 : // | *| ^ |* |
181 : // | |*******************| | <-- min unit air flow rate
182 : // R3 | R2 | ^ R1 | R2 | R3
183 : // min SAT --> |^^^^| | |
184 : // ^
185 : // ^ |
186 : // (-) increasing cooling load < 0 > increasing heating load (+)
187 : //
188 : // Notes: SAT is allowed to decrease/increase once air flow rate is max'ed out otherwise load would not be met
189 : // Load here is not the zone load, it's the load the system must meet to meet the Tstat set point (i.e., OA can alter required
190 : // capacity) lowSpeedFanRatio = min/max unit air flow rate
191 : //
192 : // Step 1: calculate load at Region 1 lower (cooling) or upper (heating) boundary at minimum air flow rate
193 : // - if load can be met, test maximum output (PLR = 1) before calling RootSolver
194 : // - if maximum capacity is greater than load, solve for PLR
195 : // Step 2: calculate load at Region 3 lower (cooling) or upper (heating) boundary at maximum air flow rate
196 : // - if load is less than boundary load, solve for air flow and PLR that meet the load
197 : // - ELSE
198 : // Step 3: solve for Region 3 PLR
199 : // DONE
200 : //
201 :
202 : // Step 1: Determine boundary for region 1
203 : // calculate sensible load based on minimum air flow rate and specified supply air temperature limit
204 11 : if (SZVAVModel.ATMixerExists) {
205 0 : if (SZVAVModel.ATMixerType == HVAC::MixerType::SupplySide) {
206 : // Air terminal supply side mixer
207 0 : lowBoundaryLoad =
208 0 : minAirMassFlow * (Psychrometrics::PsyHFnTdbW(state.dataLoopNodes->Node(SZVAVModel.ATMixerOutNode).Temp, ZoneHumRat) -
209 0 : Psychrometrics::PsyHFnTdbW(ZoneTemp, ZoneHumRat));
210 : } else {
211 : // Air terminal inlet side mixer
212 0 : lowBoundaryLoad =
213 0 : minAirMassFlow * (Psychrometrics::PsyHFnTdbW(maxOutletTemp, ZoneHumRat) - Psychrometrics::PsyHFnTdbW(ZoneTemp, ZoneHumRat));
214 : }
215 : } else {
216 11 : minHumRat = min(state.dataLoopNodes->Node(InletNode).HumRat, state.dataLoopNodes->Node(OutletNode).HumRat);
217 11 : lowBoundaryLoad =
218 11 : minAirMassFlow * (Psychrometrics::PsyHFnTdbW(maxOutletTemp, minHumRat) - Psychrometrics::PsyHFnTdbW(InletTemp, minHumRat));
219 : }
220 :
221 11 : if ((CoolingLoad && lowBoundaryLoad < ZoneLoad) || (HeatingLoad && lowBoundaryLoad > ZoneLoad)) { // in Region 1 of figure
222 : // Step 1: set min air flow and full coil capacity
223 2 : PartLoadRatio = 1.0; // full coil capacity
224 2 : SZVAVModel.FanPartLoadRatio =
225 : 0.0; // minimum fan PLR, air flow = ( fanPartLoadRatio * maxAirMassFlow ) + ( ( 1.0 - fanPartLoadRatio ) * minAirMassFlow )
226 2 : state.dataLoopNodes->Node(InletNode).MassFlowRate = minAirMassFlow;
227 : // set max water flow rate and check to see if plant limits flow
228 2 : if (coilPlantLoc.loopNum > 0) {
229 2 : PlantUtilities::SetComponentFlowRate(state, maxCoilFluidFlow, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
230 : }
231 :
232 2 : if (HeatingLoad) { // Function UnitarySystems::calcUnitarySystemToLoad, 4th and 5th arguments are CoolPLR and HeatPLR
233 : // set the water flow ratio so water coil gets proper flow
234 1 : if (SZVAVModel.MaxHeatCoilFluidFlow > 0.0) {
235 1 : SZVAVModel.HeatCoilWaterFlowRatio = maxCoilFluidFlow / SZVAVModel.MaxHeatCoilFluidFlow;
236 : }
237 : }
238 2 : FanCoilUnits::Calc4PipeFanCoil(state, SysIndex, SZVAVModel.ControlZoneNum, FirstHVACIteration, TempSensOutput, PartLoadRatio);
239 2 : coilActive = state.dataLoopNodes->Node(coilAirInletNode).Temp - state.dataLoopNodes->Node(coilAirOutletNode).Temp;
240 :
241 2 : if (!coilActive) { // if the coil is schedule off or the plant cannot provide water
242 0 : if (coilPlantLoc.loopNum > 0) {
243 0 : state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate = 0.0;
244 0 : PlantUtilities::SetComponentFlowRate(
245 0 : state, state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
246 : }
247 0 : return;
248 : }
249 :
250 2 : if ((CoolingLoad && TempSensOutput < ZoneLoad) || (HeatingLoad && TempSensOutput > ZoneLoad)) { // low speed fan can meet load
251 :
252 19 : auto f = [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad, coilFluidInletNode, maxCoilFluidFlow, minAirMassFlow](
253 : Real64 const PLR) {
254 38 : return FanCoilUnits::CalcFanCoilWaterFlowResidual(state,
255 : PLR,
256 : SysIndex,
257 : FirstHVACIteration,
258 19 : SZVAVModel.ControlZoneNum,
259 : ZoneLoad,
260 19 : SZVAVModel.AirInNode,
261 : coilFluidInletNode,
262 : maxCoilFluidFlow,
263 19 : minAirMassFlow);
264 2 : };
265 2 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f, 0.0, 1.0);
266 2 : if (SolFlag < 0) {
267 0 : MessagePrefix = "Step 1: ";
268 : }
269 :
270 2 : if (coilPlantLoc.loopNum > 0) {
271 2 : PlantUtilities::SetComponentFlowRate(
272 2 : state, state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
273 : }
274 : }
275 :
276 2 : } else {
277 :
278 : // Step 2: Load is greater then allowed in region 1, determine boundary load for region 3
279 : // only difference in this calculation is using maxAirMassFlow instead of minAirMassFlow, just use the ratio to adjust previous
280 : // calculation
281 9 : highBoundaryLoad = lowBoundaryLoad * maxAirMassFlow / minAirMassFlow;
282 :
283 9 : if ((CoolingLoad && highBoundaryLoad < ZoneLoad) || (HeatingLoad && highBoundaryLoad > ZoneLoad)) { // in Region 2 of figure
284 :
285 2 : outletTemp = state.dataLoopNodes->Node(OutletNode).Temp;
286 2 : minHumRat = state.dataLoopNodes->Node(SZVAVModel.NodeNumOfControlledZone).HumRat;
287 2 : if (outletTemp < ZoneTemp) {
288 1 : minHumRat = state.dataLoopNodes->Node(OutletNode).HumRat;
289 : }
290 2 : outletTemp = maxOutletTemp;
291 2 : AirMassFlow = min(maxAirMassFlow,
292 2 : (ZoneLoad / (Psychrometrics::PsyHFnTdbW(outletTemp, minHumRat) - Psychrometrics::PsyHFnTdbW(ZoneTemp, minHumRat))));
293 2 : AirMassFlow = max(minAirMassFlow, AirMassFlow);
294 2 : SZVAVModel.FanPartLoadRatio = ((AirMassFlow - (maxAirMassFlow * lowSpeedFanRatio)) / ((1.0 - lowSpeedFanRatio) * maxAirMassFlow));
295 :
296 2 : state.dataLoopNodes->Node(InletNode).MassFlowRate = AirMassFlow;
297 : // does unit have capacity less than load at this air flow rate
298 2 : if (coilFluidInletNode > 0) {
299 2 : state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate = lowWaterMdot;
300 : }
301 2 : FanCoilUnits::Calc4PipeFanCoil(state, SysIndex, SZVAVModel.ControlZoneNum, FirstHVACIteration, TempSensOutput, 0.0);
302 2 : if ((CoolingLoad && (TempSensOutput > ZoneLoad)) || (HeatingLoad && (TempSensOutput < ZoneLoad))) {
303 : // can unit get there with max water flow?
304 2 : if (coilFluidInletNode > 0) {
305 2 : state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate = maxCoilFluidFlow;
306 : }
307 2 : FanCoilUnits::Calc4PipeFanCoil(state, SysIndex, SZVAVModel.ControlZoneNum, FirstHVACIteration, TempSensOutput, 1.0);
308 :
309 : // set max water flow rate and check to see if plant limits flow
310 2 : if (coilPlantLoc.loopNum > 0) {
311 2 : PlantUtilities::SetComponentFlowRate(state, maxCoilFluidFlow, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
312 : }
313 :
314 2 : if ((CoolingLoad && (TempSensOutput < ZoneLoad)) || (HeatingLoad && (TempSensOutput > ZoneLoad))) {
315 2 : if (SZVAVModel.HCoilType_Num == FanCoilUnits::HCoil::Water || !HeatingLoad) {
316 20 : auto f = [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad, coilFluidInletNode, maxCoilFluidFlow, AirMassFlow](
317 : Real64 const PLR) {
318 40 : return FanCoilUnits::CalcFanCoilWaterFlowResidual(state,
319 : PLR,
320 : SysIndex,
321 : FirstHVACIteration,
322 20 : SZVAVModel.ControlZoneNum,
323 : ZoneLoad,
324 20 : SZVAVModel.AirInNode,
325 : coilFluidInletNode,
326 : maxCoilFluidFlow,
327 20 : AirMassFlow);
328 2 : };
329 2 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f, 0.0, 1.0);
330 2 : } else {
331 0 : auto f = [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad](Real64 const PartLoadRatio) {
332 0 : return FanCoilUnits::CalcFanCoilLoadResidual(
333 0 : state, SysIndex, FirstHVACIteration, SZVAVModel.ControlZoneNum, ZoneLoad, PartLoadRatio);
334 0 : };
335 0 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f, 0.0, 1.0);
336 : }
337 2 : outletTemp = state.dataLoopNodes->Node(OutletNode).Temp;
338 : if ((CoolingLoad && outletTemp < maxOutletTemp) || (HeatingLoad && outletTemp > maxOutletTemp)) {
339 : // must do something here to maintain outlet temp while in Region 2
340 : }
341 2 : if (SolFlag < 0) {
342 0 : MessagePrefix = "Step 2: ";
343 : }
344 2 : } else { // not enough capacity at this air flow rate. Unit does have enough capacity a full water/air, otherwise wouldn't be here
345 : // this is different from the PTUnit and UnitarySys routines in this module
346 : // find the water flow rate that meets the min load at region 1/2 boundary
347 0 : if (SZVAVModel.HCoilType_Num == FanCoilUnits::HCoil::Water || !HeatingLoad) {
348 : auto f = // (AUTO_OK_LAMBDA)
349 0 : [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad, coilFluidInletNode, maxCoilFluidFlow, minAirMassFlow](
350 : Real64 const PLR) {
351 0 : return FanCoilUnits::CalcFanCoilWaterFlowResidual(state,
352 : PLR,
353 : SysIndex,
354 : FirstHVACIteration,
355 0 : SZVAVModel.ControlZoneNum,
356 : ZoneLoad,
357 0 : SZVAVModel.AirInNode,
358 : coilFluidInletNode,
359 : maxCoilFluidFlow,
360 0 : minAirMassFlow);
361 0 : };
362 0 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, lowWaterMdot, f, 0.0, 1.0);
363 0 : Real64 minFlow = lowWaterMdot;
364 0 : if (SolFlag < 0) {
365 0 : MessagePrefix = "Step 2a: ";
366 : } else {
367 0 : minFlow = 0.0;
368 : }
369 0 : auto f2 = [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad, coilFluidInletNode, minFlow](Real64 const PLR) {
370 0 : return FanCoilUnits::CalcFanCoilAirAndWaterFlowResidual(state,
371 : PLR,
372 : SysIndex,
373 : FirstHVACIteration,
374 0 : SZVAVModel.ControlZoneNum,
375 : ZoneLoad,
376 0 : SZVAVModel.AirInNode,
377 : coilFluidInletNode,
378 0 : minFlow);
379 0 : };
380 0 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f2, 0.0, 1.0);
381 0 : if (SolFlag < 0) {
382 0 : MessagePrefix = "Step 2b: ";
383 : }
384 0 : } else {
385 0 : auto f = [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad](Real64 const PartLoadRatio) {
386 0 : return FanCoilUnits::CalcFanCoilLoadResidual(
387 0 : state, SysIndex, FirstHVACIteration, SZVAVModel.ControlZoneNum, ZoneLoad, PartLoadRatio);
388 0 : };
389 0 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f, 0.0, 1.0);
390 0 : if (SolFlag < 0) {
391 0 : MessagePrefix = "Step 2: ";
392 : }
393 : }
394 : }
395 2 : } else { // too much capacity when coil off, could lower air flow rate here to meet load if air flow is above minimum
396 0 : if (SZVAVModel.HCoilType_Num == FanCoilUnits::HCoil::Water || !HeatingLoad) {
397 0 : auto f2 = [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad, coilFluidInletNode](Real64 const PLR) {
398 0 : return FanCoilUnits::CalcFanCoilAirAndWaterFlowResidual(state,
399 : PLR,
400 : SysIndex,
401 : FirstHVACIteration,
402 0 : SZVAVModel.ControlZoneNum,
403 : ZoneLoad,
404 0 : SZVAVModel.AirInNode,
405 : coilFluidInletNode,
406 0 : 0.0);
407 0 : };
408 0 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f2, 0.0, 1.0);
409 0 : } else {
410 0 : auto f = [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad](Real64 const PartLoadRatio) {
411 0 : return FanCoilUnits::CalcFanCoilLoadResidual(
412 0 : state, SysIndex, FirstHVACIteration, SZVAVModel.ControlZoneNum, ZoneLoad, PartLoadRatio);
413 0 : };
414 0 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f, 0.0, 1.0);
415 : }
416 0 : if (SolFlag < 0) {
417 0 : MessagePrefix = "Step 2c: ";
418 : }
419 : }
420 :
421 2 : } else { // in region 3 of figure
422 :
423 7 : PartLoadRatio = 1.0; // full coil capacity
424 7 : SZVAVModel.FanPartLoadRatio =
425 : 1.0; // minimum fan PLR, air flow = ( fanPartLoadRatio * maxAirMassFlow ) + ( ( 1.0 - fanPartLoadRatio ) * minAirMassFlow )
426 7 : state.dataLoopNodes->Node(InletNode).MassFlowRate = maxAirMassFlow;
427 : // set max water flow rate and check to see if plant limits flow
428 7 : if (coilPlantLoc.loopNum > 0) {
429 3 : PlantUtilities::SetComponentFlowRate(state, maxCoilFluidFlow, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
430 : }
431 :
432 7 : if (HeatingLoad) { // Function UnitarySystems::calcUnitarySystemToLoad, 4th and 5th arguments are CoolPLR and HeatPLR
433 : // set the water flow ratio so water coil gets proper flow
434 5 : if (SZVAVModel.MaxHeatCoilFluidFlow > 0.0) {
435 1 : SZVAVModel.HeatCoilWaterFlowRatio = maxCoilFluidFlow / SZVAVModel.MaxHeatCoilFluidFlow;
436 : }
437 : }
438 7 : FanCoilUnits::Calc4PipeFanCoil(state, SysIndex, SZVAVModel.ControlZoneNum, FirstHVACIteration, TempSensOutput, PartLoadRatio);
439 7 : coilActive = state.dataLoopNodes->Node(coilAirInletNode).Temp - state.dataLoopNodes->Node(coilAirOutletNode).Temp;
440 7 : if (!coilActive) { // if the coil is schedule off or the plant cannot provide water
441 0 : if (coilPlantLoc.loopNum > 0) {
442 0 : state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate = 0.0;
443 0 : PlantUtilities::SetComponentFlowRate(
444 0 : state, state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
445 : }
446 0 : return;
447 : }
448 :
449 7 : if ((CoolingLoad && ZoneLoad < TempSensOutput) || (HeatingLoad && ZoneLoad > TempSensOutput)) {
450 0 : return; // system cannot meet load, leave at max capacity
451 : }
452 :
453 : // check if coil off is less than load
454 7 : PartLoadRatio = 0.0; // no coil capacity at full air flow
455 7 : if (coilPlantLoc.loopNum > 0) {
456 3 : state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate = 0.0;
457 3 : PlantUtilities::SetComponentFlowRate(
458 3 : state, state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
459 : }
460 7 : FanCoilUnits::Calc4PipeFanCoil(state, SysIndex, SZVAVModel.ControlZoneNum, FirstHVACIteration, TempSensOutput, PartLoadRatio);
461 7 : if ((CoolingLoad && ZoneLoad < TempSensOutput) || (HeatingLoad && ZoneLoad > TempSensOutput)) {
462 : // otherwise iterate on load
463 7 : if (SZVAVModel.HCoilType_Num == FanCoilUnits::HCoil::Water || !HeatingLoad) {
464 21 : auto f = [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad, coilFluidInletNode, maxCoilFluidFlow, maxAirMassFlow](
465 : Real64 const PLR) {
466 42 : return FanCoilUnits::CalcFanCoilWaterFlowResidual(state,
467 : PLR,
468 : SysIndex,
469 : FirstHVACIteration,
470 21 : SZVAVModel.ControlZoneNum,
471 : ZoneLoad,
472 21 : SZVAVModel.AirInNode,
473 : coilFluidInletNode,
474 : maxCoilFluidFlow,
475 21 : maxAirMassFlow);
476 3 : };
477 3 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f, 0.0, 1.0);
478 3 : } else {
479 12 : auto f = [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad](Real64 const PartLoadRatio) {
480 24 : return FanCoilUnits::CalcFanCoilLoadResidual(
481 12 : state, SysIndex, FirstHVACIteration, SZVAVModel.ControlZoneNum, ZoneLoad, PartLoadRatio);
482 4 : };
483 4 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f, 0.0, 1.0);
484 : }
485 7 : if (SolFlag < 0) {
486 0 : MessagePrefix = "Step 3: ";
487 : }
488 7 : } else { // too much capacity at full air flow with coil off, operate coil and fan in unison
489 0 : if (SZVAVModel.HCoilType_Num == FanCoilUnits::HCoil::Water || !HeatingLoad) {
490 0 : auto f2 = [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad, coilFluidInletNode](Real64 const PLR) {
491 0 : return FanCoilUnits::CalcFanCoilAirAndWaterFlowResidual(state,
492 : PLR,
493 : SysIndex,
494 : FirstHVACIteration,
495 0 : SZVAVModel.ControlZoneNum,
496 : ZoneLoad,
497 0 : SZVAVModel.AirInNode,
498 : coilFluidInletNode,
499 0 : 0.0);
500 0 : };
501 0 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f2, 0.0, 1.0);
502 0 : } else {
503 0 : auto f = [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad](Real64 const PartLoadRatio) {
504 0 : return FanCoilUnits::CalcFanCoilLoadResidual(
505 0 : state, SysIndex, FirstHVACIteration, SZVAVModel.ControlZoneNum, ZoneLoad, PartLoadRatio);
506 0 : };
507 0 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f, 0.0, 1.0);
508 : }
509 0 : if (SolFlag < 0) {
510 0 : MessagePrefix = "Step 3a: ";
511 : }
512 : }
513 : }
514 :
515 9 : if (coilPlantLoc.loopNum > 0) {
516 5 : PlantUtilities::SetComponentFlowRate(
517 5 : state, state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
518 : }
519 : }
520 :
521 11 : if (SolFlag < 0) {
522 0 : if (SolFlag == -1) {
523 : // get capacity for warning
524 0 : FanCoilUnits::Calc4PipeFanCoil(state, SysIndex, SZVAVModel.ControlZoneNum, FirstHVACIteration, TempSensOutput, PartLoadRatio);
525 :
526 0 : if (std::abs(TempSensOutput - ZoneLoad) * SZVAVModel.ControlZoneMassFlowFrac >
527 : 15.0) { // water coil can provide same output at varying water PLR (model discontinuity?)
528 0 : if (SZVAVModel.MaxIterIndex == 0) {
529 0 : ShowWarningMessage(
530 0 : state, format("{}Coil control failed to converge for {}:{}", MessagePrefix, SZVAVModel.UnitType, SZVAVModel.Name));
531 0 : ShowContinueError(state, " Iteration limit exceeded in calculating system sensible part-load ratio.");
532 0 : ShowContinueErrorTimeStamp(
533 : state,
534 0 : format("Sensible load to be met = {:.2T} (watts), sensible output = {:.2T} (watts), and the simulation continues.",
535 : ZoneLoad,
536 : TempSensOutput));
537 : }
538 0 : ShowRecurringWarningErrorAtEnd(
539 : state,
540 0 : SZVAVModel.UnitType + " \"" + SZVAVModel.Name +
541 : "\" - Iteration limit exceeded in calculating sensible part-load ratio error continues. Sensible load statistics:",
542 0 : SZVAVModel.MaxIterIndex,
543 : ZoneLoad,
544 : ZoneLoad);
545 : }
546 0 : } else if (SolFlag == -2) {
547 0 : if (SZVAVModel.RegulaFalsiFailedIndex == 0) {
548 0 : ShowWarningMessage(state, format("{}Coil control failed for {}:{}", MessagePrefix, SZVAVModel.UnitType, SZVAVModel.Name));
549 0 : ShowContinueError(state, " sensible part-load ratio determined to be outside the range of 0-1.");
550 0 : ShowContinueErrorTimeStamp(state, format("Sensible load to be met = {:.2T} (watts), and the simulation continues.", ZoneLoad));
551 : }
552 0 : ShowRecurringWarningErrorAtEnd(state,
553 0 : SZVAVModel.UnitType + " \"" + SZVAVModel.Name +
554 : "\" - sensible part-load ratio out of range error continues. Sensible load statistics:",
555 0 : SZVAVModel.RegulaFalsiFailedIndex,
556 : ZoneLoad,
557 : ZoneLoad);
558 : }
559 : }
560 11 : }
561 :
562 21 : void calcSZVAVModel(EnergyPlusData &state,
563 : UnitarySystems::UnitarySys &SZVAVModel,
564 : int const SysIndex,
565 : bool const FirstHVACIteration,
566 : bool const CoolingLoad,
567 : bool const HeatingLoad,
568 : Real64 const ZoneLoad,
569 : Real64 &OnOffAirFlowRatio,
570 : bool const HXUnitOn,
571 : int const AirLoopNum,
572 : Real64 &PartLoadRatio,
573 : HVAC::CompressorOp const CompressorONFlag)
574 : {
575 :
576 21 : UnitarySystems::UnitarySys &thisSys = state.dataUnitarySystems->unitarySys[SysIndex];
577 :
578 21 : int constexpr MaxIter(100); // maximum number of iterations
579 21 : int SolFlag(0); // return flag from RegulaFalsi for sensible load
580 21 : std::string MessagePrefix; // label for warning reporting
581 :
582 21 : Real64 boundaryLoadMet(0.0);
583 21 : Real64 minHumRat(0.0);
584 21 : Real64 outletTemp(0.0);
585 21 : bool coilActive(false);
586 21 : Real64 AirMassFlow(0.0);
587 :
588 21 : Real64 maxCoilFluidFlow(0.0);
589 21 : Real64 maxOutletTemp(0.0);
590 21 : Real64 minAirMassFlow(0.0);
591 21 : Real64 maxAirMassFlow(0.0);
592 21 : Real64 lowSpeedFanRatio(0.0);
593 21 : int coilFluidInletNode(0);
594 21 : int coilFluidOutletNode(0);
595 21 : PlantLocation coilPlantLoc{};
596 21 : int coilAirInletNode(0);
597 21 : int coilAirOutletNode(0);
598 21 : Real64 HeatCoilLoad(0.0);
599 21 : Real64 SupHeaterLoad(0.0);
600 :
601 : Real64 TempSensOutput; // iterative sensible capacity [W]
602 : Real64 TempLatOutput; // iterative latent capacity [W]
603 :
604 : // set up mode specific variables to use in common function calls
605 21 : if (CoolingLoad) {
606 7 : maxCoilFluidFlow = SZVAVModel.MaxCoolCoilFluidFlow;
607 7 : maxOutletTemp = SZVAVModel.DesignMinOutletTemp;
608 7 : minAirMassFlow = SZVAVModel.MaxNoCoolHeatAirMassFlow;
609 7 : maxAirMassFlow = SZVAVModel.MaxCoolAirMassFlow;
610 7 : lowSpeedFanRatio = SZVAVModel.LowSpeedCoolFanRatio;
611 7 : coilFluidInletNode = SZVAVModel.CoolCoilFluidInletNode;
612 7 : coilFluidOutletNode = SZVAVModel.CoolCoilFluidOutletNodeNum;
613 7 : coilPlantLoc = SZVAVModel.CoolCoilPlantLoc;
614 7 : coilAirInletNode = SZVAVModel.CoolCoilInletNodeNum;
615 7 : coilAirOutletNode = SZVAVModel.CoolCoilOutletNodeNum;
616 14 : } else if (HeatingLoad) {
617 14 : maxCoilFluidFlow = SZVAVModel.MaxHeatCoilFluidFlow;
618 14 : maxOutletTemp = SZVAVModel.DesignMaxOutletTemp;
619 14 : minAirMassFlow = SZVAVModel.MaxNoCoolHeatAirMassFlow;
620 14 : maxAirMassFlow = SZVAVModel.MaxHeatAirMassFlow;
621 14 : lowSpeedFanRatio = SZVAVModel.LowSpeedHeatFanRatio;
622 14 : coilFluidInletNode = SZVAVModel.HeatCoilFluidInletNode;
623 14 : coilFluidOutletNode = SZVAVModel.HeatCoilFluidOutletNodeNum;
624 14 : coilPlantLoc = SZVAVModel.HeatCoilPlantLoc;
625 14 : coilAirInletNode = SZVAVModel.HeatCoilInletNodeNum;
626 14 : coilAirOutletNode = SZVAVModel.HeatCoilOutletNodeNum;
627 : } else { // should never get here, protect against uninitialized variables
628 0 : maxCoilFluidFlow = 0.0;
629 0 : maxOutletTemp = 0.0;
630 0 : minAirMassFlow = 0.0;
631 0 : maxAirMassFlow = 0.0;
632 0 : lowSpeedFanRatio = 0.0;
633 0 : coilFluidInletNode = 0;
634 0 : coilFluidOutletNode = 0;
635 0 : coilPlantLoc = {0, DataPlant::LoopSideLocation::Invalid, 0, 0};
636 0 : coilAirInletNode = 0;
637 0 : coilAirOutletNode = 0;
638 : }
639 : // set up RegulaFalsi variables
640 21 : int InletNode = SZVAVModel.AirInNode;
641 21 : Real64 InletTemp = state.dataLoopNodes->Node(InletNode).Temp;
642 21 : int OutletNode = SZVAVModel.AirOutNode;
643 21 : Real64 ZoneTemp = state.dataLoopNodes->Node(SZVAVModel.NodeNumOfControlledZone).Temp;
644 21 : Real64 ZoneHumRat = state.dataLoopNodes->Node(SZVAVModel.NodeNumOfControlledZone).HumRat;
645 :
646 : // model attempts to control air flow rate and coil capacity in specific operating regions:
647 : // Region 1 (R1) - minimum air flow rate at modulated coil capacity (up to min/max temperature limits)
648 : // Region 2 (R2) - modulated air flow rate and coil capacity (up to max air flow rate while maintaining min/max temperature limits)
649 : // Region 3 (R3) - maximum air flow rate and modulated/increased coil capacity (allow increased capacity at full air flow rate to meet
650 : // remaining load)
651 : //
652 : // | | | | ^ ^ = supply air temperature
653 : // | | | | ^ * = supply air flow rate
654 : // | | |^^^^| <--- maximum supply air temperature
655 : // | | ^ | |
656 : // | | ^ | |
657 : // ***********| | ^ | |************** <-- max unit air flow rate
658 : // |* | ^ | *|
659 : // | * | ^ | * |
660 : // | * | ^ | * |
661 : // | *| ^ |* |
662 : // | |*******************| | <-- min unit air flow rate
663 : // R3 | R2 | ^ R1 | R2 | R3
664 : // min SAT --> |^^^^| | |
665 : // ^
666 : // ^ |
667 : // (-) increasing cooling load < 0 > increasing heating load (+)
668 : //
669 : // Notes: SAT is allowed to decrease/increase once air flow rate is max'ed out otherwise load would not be met
670 : // Load here is not the zone load, it's the load the system must meet to meet the Tstat set point (i.e., OA can alter required
671 : // capacity) lowSpeedFanRatio = min/max unit air flow rate
672 : //
673 : // Step 1: calculate load at Region 1 lower (cooling) or upper (heating) boundary at minimum air flow rate
674 : // - if load can be met, test maximum output (PLR = 1) before calling RootSolver
675 : // - if maximum capacity is greater than load, solve for PLR
676 : // Step 2: calculate load at Region 3 lower (cooling) or upper (heating) boundary at maximum air flow rate
677 : // - if load is less than boundary load, solve for air flow and PLR that meet the load
678 : // - ELSE
679 : // Step 3: solve for Region 3 PLR
680 : // DONE
681 : //
682 :
683 : // Step 1: Determine boundary for region 1
684 : // calculate sensible load based on minimum air flow rate and specified supply air temperature limit
685 21 : if (SZVAVModel.ATMixerExists) {
686 0 : if (SZVAVModel.ATMixerType == HVAC::MixerType::SupplySide) {
687 : // Air terminal supply side mixer
688 0 : boundaryLoadMet =
689 0 : minAirMassFlow * (Psychrometrics::PsyHFnTdbW(state.dataLoopNodes->Node(SZVAVModel.ATMixerOutNode).Temp, ZoneHumRat) -
690 0 : Psychrometrics::PsyHFnTdbW(ZoneTemp, ZoneHumRat));
691 : } else {
692 : // Air terminal inlet side mixer
693 0 : boundaryLoadMet =
694 0 : minAirMassFlow * (Psychrometrics::PsyHFnTdbW(maxOutletTemp, ZoneHumRat) - Psychrometrics::PsyHFnTdbW(ZoneTemp, ZoneHumRat));
695 : }
696 : } else {
697 21 : minHumRat = min(state.dataLoopNodes->Node(InletNode).HumRat, state.dataLoopNodes->Node(OutletNode).HumRat);
698 21 : boundaryLoadMet =
699 21 : minAirMassFlow * (Psychrometrics::PsyHFnTdbW(maxOutletTemp, minHumRat) - Psychrometrics::PsyHFnTdbW(InletTemp, minHumRat));
700 : }
701 :
702 21 : if ((CoolingLoad && boundaryLoadMet < ZoneLoad) || (HeatingLoad && boundaryLoadMet > ZoneLoad)) { // in Region 1 of figure
703 : // Step 1: set min air flow and full coil capacity
704 10 : PartLoadRatio = 1.0; // full coil capacity
705 10 : SZVAVModel.FanPartLoadRatio =
706 : 0.0; // minimum fan PLR, air flow = ( fanPartLoadRatio * maxAirMassFlow ) + ( ( 1.0 - fanPartLoadRatio ) * minAirMassFlow )
707 10 : state.dataLoopNodes->Node(InletNode).MassFlowRate = minAirMassFlow;
708 : // set max water flow rate and check to see if plant limits flow
709 10 : if (coilPlantLoc.loopNum > 0) {
710 4 : PlantUtilities::SetComponentFlowRate(state, maxCoilFluidFlow, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
711 : }
712 :
713 10 : if (CoolingLoad) { // Function CalcUnitarySystemToLoad, 4th and 5th arguments are CoolPLR and HeatPLR
714 : // set the water flow ratio so water coil gets proper flow
715 3 : if (SZVAVModel.MaxCoolCoilFluidFlow > 0.0) {
716 3 : SZVAVModel.CoolCoilWaterFlowRatio = maxCoilFluidFlow / SZVAVModel.MaxCoolCoilFluidFlow;
717 : }
718 3 : thisSys.calcUnitarySystemToLoad(state,
719 : AirLoopNum,
720 : FirstHVACIteration,
721 : PartLoadRatio,
722 : 0.0,
723 : OnOffAirFlowRatio,
724 : TempSensOutput,
725 : TempLatOutput,
726 : HXUnitOn,
727 : HeatCoilLoad,
728 : SupHeaterLoad,
729 : CompressorONFlag);
730 : } else {
731 7 : if (SZVAVModel.MaxHeatCoilFluidFlow > 0.0) {
732 2 : SZVAVModel.HeatCoilWaterFlowRatio = maxCoilFluidFlow / SZVAVModel.MaxHeatCoilFluidFlow;
733 : }
734 7 : thisSys.calcUnitarySystemToLoad(state,
735 : AirLoopNum,
736 : FirstHVACIteration,
737 : 0.0,
738 : PartLoadRatio,
739 : OnOffAirFlowRatio,
740 : TempSensOutput,
741 : TempLatOutput,
742 : HXUnitOn,
743 : ZoneLoad,
744 : SupHeaterLoad,
745 : CompressorONFlag);
746 : }
747 10 : coilActive = state.dataLoopNodes->Node(coilAirInletNode).Temp - state.dataLoopNodes->Node(coilAirOutletNode).Temp;
748 :
749 10 : if (!coilActive) { // if the coil is schedule off or the plant cannot provide water
750 2 : if (coilPlantLoc.loopNum > 0) {
751 1 : state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate = 0.0;
752 1 : PlantUtilities::SetComponentFlowRate(
753 1 : state, state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
754 : }
755 2 : return;
756 : }
757 :
758 8 : if ((CoolingLoad && TempSensOutput < ZoneLoad) || (HeatingLoad && TempSensOutput > ZoneLoad)) { // low speed fan can meet load
759 38 : auto f = [&state,
760 : SysIndex,
761 : FirstHVACIteration,
762 : ZoneLoad,
763 : &SZVAVModel,
764 8 : OnOffAirFlowRatio,
765 : AirLoopNum,
766 : coilFluidInletNode,
767 : lowSpeedFanRatio,
768 : maxCoilFluidFlow,
769 : minAirMassFlow,
770 : maxAirMassFlow,
771 : CoolingLoad](Real64 const PartLoadRatio) {
772 76 : return UnitarySystems::UnitarySys::calcUnitarySystemWaterFlowResidual(state,
773 : PartLoadRatio, // coil part load ratio
774 : SysIndex,
775 : FirstHVACIteration,
776 : ZoneLoad,
777 38 : SZVAVModel.AirInNode,
778 : OnOffAirFlowRatio,
779 : AirLoopNum,
780 : coilFluidInletNode,
781 : maxCoilFluidFlow,
782 : lowSpeedFanRatio,
783 : minAirMassFlow,
784 : 0.0,
785 : maxAirMassFlow,
786 : CoolingLoad,
787 38 : 1.0);
788 8 : };
789 8 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f, 0.0, 1.0);
790 8 : if (SolFlag < 0) {
791 0 : MessagePrefix = "Step 1: ";
792 : }
793 :
794 8 : if (coilPlantLoc.loopNum > 0) {
795 3 : PlantUtilities::SetComponentFlowRate(
796 3 : state, state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
797 : }
798 : }
799 :
800 8 : } else {
801 :
802 : // Step 2: Load is greater than allowed in region 1, determine boundary load for region 3
803 : // only difference in this calculation is using maxAirMassFlow instead of minAirMassFlow, just use the ratio to adjust previous
804 : // calculation
805 11 : boundaryLoadMet *= maxAirMassFlow / minAirMassFlow;
806 :
807 11 : if ((CoolingLoad && boundaryLoadMet < ZoneLoad) || (HeatingLoad && boundaryLoadMet > ZoneLoad)) { // in Region 2 of figure
808 :
809 6 : outletTemp = state.dataLoopNodes->Node(OutletNode).Temp;
810 6 : minHumRat = state.dataLoopNodes->Node(SZVAVModel.NodeNumOfControlledZone).HumRat;
811 6 : if (outletTemp < ZoneTemp) {
812 2 : minHumRat = state.dataLoopNodes->Node(OutletNode).HumRat;
813 : }
814 6 : outletTemp = maxOutletTemp;
815 6 : AirMassFlow = min(maxAirMassFlow,
816 6 : (ZoneLoad / (Psychrometrics::PsyHFnTdbW(outletTemp, minHumRat) - Psychrometrics::PsyHFnTdbW(ZoneTemp, minHumRat))));
817 6 : AirMassFlow = max(minAirMassFlow, AirMassFlow);
818 6 : SZVAVModel.FanPartLoadRatio = ((AirMassFlow - (maxAirMassFlow * lowSpeedFanRatio)) / ((1.0 - lowSpeedFanRatio) * maxAirMassFlow));
819 :
820 33 : auto f = [&state,
821 : SysIndex,
822 : FirstHVACIteration,
823 : ZoneLoad,
824 : &SZVAVModel,
825 6 : OnOffAirFlowRatio,
826 : AirLoopNum,
827 : coilFluidInletNode,
828 : lowSpeedFanRatio,
829 : AirMassFlow,
830 : maxAirMassFlow,
831 : CoolingLoad,
832 : maxCoilFluidFlow](Real64 const PartLoadRatio) {
833 66 : return UnitarySystems::UnitarySys::calcUnitarySystemWaterFlowResidual(state,
834 : PartLoadRatio, // coil part load ratio
835 : SysIndex,
836 : FirstHVACIteration,
837 : ZoneLoad,
838 33 : SZVAVModel.AirInNode,
839 : OnOffAirFlowRatio,
840 : AirLoopNum,
841 : coilFluidInletNode,
842 : maxCoilFluidFlow,
843 : lowSpeedFanRatio,
844 : AirMassFlow,
845 : 0.0,
846 : maxAirMassFlow,
847 : CoolingLoad,
848 33 : 1.0);
849 6 : };
850 6 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f, 0.0, 1.0);
851 6 : if (SolFlag < 0) {
852 0 : MessagePrefix = "Step 2: ";
853 : }
854 :
855 6 : } else { // in region 3 of figure
856 :
857 5 : PartLoadRatio = 1.0; // full coil capacity
858 5 : SZVAVModel.FanPartLoadRatio =
859 : 1.0; // minimum fan PLR, air flow = ( fanPartLoadRatio * maxAirMassFlow ) + ( ( 1.0 - fanPartLoadRatio ) * minAirMassFlow )
860 5 : state.dataLoopNodes->Node(InletNode).MassFlowRate = maxAirMassFlow;
861 : // set max water flow rate and check to see if plant limits flow
862 5 : if (coilPlantLoc.loopNum > 0) {
863 2 : PlantUtilities::SetComponentFlowRate(state, maxCoilFluidFlow, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
864 : }
865 :
866 5 : if (CoolingLoad) { // Function CalcUnitarySystemToLoad, 4th and 5th arguments are CoolPLR and HeatPLR
867 : // set the water flow ratio so water coil gets proper flow
868 2 : if (SZVAVModel.MaxCoolCoilFluidFlow > 0.0) {
869 2 : SZVAVModel.CoolCoilWaterFlowRatio = maxCoilFluidFlow / SZVAVModel.MaxCoolCoilFluidFlow;
870 : }
871 2 : thisSys.calcUnitarySystemToLoad(state,
872 : AirLoopNum,
873 : FirstHVACIteration,
874 : PartLoadRatio,
875 : 0.0,
876 : OnOffAirFlowRatio,
877 : TempSensOutput,
878 : TempLatOutput,
879 : HXUnitOn,
880 : HeatCoilLoad,
881 : SupHeaterLoad,
882 : CompressorONFlag);
883 : } else {
884 3 : if (SZVAVModel.MaxHeatCoilFluidFlow > 0.0) {
885 1 : SZVAVModel.HeatCoilWaterFlowRatio = maxCoilFluidFlow / SZVAVModel.MaxHeatCoilFluidFlow;
886 : }
887 3 : thisSys.calcUnitarySystemToLoad(state,
888 : AirLoopNum,
889 : FirstHVACIteration,
890 : 0.0,
891 : PartLoadRatio,
892 : OnOffAirFlowRatio,
893 : TempSensOutput,
894 : TempLatOutput,
895 : HXUnitOn,
896 : ZoneLoad,
897 : SupHeaterLoad,
898 : CompressorONFlag);
899 : }
900 5 : coilActive = state.dataLoopNodes->Node(coilAirInletNode).Temp - state.dataLoopNodes->Node(coilAirOutletNode).Temp;
901 5 : if (!coilActive) { // if the coil is schedule off or the plant cannot provide water
902 0 : if (coilPlantLoc.loopNum > 0) {
903 0 : state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate = 0.0;
904 0 : PlantUtilities::SetComponentFlowRate(
905 0 : state, state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
906 : }
907 1 : return;
908 : }
909 :
910 5 : if ((CoolingLoad && ZoneLoad < TempSensOutput) || (HeatingLoad && ZoneLoad > TempSensOutput)) {
911 1 : return; // system cannot meet load, leave at max capacity
912 : }
913 :
914 : // otherwise iterate on load
915 24 : auto f = [&state,
916 : SysIndex,
917 : FirstHVACIteration,
918 : ZoneLoad,
919 : &SZVAVModel,
920 4 : OnOffAirFlowRatio,
921 : AirLoopNum,
922 : coilFluidInletNode,
923 : lowSpeedFanRatio,
924 : maxCoilFluidFlow,
925 : maxAirMassFlow,
926 : CoolingLoad](Real64 const PartLoadRatio) {
927 48 : return UnitarySystems::UnitarySys::calcUnitarySystemWaterFlowResidual(state,
928 : PartLoadRatio, // coil part load ratio
929 : SysIndex,
930 : FirstHVACIteration,
931 : ZoneLoad,
932 24 : SZVAVModel.AirInNode,
933 : OnOffAirFlowRatio,
934 : AirLoopNum,
935 : coilFluidInletNode,
936 : maxCoilFluidFlow,
937 : lowSpeedFanRatio,
938 : maxAirMassFlow,
939 : 0.0,
940 : maxAirMassFlow,
941 : CoolingLoad,
942 24 : 1.0);
943 4 : };
944 4 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f, 0.0, 1.0);
945 : // Par[12] = maxAirMassFlow; // operating air flow rate, minAirMassFlow indicates low speed air flow rate,
946 : // maxAirMassFlow indicates full
947 : // // air flow
948 : // Par[13] = 0.0; // SA Temp target, 0 means iterate on load and not SA temperature
949 : // General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, thisSys.calcUnitarySystemWaterFlowResidual,
950 : // 0.0, 1.0, Par);
951 4 : if (SolFlag < 0) {
952 0 : MessagePrefix = "Step 3: ";
953 : }
954 : }
955 :
956 10 : if (coilPlantLoc.loopNum > 0) {
957 4 : PlantUtilities::SetComponentFlowRate(
958 4 : state, state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
959 : }
960 : }
961 :
962 18 : if (SolFlag < 0) {
963 0 : if (SolFlag == -1) {
964 : // get capacity for warning
965 0 : if (CoolingLoad) { // Function CalcUnitarySystemToLoad, 4th and 5th arguments are CoolPLR and HeatPLR
966 0 : thisSys.calcUnitarySystemToLoad(state,
967 : AirLoopNum,
968 : FirstHVACIteration,
969 : PartLoadRatio,
970 : 0.0,
971 : OnOffAirFlowRatio,
972 : TempSensOutput,
973 : TempLatOutput,
974 : HXUnitOn,
975 : HeatCoilLoad,
976 : SupHeaterLoad,
977 : CompressorONFlag);
978 : } else {
979 0 : thisSys.calcUnitarySystemToLoad(state,
980 : AirLoopNum,
981 : FirstHVACIteration,
982 : 0.0,
983 : PartLoadRatio,
984 : OnOffAirFlowRatio,
985 : TempSensOutput,
986 : TempLatOutput,
987 : HXUnitOn,
988 : ZoneLoad,
989 : SupHeaterLoad,
990 : CompressorONFlag);
991 : }
992 :
993 0 : if (std::abs(TempSensOutput - ZoneLoad) * SZVAVModel.ControlZoneMassFlowFrac >
994 : 15.0) { // water coil can provide same output at varying water PLR (model discontinuity?)
995 0 : if (SZVAVModel.MaxIterIndex == 0) {
996 0 : ShowWarningMessage(
997 0 : state, format("{}Coil control failed to converge for {}:{}", MessagePrefix, SZVAVModel.UnitType, SZVAVModel.Name));
998 0 : ShowContinueError(state, " Iteration limit exceeded in calculating system sensible part-load ratio.");
999 0 : ShowContinueErrorTimeStamp(
1000 : state,
1001 0 : format("Sensible load to be met = {:.2T} (watts), sensible output = {:.2T} (watts), and the simulation continues.",
1002 : ZoneLoad,
1003 : TempSensOutput));
1004 : }
1005 0 : ShowRecurringWarningErrorAtEnd(
1006 : state,
1007 0 : SZVAVModel.UnitType + " \"" + SZVAVModel.Name +
1008 : "\" - Iteration limit exceeded in calculating sensible part-load ratio error continues. Sensible load statistics:",
1009 0 : SZVAVModel.MaxIterIndex,
1010 : ZoneLoad,
1011 : ZoneLoad);
1012 : }
1013 0 : } else if (SolFlag == -2) {
1014 0 : if (SZVAVModel.RegulaFalsiFailedIndex == 0) {
1015 0 : ShowWarningMessage(state, format("{}Coil control failed for {}:{}", MessagePrefix, SZVAVModel.UnitType, SZVAVModel.Name));
1016 0 : ShowContinueError(state, " sensible part-load ratio determined to be outside the range of 0-1.");
1017 0 : ShowContinueErrorTimeStamp(state, format("Sensible load to be met = {:.2T} (watts), and the simulation continues.", ZoneLoad));
1018 : }
1019 0 : ShowRecurringWarningErrorAtEnd(state,
1020 0 : SZVAVModel.UnitType + " \"" + SZVAVModel.Name +
1021 : "\" - sensible part-load ratio out of range error continues. Sensible load statistics:",
1022 0 : SZVAVModel.RegulaFalsiFailedIndex,
1023 : ZoneLoad,
1024 : ZoneLoad);
1025 : }
1026 : }
1027 21 : }
1028 :
1029 : } // namespace SZVAVModel
1030 :
1031 : } // namespace EnergyPlus
|