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 2 : if (HeatingLoad) { // Function UnitarySystems::calcUnitarySystemToLoad, 4th and 5th arguments are CoolPLR and HeatPLR
232 : // set the water flow ratio so water coil gets proper flow
233 1 : if (SZVAVModel.MaxHeatCoilFluidFlow > 0.0) SZVAVModel.HeatCoilWaterFlowRatio = maxCoilFluidFlow / SZVAVModel.MaxHeatCoilFluidFlow;
234 : }
235 2 : FanCoilUnits::Calc4PipeFanCoil(state, SysIndex, SZVAVModel.ControlZoneNum, FirstHVACIteration, TempSensOutput, PartLoadRatio);
236 2 : coilActive = state.dataLoopNodes->Node(coilAirInletNode).Temp - state.dataLoopNodes->Node(coilAirOutletNode).Temp;
237 :
238 2 : if (!coilActive) { // if the coil is schedule off or the plant cannot provide water
239 0 : if (coilPlantLoc.loopNum > 0) {
240 0 : state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate = 0.0;
241 0 : PlantUtilities::SetComponentFlowRate(
242 0 : state, state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
243 : }
244 0 : return;
245 : }
246 :
247 2 : if ((CoolingLoad && TempSensOutput < ZoneLoad) || (HeatingLoad && TempSensOutput > ZoneLoad)) { // low speed fan can meet load
248 :
249 19 : auto f = [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad, coilFluidInletNode, maxCoilFluidFlow, minAirMassFlow](
250 : Real64 const PLR) {
251 38 : return FanCoilUnits::CalcFanCoilWaterFlowResidual(state,
252 : PLR,
253 : SysIndex,
254 : FirstHVACIteration,
255 19 : SZVAVModel.ControlZoneNum,
256 : ZoneLoad,
257 19 : SZVAVModel.AirInNode,
258 : coilFluidInletNode,
259 : maxCoilFluidFlow,
260 19 : minAirMassFlow);
261 2 : };
262 2 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f, 0.0, 1.0);
263 2 : if (SolFlag < 0) {
264 0 : MessagePrefix = "Step 1: ";
265 : }
266 :
267 2 : if (coilPlantLoc.loopNum > 0)
268 2 : PlantUtilities::SetComponentFlowRate(
269 2 : state, state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
270 : }
271 :
272 2 : } else {
273 :
274 : // Step 2: Load is greater then allowed in region 1, determine boundary load for region 3
275 : // only difference in this calculation is using maxAirMassFlow instead of minAirMassFlow, just use the ratio to adjust previous
276 : // calculation
277 9 : highBoundaryLoad = lowBoundaryLoad * maxAirMassFlow / minAirMassFlow;
278 :
279 9 : if ((CoolingLoad && highBoundaryLoad < ZoneLoad) || (HeatingLoad && highBoundaryLoad > ZoneLoad)) { // in Region 2 of figure
280 :
281 2 : outletTemp = state.dataLoopNodes->Node(OutletNode).Temp;
282 2 : minHumRat = state.dataLoopNodes->Node(SZVAVModel.NodeNumOfControlledZone).HumRat;
283 2 : if (outletTemp < ZoneTemp) minHumRat = state.dataLoopNodes->Node(OutletNode).HumRat;
284 2 : outletTemp = maxOutletTemp;
285 2 : AirMassFlow = min(maxAirMassFlow,
286 2 : (ZoneLoad / (Psychrometrics::PsyHFnTdbW(outletTemp, minHumRat) - Psychrometrics::PsyHFnTdbW(ZoneTemp, minHumRat))));
287 2 : AirMassFlow = max(minAirMassFlow, AirMassFlow);
288 2 : SZVAVModel.FanPartLoadRatio = ((AirMassFlow - (maxAirMassFlow * lowSpeedFanRatio)) / ((1.0 - lowSpeedFanRatio) * maxAirMassFlow));
289 :
290 2 : state.dataLoopNodes->Node(InletNode).MassFlowRate = AirMassFlow;
291 : // does unit have capacity less than load at this air flow rate
292 2 : if (coilFluidInletNode > 0) state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate = lowWaterMdot;
293 2 : FanCoilUnits::Calc4PipeFanCoil(state, SysIndex, SZVAVModel.ControlZoneNum, FirstHVACIteration, TempSensOutput, 0.0);
294 2 : if ((CoolingLoad && (TempSensOutput > ZoneLoad)) || (HeatingLoad && (TempSensOutput < ZoneLoad))) {
295 : // can unit get there with max water flow?
296 2 : if (coilFluidInletNode > 0) state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate = maxCoilFluidFlow;
297 2 : FanCoilUnits::Calc4PipeFanCoil(state, SysIndex, SZVAVModel.ControlZoneNum, FirstHVACIteration, TempSensOutput, 1.0);
298 :
299 : // set max water flow rate and check to see if plant limits flow
300 2 : if (coilPlantLoc.loopNum > 0)
301 2 : PlantUtilities::SetComponentFlowRate(state, maxCoilFluidFlow, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
302 :
303 2 : if ((CoolingLoad && (TempSensOutput < ZoneLoad)) || (HeatingLoad && (TempSensOutput > ZoneLoad))) {
304 2 : if (SZVAVModel.HCoilType_Num == FanCoilUnits::HCoil::Water || !HeatingLoad) {
305 20 : auto f = [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad, coilFluidInletNode, maxCoilFluidFlow, AirMassFlow](
306 : Real64 const PLR) {
307 40 : return FanCoilUnits::CalcFanCoilWaterFlowResidual(state,
308 : PLR,
309 : SysIndex,
310 : FirstHVACIteration,
311 20 : SZVAVModel.ControlZoneNum,
312 : ZoneLoad,
313 20 : SZVAVModel.AirInNode,
314 : coilFluidInletNode,
315 : maxCoilFluidFlow,
316 20 : AirMassFlow);
317 2 : };
318 2 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f, 0.0, 1.0);
319 2 : } else {
320 0 : auto f = [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad](Real64 const PartLoadRatio) {
321 0 : return FanCoilUnits::CalcFanCoilLoadResidual(
322 0 : state, SysIndex, FirstHVACIteration, SZVAVModel.ControlZoneNum, ZoneLoad, PartLoadRatio);
323 0 : };
324 0 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f, 0.0, 1.0);
325 : }
326 2 : outletTemp = state.dataLoopNodes->Node(OutletNode).Temp;
327 : if ((CoolingLoad && outletTemp < maxOutletTemp) || (HeatingLoad && outletTemp > maxOutletTemp)) {
328 : // must do something here to maintain outlet temp while in Region 2
329 : }
330 2 : if (SolFlag < 0) {
331 0 : MessagePrefix = "Step 2: ";
332 : }
333 2 : } else { // not enough capacity at this air flow rate. Unit does have enough capacity a full water/air, otherwise wouldn't be here
334 : // this is different from the PTUnit and UnitarySys routines in this module
335 : // find the water flow rate that meets the min load at region 1/2 boundary
336 0 : if (SZVAVModel.HCoilType_Num == FanCoilUnits::HCoil::Water || !HeatingLoad) {
337 : auto f = // (AUTO_OK_LAMBDA)
338 0 : [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad, coilFluidInletNode, maxCoilFluidFlow, minAirMassFlow](
339 : Real64 const PLR) {
340 0 : return FanCoilUnits::CalcFanCoilWaterFlowResidual(state,
341 : PLR,
342 : SysIndex,
343 : FirstHVACIteration,
344 0 : SZVAVModel.ControlZoneNum,
345 : ZoneLoad,
346 0 : SZVAVModel.AirInNode,
347 : coilFluidInletNode,
348 : maxCoilFluidFlow,
349 0 : minAirMassFlow);
350 0 : };
351 0 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, lowWaterMdot, f, 0.0, 1.0);
352 0 : Real64 minFlow = lowWaterMdot;
353 0 : if (SolFlag < 0) {
354 0 : MessagePrefix = "Step 2a: ";
355 : } else {
356 0 : minFlow = 0.0;
357 : }
358 0 : auto f2 = [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad, coilFluidInletNode, minFlow](Real64 const PLR) {
359 0 : return FanCoilUnits::CalcFanCoilAirAndWaterFlowResidual(state,
360 : PLR,
361 : SysIndex,
362 : FirstHVACIteration,
363 0 : SZVAVModel.ControlZoneNum,
364 : ZoneLoad,
365 0 : SZVAVModel.AirInNode,
366 : coilFluidInletNode,
367 0 : minFlow);
368 0 : };
369 0 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f2, 0.0, 1.0);
370 0 : if (SolFlag < 0) {
371 0 : MessagePrefix = "Step 2b: ";
372 : }
373 0 : } else {
374 0 : auto f = [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad](Real64 const PartLoadRatio) {
375 0 : return FanCoilUnits::CalcFanCoilLoadResidual(
376 0 : state, SysIndex, FirstHVACIteration, SZVAVModel.ControlZoneNum, ZoneLoad, PartLoadRatio);
377 0 : };
378 0 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f, 0.0, 1.0);
379 0 : if (SolFlag < 0) {
380 0 : MessagePrefix = "Step 2: ";
381 : }
382 : }
383 : }
384 2 : } else { // too much capacity when coil off, could lower air flow rate here to meet load if air flow is above minimum
385 0 : if (SZVAVModel.HCoilType_Num == FanCoilUnits::HCoil::Water || !HeatingLoad) {
386 0 : auto f2 = [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad, coilFluidInletNode](Real64 const PLR) {
387 0 : return FanCoilUnits::CalcFanCoilAirAndWaterFlowResidual(state,
388 : PLR,
389 : SysIndex,
390 : FirstHVACIteration,
391 0 : SZVAVModel.ControlZoneNum,
392 : ZoneLoad,
393 0 : SZVAVModel.AirInNode,
394 : coilFluidInletNode,
395 0 : 0.0);
396 0 : };
397 0 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f2, 0.0, 1.0);
398 0 : } else {
399 0 : auto f = [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad](Real64 const PartLoadRatio) {
400 0 : return FanCoilUnits::CalcFanCoilLoadResidual(
401 0 : state, SysIndex, FirstHVACIteration, SZVAVModel.ControlZoneNum, ZoneLoad, PartLoadRatio);
402 0 : };
403 0 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f, 0.0, 1.0);
404 : }
405 0 : if (SolFlag < 0) {
406 0 : MessagePrefix = "Step 2c: ";
407 : }
408 : }
409 :
410 2 : } else { // in region 3 of figure
411 :
412 7 : PartLoadRatio = 1.0; // full coil capacity
413 7 : SZVAVModel.FanPartLoadRatio =
414 : 1.0; // minimum fan PLR, air flow = ( fanPartLoadRatio * maxAirMassFlow ) + ( ( 1.0 - fanPartLoadRatio ) * minAirMassFlow )
415 7 : state.dataLoopNodes->Node(InletNode).MassFlowRate = maxAirMassFlow;
416 : // set max water flow rate and check to see if plant limits flow
417 7 : if (coilPlantLoc.loopNum > 0)
418 3 : PlantUtilities::SetComponentFlowRate(state, maxCoilFluidFlow, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
419 :
420 7 : if (HeatingLoad) { // Function UnitarySystems::calcUnitarySystemToLoad, 4th and 5th arguments are CoolPLR and HeatPLR
421 : // set the water flow ratio so water coil gets proper flow
422 5 : if (SZVAVModel.MaxHeatCoilFluidFlow > 0.0) SZVAVModel.HeatCoilWaterFlowRatio = maxCoilFluidFlow / SZVAVModel.MaxHeatCoilFluidFlow;
423 : }
424 7 : FanCoilUnits::Calc4PipeFanCoil(state, SysIndex, SZVAVModel.ControlZoneNum, FirstHVACIteration, TempSensOutput, PartLoadRatio);
425 7 : coilActive = state.dataLoopNodes->Node(coilAirInletNode).Temp - state.dataLoopNodes->Node(coilAirOutletNode).Temp;
426 7 : if (!coilActive) { // if the coil is schedule off or the plant cannot provide water
427 0 : if (coilPlantLoc.loopNum > 0) {
428 0 : state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate = 0.0;
429 0 : PlantUtilities::SetComponentFlowRate(
430 0 : state, state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
431 : }
432 0 : return;
433 : }
434 :
435 7 : if ((CoolingLoad && ZoneLoad < TempSensOutput) || (HeatingLoad && ZoneLoad > TempSensOutput))
436 0 : return; // system cannot meet load, leave at max capacity
437 :
438 : // check if coil off is less than load
439 7 : PartLoadRatio = 0.0; // no coil capacity at full air flow
440 7 : if (coilPlantLoc.loopNum > 0) {
441 3 : state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate = 0.0;
442 3 : PlantUtilities::SetComponentFlowRate(
443 3 : state, state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
444 : }
445 7 : FanCoilUnits::Calc4PipeFanCoil(state, SysIndex, SZVAVModel.ControlZoneNum, FirstHVACIteration, TempSensOutput, PartLoadRatio);
446 7 : if ((CoolingLoad && ZoneLoad < TempSensOutput) || (HeatingLoad && ZoneLoad > TempSensOutput)) {
447 : // otherwise iterate on load
448 7 : if (SZVAVModel.HCoilType_Num == FanCoilUnits::HCoil::Water || !HeatingLoad) {
449 21 : auto f = [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad, coilFluidInletNode, maxCoilFluidFlow, maxAirMassFlow](
450 : Real64 const PLR) {
451 42 : return FanCoilUnits::CalcFanCoilWaterFlowResidual(state,
452 : PLR,
453 : SysIndex,
454 : FirstHVACIteration,
455 21 : SZVAVModel.ControlZoneNum,
456 : ZoneLoad,
457 21 : SZVAVModel.AirInNode,
458 : coilFluidInletNode,
459 : maxCoilFluidFlow,
460 21 : maxAirMassFlow);
461 3 : };
462 3 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f, 0.0, 1.0);
463 3 : } else {
464 12 : auto f = [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad](Real64 const PartLoadRatio) {
465 24 : return FanCoilUnits::CalcFanCoilLoadResidual(
466 12 : state, SysIndex, FirstHVACIteration, SZVAVModel.ControlZoneNum, ZoneLoad, PartLoadRatio);
467 4 : };
468 4 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f, 0.0, 1.0);
469 : }
470 7 : if (SolFlag < 0) {
471 0 : MessagePrefix = "Step 3: ";
472 : }
473 7 : } else { // too much capacity at full air flow with coil off, operate coil and fan in unison
474 0 : if (SZVAVModel.HCoilType_Num == FanCoilUnits::HCoil::Water || !HeatingLoad) {
475 0 : auto f2 = [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad, coilFluidInletNode](Real64 const PLR) {
476 0 : return FanCoilUnits::CalcFanCoilAirAndWaterFlowResidual(state,
477 : PLR,
478 : SysIndex,
479 : FirstHVACIteration,
480 0 : SZVAVModel.ControlZoneNum,
481 : ZoneLoad,
482 0 : SZVAVModel.AirInNode,
483 : coilFluidInletNode,
484 0 : 0.0);
485 0 : };
486 0 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f2, 0.0, 1.0);
487 0 : } else {
488 0 : auto f = [&state, SysIndex, FirstHVACIteration, &SZVAVModel, ZoneLoad](Real64 const PartLoadRatio) {
489 0 : return FanCoilUnits::CalcFanCoilLoadResidual(
490 0 : state, SysIndex, FirstHVACIteration, SZVAVModel.ControlZoneNum, ZoneLoad, PartLoadRatio);
491 0 : };
492 0 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f, 0.0, 1.0);
493 : }
494 0 : if (SolFlag < 0) {
495 0 : MessagePrefix = "Step 3a: ";
496 : }
497 : }
498 : }
499 :
500 9 : if (coilPlantLoc.loopNum > 0)
501 5 : PlantUtilities::SetComponentFlowRate(
502 5 : state, state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
503 : }
504 :
505 11 : if (SolFlag < 0) {
506 0 : if (SolFlag == -1) {
507 : // get capacity for warning
508 0 : FanCoilUnits::Calc4PipeFanCoil(state, SysIndex, SZVAVModel.ControlZoneNum, FirstHVACIteration, TempSensOutput, PartLoadRatio);
509 :
510 0 : if (std::abs(TempSensOutput - ZoneLoad) * SZVAVModel.ControlZoneMassFlowFrac >
511 : 15.0) { // water coil can provide same output at varying water PLR (model discontinuity?)
512 0 : if (SZVAVModel.MaxIterIndex == 0) {
513 0 : ShowWarningMessage(
514 0 : state, format("{}Coil control failed to converge for {}:{}", MessagePrefix, SZVAVModel.UnitType, SZVAVModel.Name));
515 0 : ShowContinueError(state, " Iteration limit exceeded in calculating system sensible part-load ratio.");
516 0 : ShowContinueErrorTimeStamp(
517 : state,
518 0 : format("Sensible load to be met = {:.2T} (watts), sensible output = {:.2T} (watts), and the simulation continues.",
519 : ZoneLoad,
520 : TempSensOutput));
521 : }
522 0 : ShowRecurringWarningErrorAtEnd(
523 : state,
524 0 : SZVAVModel.UnitType + " \"" + SZVAVModel.Name +
525 : "\" - Iteration limit exceeded in calculating sensible part-load ratio error continues. Sensible load statistics:",
526 0 : SZVAVModel.MaxIterIndex,
527 : ZoneLoad,
528 : ZoneLoad);
529 : }
530 0 : } else if (SolFlag == -2) {
531 0 : if (SZVAVModel.RegulaFalsiFailedIndex == 0) {
532 0 : ShowWarningMessage(state, format("{}Coil control failed for {}:{}", MessagePrefix, SZVAVModel.UnitType, SZVAVModel.Name));
533 0 : ShowContinueError(state, " sensible part-load ratio determined to be outside the range of 0-1.");
534 0 : ShowContinueErrorTimeStamp(state, format("Sensible load to be met = {:.2T} (watts), and the simulation continues.", ZoneLoad));
535 : }
536 0 : ShowRecurringWarningErrorAtEnd(state,
537 0 : SZVAVModel.UnitType + " \"" + SZVAVModel.Name +
538 : "\" - sensible part-load ratio out of range error continues. Sensible load statistics:",
539 0 : SZVAVModel.RegulaFalsiFailedIndex,
540 : ZoneLoad,
541 : ZoneLoad);
542 : }
543 : }
544 11 : }
545 :
546 21 : void calcSZVAVModel(EnergyPlusData &state,
547 : UnitarySystems::UnitarySys &SZVAVModel,
548 : int const SysIndex,
549 : bool const FirstHVACIteration,
550 : bool const CoolingLoad,
551 : bool const HeatingLoad,
552 : Real64 const ZoneLoad,
553 : Real64 &OnOffAirFlowRatio,
554 : bool const HXUnitOn,
555 : int const AirLoopNum,
556 : Real64 &PartLoadRatio,
557 : HVAC::CompressorOp const CompressorONFlag)
558 : {
559 :
560 21 : UnitarySystems::UnitarySys &thisSys = state.dataUnitarySystems->unitarySys[SysIndex];
561 :
562 21 : int constexpr MaxIter(100); // maximum number of iterations
563 21 : int SolFlag(0); // return flag from RegulaFalsi for sensible load
564 21 : std::string MessagePrefix; // label for warning reporting
565 :
566 21 : Real64 boundaryLoadMet(0.0);
567 21 : Real64 minHumRat(0.0);
568 21 : Real64 outletTemp(0.0);
569 21 : bool coilActive(false);
570 21 : Real64 AirMassFlow(0.0);
571 :
572 21 : Real64 maxCoilFluidFlow(0.0);
573 21 : Real64 maxOutletTemp(0.0);
574 21 : Real64 minAirMassFlow(0.0);
575 21 : Real64 maxAirMassFlow(0.0);
576 21 : Real64 lowSpeedFanRatio(0.0);
577 21 : int coilFluidInletNode(0);
578 21 : int coilFluidOutletNode(0);
579 21 : PlantLocation coilPlantLoc{};
580 21 : int coilAirInletNode(0);
581 21 : int coilAirOutletNode(0);
582 21 : Real64 HeatCoilLoad(0.0);
583 21 : Real64 SupHeaterLoad(0.0);
584 :
585 : Real64 TempSensOutput; // iterative sensible capacity [W]
586 : Real64 TempLatOutput; // iterative latent capacity [W]
587 :
588 : // set up mode specific variables to use in common function calls
589 21 : if (CoolingLoad) {
590 7 : maxCoilFluidFlow = SZVAVModel.MaxCoolCoilFluidFlow;
591 7 : maxOutletTemp = SZVAVModel.DesignMinOutletTemp;
592 7 : minAirMassFlow = SZVAVModel.MaxNoCoolHeatAirMassFlow;
593 7 : maxAirMassFlow = SZVAVModel.MaxCoolAirMassFlow;
594 7 : lowSpeedFanRatio = SZVAVModel.LowSpeedCoolFanRatio;
595 7 : coilFluidInletNode = SZVAVModel.CoolCoilFluidInletNode;
596 7 : coilFluidOutletNode = SZVAVModel.CoolCoilFluidOutletNodeNum;
597 7 : coilPlantLoc = SZVAVModel.CoolCoilPlantLoc;
598 7 : coilAirInletNode = SZVAVModel.CoolCoilInletNodeNum;
599 7 : coilAirOutletNode = SZVAVModel.CoolCoilOutletNodeNum;
600 14 : } else if (HeatingLoad) {
601 14 : maxCoilFluidFlow = SZVAVModel.MaxHeatCoilFluidFlow;
602 14 : maxOutletTemp = SZVAVModel.DesignMaxOutletTemp;
603 14 : minAirMassFlow = SZVAVModel.MaxNoCoolHeatAirMassFlow;
604 14 : maxAirMassFlow = SZVAVModel.MaxHeatAirMassFlow;
605 14 : lowSpeedFanRatio = SZVAVModel.LowSpeedHeatFanRatio;
606 14 : coilFluidInletNode = SZVAVModel.HeatCoilFluidInletNode;
607 14 : coilFluidOutletNode = SZVAVModel.HeatCoilFluidOutletNodeNum;
608 14 : coilPlantLoc = SZVAVModel.HeatCoilPlantLoc;
609 14 : coilAirInletNode = SZVAVModel.HeatCoilInletNodeNum;
610 14 : coilAirOutletNode = SZVAVModel.HeatCoilOutletNodeNum;
611 : } else { // should never get here, protect against uninitialized variables
612 0 : maxCoilFluidFlow = 0.0;
613 0 : maxOutletTemp = 0.0;
614 0 : minAirMassFlow = 0.0;
615 0 : maxAirMassFlow = 0.0;
616 0 : lowSpeedFanRatio = 0.0;
617 0 : coilFluidInletNode = 0;
618 0 : coilFluidOutletNode = 0;
619 0 : coilPlantLoc = {0, DataPlant::LoopSideLocation::Invalid, 0, 0};
620 0 : coilAirInletNode = 0;
621 0 : coilAirOutletNode = 0;
622 : }
623 : // set up RegulaFalsi variables
624 21 : int InletNode = SZVAVModel.AirInNode;
625 21 : Real64 InletTemp = state.dataLoopNodes->Node(InletNode).Temp;
626 21 : int OutletNode = SZVAVModel.AirOutNode;
627 21 : Real64 ZoneTemp = state.dataLoopNodes->Node(SZVAVModel.NodeNumOfControlledZone).Temp;
628 21 : Real64 ZoneHumRat = state.dataLoopNodes->Node(SZVAVModel.NodeNumOfControlledZone).HumRat;
629 :
630 : // model attempts to control air flow rate and coil capacity in specific operating regions:
631 : // Region 1 (R1) - minimum air flow rate at modulated coil capacity (up to min/max temperature limits)
632 : // Region 2 (R2) - modulated air flow rate and coil capacity (up to max air flow rate while maintaining min/max temperature limits)
633 : // Region 3 (R3) - maximum air flow rate and modulated/increased coil capacity (allow increased capacity at full air flow rate to meet
634 : // remaining load)
635 : //
636 : // | | | | ^ ^ = supply air temperature
637 : // | | | | ^ * = supply air flow rate
638 : // | | |^^^^| <--- maximum supply air temperature
639 : // | | ^ | |
640 : // | | ^ | |
641 : // ***********| | ^ | |************** <-- max unit air flow rate
642 : // |* | ^ | *|
643 : // | * | ^ | * |
644 : // | * | ^ | * |
645 : // | *| ^ |* |
646 : // | |*******************| | <-- min unit air flow rate
647 : // R3 | R2 | ^ R1 | R2 | R3
648 : // min SAT --> |^^^^| | |
649 : // ^
650 : // ^ |
651 : // (-) increasing cooling load < 0 > increasing heating load (+)
652 : //
653 : // Notes: SAT is allowed to decrease/increase once air flow rate is max'ed out otherwise load would not be met
654 : // 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
655 : // capacity) lowSpeedFanRatio = min/max unit air flow rate
656 : //
657 : // Step 1: calculate load at Region 1 lower (cooling) or upper (heating) boundary at minimum air flow rate
658 : // - if load can be met, test maximum output (PLR = 1) before calling RootSolver
659 : // - if maximum capacity is greater than load, solve for PLR
660 : // Step 2: calculate load at Region 3 lower (cooling) or upper (heating) boundary at maximum air flow rate
661 : // - if load is less than boundary load, solve for air flow and PLR that meet the load
662 : // - ELSE
663 : // Step 3: solve for Region 3 PLR
664 : // DONE
665 : //
666 :
667 : // Step 1: Determine boundary for region 1
668 : // calculate sensible load based on minimum air flow rate and specified supply air temperature limit
669 21 : if (SZVAVModel.ATMixerExists) {
670 0 : if (SZVAVModel.ATMixerType == HVAC::MixerType::SupplySide) {
671 : // Air terminal supply side mixer
672 0 : boundaryLoadMet =
673 0 : minAirMassFlow * (Psychrometrics::PsyHFnTdbW(state.dataLoopNodes->Node(SZVAVModel.ATMixerOutNode).Temp, ZoneHumRat) -
674 0 : Psychrometrics::PsyHFnTdbW(ZoneTemp, ZoneHumRat));
675 : } else {
676 : // Air terminal inlet side mixer
677 0 : boundaryLoadMet =
678 0 : minAirMassFlow * (Psychrometrics::PsyHFnTdbW(maxOutletTemp, ZoneHumRat) - Psychrometrics::PsyHFnTdbW(ZoneTemp, ZoneHumRat));
679 : }
680 : } else {
681 21 : minHumRat = min(state.dataLoopNodes->Node(InletNode).HumRat, state.dataLoopNodes->Node(OutletNode).HumRat);
682 21 : boundaryLoadMet =
683 21 : minAirMassFlow * (Psychrometrics::PsyHFnTdbW(maxOutletTemp, minHumRat) - Psychrometrics::PsyHFnTdbW(InletTemp, minHumRat));
684 : }
685 :
686 21 : if ((CoolingLoad && boundaryLoadMet < ZoneLoad) || (HeatingLoad && boundaryLoadMet > ZoneLoad)) { // in Region 1 of figure
687 : // Step 1: set min air flow and full coil capacity
688 10 : PartLoadRatio = 1.0; // full coil capacity
689 10 : SZVAVModel.FanPartLoadRatio =
690 : 0.0; // minimum fan PLR, air flow = ( fanPartLoadRatio * maxAirMassFlow ) + ( ( 1.0 - fanPartLoadRatio ) * minAirMassFlow )
691 10 : state.dataLoopNodes->Node(InletNode).MassFlowRate = minAirMassFlow;
692 : // set max water flow rate and check to see if plant limits flow
693 10 : if (coilPlantLoc.loopNum > 0)
694 4 : PlantUtilities::SetComponentFlowRate(state, maxCoilFluidFlow, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
695 :
696 10 : if (CoolingLoad) { // Function CalcUnitarySystemToLoad, 4th and 5th arguments are CoolPLR and HeatPLR
697 : // set the water flow ratio so water coil gets proper flow
698 3 : if (SZVAVModel.MaxCoolCoilFluidFlow > 0.0) SZVAVModel.CoolCoilWaterFlowRatio = maxCoilFluidFlow / SZVAVModel.MaxCoolCoilFluidFlow;
699 3 : thisSys.calcUnitarySystemToLoad(state,
700 : AirLoopNum,
701 : FirstHVACIteration,
702 : PartLoadRatio,
703 : 0.0,
704 : OnOffAirFlowRatio,
705 : TempSensOutput,
706 : TempLatOutput,
707 : HXUnitOn,
708 : HeatCoilLoad,
709 : SupHeaterLoad,
710 : CompressorONFlag);
711 : } else {
712 7 : if (SZVAVModel.MaxHeatCoilFluidFlow > 0.0) SZVAVModel.HeatCoilWaterFlowRatio = maxCoilFluidFlow / SZVAVModel.MaxHeatCoilFluidFlow;
713 7 : thisSys.calcUnitarySystemToLoad(state,
714 : AirLoopNum,
715 : FirstHVACIteration,
716 : 0.0,
717 : PartLoadRatio,
718 : OnOffAirFlowRatio,
719 : TempSensOutput,
720 : TempLatOutput,
721 : HXUnitOn,
722 : ZoneLoad,
723 : SupHeaterLoad,
724 : CompressorONFlag);
725 : }
726 10 : coilActive = state.dataLoopNodes->Node(coilAirInletNode).Temp - state.dataLoopNodes->Node(coilAirOutletNode).Temp;
727 :
728 10 : if (!coilActive) { // if the coil is schedule off or the plant cannot provide water
729 2 : if (coilPlantLoc.loopNum > 0) {
730 1 : state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate = 0.0;
731 1 : PlantUtilities::SetComponentFlowRate(
732 1 : state, state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
733 : }
734 2 : return;
735 : }
736 :
737 8 : if ((CoolingLoad && TempSensOutput < ZoneLoad) || (HeatingLoad && TempSensOutput > ZoneLoad)) { // low speed fan can meet load
738 38 : auto f = [&state,
739 : SysIndex,
740 : FirstHVACIteration,
741 : ZoneLoad,
742 : &SZVAVModel,
743 8 : OnOffAirFlowRatio,
744 : AirLoopNum,
745 : coilFluidInletNode,
746 : lowSpeedFanRatio,
747 : maxCoilFluidFlow,
748 : minAirMassFlow,
749 : maxAirMassFlow,
750 : CoolingLoad](Real64 const PartLoadRatio) {
751 76 : return UnitarySystems::UnitarySys::calcUnitarySystemWaterFlowResidual(state,
752 : PartLoadRatio, // coil part load ratio
753 : SysIndex,
754 : FirstHVACIteration,
755 : ZoneLoad,
756 38 : SZVAVModel.AirInNode,
757 : OnOffAirFlowRatio,
758 : AirLoopNum,
759 : coilFluidInletNode,
760 : maxCoilFluidFlow,
761 : lowSpeedFanRatio,
762 : minAirMassFlow,
763 : 0.0,
764 : maxAirMassFlow,
765 : CoolingLoad,
766 38 : 1.0);
767 8 : };
768 8 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f, 0.0, 1.0);
769 8 : if (SolFlag < 0) {
770 0 : MessagePrefix = "Step 1: ";
771 : }
772 :
773 8 : if (coilPlantLoc.loopNum > 0)
774 3 : PlantUtilities::SetComponentFlowRate(
775 3 : state, state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
776 : }
777 :
778 8 : } else {
779 :
780 : // Step 2: Load is greater than allowed in region 1, determine boundary load for region 3
781 : // only difference in this calculation is using maxAirMassFlow instead of minAirMassFlow, just use the ratio to adjust previous
782 : // calculation
783 11 : boundaryLoadMet *= maxAirMassFlow / minAirMassFlow;
784 :
785 11 : if ((CoolingLoad && boundaryLoadMet < ZoneLoad) || (HeatingLoad && boundaryLoadMet > ZoneLoad)) { // in Region 2 of figure
786 :
787 6 : outletTemp = state.dataLoopNodes->Node(OutletNode).Temp;
788 6 : minHumRat = state.dataLoopNodes->Node(SZVAVModel.NodeNumOfControlledZone).HumRat;
789 6 : if (outletTemp < ZoneTemp) minHumRat = state.dataLoopNodes->Node(OutletNode).HumRat;
790 6 : outletTemp = maxOutletTemp;
791 6 : AirMassFlow = min(maxAirMassFlow,
792 6 : (ZoneLoad / (Psychrometrics::PsyHFnTdbW(outletTemp, minHumRat) - Psychrometrics::PsyHFnTdbW(ZoneTemp, minHumRat))));
793 6 : AirMassFlow = max(minAirMassFlow, AirMassFlow);
794 6 : SZVAVModel.FanPartLoadRatio = ((AirMassFlow - (maxAirMassFlow * lowSpeedFanRatio)) / ((1.0 - lowSpeedFanRatio) * maxAirMassFlow));
795 :
796 33 : auto f = [&state,
797 : SysIndex,
798 : FirstHVACIteration,
799 : ZoneLoad,
800 : &SZVAVModel,
801 6 : OnOffAirFlowRatio,
802 : AirLoopNum,
803 : coilFluidInletNode,
804 : lowSpeedFanRatio,
805 : AirMassFlow,
806 : maxAirMassFlow,
807 : CoolingLoad,
808 : maxCoilFluidFlow](Real64 const PartLoadRatio) {
809 66 : return UnitarySystems::UnitarySys::calcUnitarySystemWaterFlowResidual(state,
810 : PartLoadRatio, // coil part load ratio
811 : SysIndex,
812 : FirstHVACIteration,
813 : ZoneLoad,
814 33 : SZVAVModel.AirInNode,
815 : OnOffAirFlowRatio,
816 : AirLoopNum,
817 : coilFluidInletNode,
818 : maxCoilFluidFlow,
819 : lowSpeedFanRatio,
820 : AirMassFlow,
821 : 0.0,
822 : maxAirMassFlow,
823 : CoolingLoad,
824 33 : 1.0);
825 6 : };
826 6 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f, 0.0, 1.0);
827 6 : if (SolFlag < 0) {
828 0 : MessagePrefix = "Step 2: ";
829 : }
830 :
831 6 : } else { // in region 3 of figure
832 :
833 5 : PartLoadRatio = 1.0; // full coil capacity
834 5 : SZVAVModel.FanPartLoadRatio =
835 : 1.0; // minimum fan PLR, air flow = ( fanPartLoadRatio * maxAirMassFlow ) + ( ( 1.0 - fanPartLoadRatio ) * minAirMassFlow )
836 5 : state.dataLoopNodes->Node(InletNode).MassFlowRate = maxAirMassFlow;
837 : // set max water flow rate and check to see if plant limits flow
838 5 : if (coilPlantLoc.loopNum > 0)
839 2 : PlantUtilities::SetComponentFlowRate(state, maxCoilFluidFlow, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
840 :
841 5 : if (CoolingLoad) { // Function CalcUnitarySystemToLoad, 4th and 5th arguments are CoolPLR and HeatPLR
842 : // set the water flow ratio so water coil gets proper flow
843 2 : if (SZVAVModel.MaxCoolCoilFluidFlow > 0.0) SZVAVModel.CoolCoilWaterFlowRatio = maxCoilFluidFlow / SZVAVModel.MaxCoolCoilFluidFlow;
844 2 : thisSys.calcUnitarySystemToLoad(state,
845 : AirLoopNum,
846 : FirstHVACIteration,
847 : PartLoadRatio,
848 : 0.0,
849 : OnOffAirFlowRatio,
850 : TempSensOutput,
851 : TempLatOutput,
852 : HXUnitOn,
853 : HeatCoilLoad,
854 : SupHeaterLoad,
855 : CompressorONFlag);
856 : } else {
857 3 : if (SZVAVModel.MaxHeatCoilFluidFlow > 0.0) SZVAVModel.HeatCoilWaterFlowRatio = maxCoilFluidFlow / SZVAVModel.MaxHeatCoilFluidFlow;
858 3 : thisSys.calcUnitarySystemToLoad(state,
859 : AirLoopNum,
860 : FirstHVACIteration,
861 : 0.0,
862 : PartLoadRatio,
863 : OnOffAirFlowRatio,
864 : TempSensOutput,
865 : TempLatOutput,
866 : HXUnitOn,
867 : ZoneLoad,
868 : SupHeaterLoad,
869 : CompressorONFlag);
870 : }
871 5 : coilActive = state.dataLoopNodes->Node(coilAirInletNode).Temp - state.dataLoopNodes->Node(coilAirOutletNode).Temp;
872 5 : if (!coilActive) { // if the coil is schedule off or the plant cannot provide water
873 0 : if (coilPlantLoc.loopNum > 0) {
874 0 : state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate = 0.0;
875 0 : PlantUtilities::SetComponentFlowRate(
876 0 : state, state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
877 : }
878 1 : return;
879 : }
880 :
881 5 : if ((CoolingLoad && ZoneLoad < TempSensOutput) || (HeatingLoad && ZoneLoad > TempSensOutput))
882 1 : return; // system cannot meet load, leave at max capacity
883 :
884 : // otherwise iterate on load
885 24 : auto f = [&state,
886 : SysIndex,
887 : FirstHVACIteration,
888 : ZoneLoad,
889 : &SZVAVModel,
890 4 : OnOffAirFlowRatio,
891 : AirLoopNum,
892 : coilFluidInletNode,
893 : lowSpeedFanRatio,
894 : maxCoilFluidFlow,
895 : maxAirMassFlow,
896 : CoolingLoad](Real64 const PartLoadRatio) {
897 48 : return UnitarySystems::UnitarySys::calcUnitarySystemWaterFlowResidual(state,
898 : PartLoadRatio, // coil part load ratio
899 : SysIndex,
900 : FirstHVACIteration,
901 : ZoneLoad,
902 24 : SZVAVModel.AirInNode,
903 : OnOffAirFlowRatio,
904 : AirLoopNum,
905 : coilFluidInletNode,
906 : maxCoilFluidFlow,
907 : lowSpeedFanRatio,
908 : maxAirMassFlow,
909 : 0.0,
910 : maxAirMassFlow,
911 : CoolingLoad,
912 24 : 1.0);
913 4 : };
914 4 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, f, 0.0, 1.0);
915 : // Par[12] = maxAirMassFlow; // operating air flow rate, minAirMassFlow indicates low speed air flow rate,
916 : // maxAirMassFlow indicates full
917 : // // air flow
918 : // Par[13] = 0.0; // SA Temp target, 0 means iterate on load and not SA temperature
919 : // General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadRatio, thisSys.calcUnitarySystemWaterFlowResidual,
920 : // 0.0, 1.0, Par);
921 4 : if (SolFlag < 0) {
922 0 : MessagePrefix = "Step 3: ";
923 : }
924 : }
925 :
926 10 : if (coilPlantLoc.loopNum > 0)
927 4 : PlantUtilities::SetComponentFlowRate(
928 4 : state, state.dataLoopNodes->Node(coilFluidInletNode).MassFlowRate, coilFluidInletNode, coilFluidOutletNode, coilPlantLoc);
929 : }
930 :
931 18 : if (SolFlag < 0) {
932 0 : if (SolFlag == -1) {
933 : // get capacity for warning
934 0 : if (CoolingLoad) { // Function CalcUnitarySystemToLoad, 4th and 5th arguments are CoolPLR and HeatPLR
935 0 : thisSys.calcUnitarySystemToLoad(state,
936 : AirLoopNum,
937 : FirstHVACIteration,
938 : PartLoadRatio,
939 : 0.0,
940 : OnOffAirFlowRatio,
941 : TempSensOutput,
942 : TempLatOutput,
943 : HXUnitOn,
944 : HeatCoilLoad,
945 : SupHeaterLoad,
946 : CompressorONFlag);
947 : } else {
948 0 : thisSys.calcUnitarySystemToLoad(state,
949 : AirLoopNum,
950 : FirstHVACIteration,
951 : 0.0,
952 : PartLoadRatio,
953 : OnOffAirFlowRatio,
954 : TempSensOutput,
955 : TempLatOutput,
956 : HXUnitOn,
957 : ZoneLoad,
958 : SupHeaterLoad,
959 : CompressorONFlag);
960 : }
961 :
962 0 : if (std::abs(TempSensOutput - ZoneLoad) * SZVAVModel.ControlZoneMassFlowFrac >
963 : 15.0) { // water coil can provide same output at varying water PLR (model discontinuity?)
964 0 : if (SZVAVModel.MaxIterIndex == 0) {
965 0 : ShowWarningMessage(
966 0 : state, format("{}Coil control failed to converge for {}:{}", MessagePrefix, SZVAVModel.UnitType, SZVAVModel.Name));
967 0 : ShowContinueError(state, " Iteration limit exceeded in calculating system sensible part-load ratio.");
968 0 : ShowContinueErrorTimeStamp(
969 : state,
970 0 : format("Sensible load to be met = {:.2T} (watts), sensible output = {:.2T} (watts), and the simulation continues.",
971 : ZoneLoad,
972 : TempSensOutput));
973 : }
974 0 : ShowRecurringWarningErrorAtEnd(
975 : state,
976 0 : SZVAVModel.UnitType + " \"" + SZVAVModel.Name +
977 : "\" - Iteration limit exceeded in calculating sensible part-load ratio error continues. Sensible load statistics:",
978 0 : SZVAVModel.MaxIterIndex,
979 : ZoneLoad,
980 : ZoneLoad);
981 : }
982 0 : } else if (SolFlag == -2) {
983 0 : if (SZVAVModel.RegulaFalsiFailedIndex == 0) {
984 0 : ShowWarningMessage(state, format("{}Coil control failed for {}:{}", MessagePrefix, SZVAVModel.UnitType, SZVAVModel.Name));
985 0 : ShowContinueError(state, " sensible part-load ratio determined to be outside the range of 0-1.");
986 0 : ShowContinueErrorTimeStamp(state, format("Sensible load to be met = {:.2T} (watts), and the simulation continues.", ZoneLoad));
987 : }
988 0 : ShowRecurringWarningErrorAtEnd(state,
989 0 : SZVAVModel.UnitType + " \"" + SZVAVModel.Name +
990 : "\" - sensible part-load ratio out of range error continues. Sensible load statistics:",
991 0 : SZVAVModel.RegulaFalsiFailedIndex,
992 : ZoneLoad,
993 : ZoneLoad);
994 : }
995 : }
996 21 : }
997 :
998 : } // namespace SZVAVModel
999 :
1000 : } // namespace EnergyPlus
|