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 : // ObjexxFCL Headers
49 : #include <ObjexxFCL/Array.functions.hh>
50 : #include <ObjexxFCL/Array2D.hh>
51 : #include <ObjexxFCL/Fmath.hh>
52 : #include <ObjexxFCL/numeric.hh>
53 : #include <ObjexxFCL/string.functions.hh>
54 :
55 : // EnergyPlus Headers
56 : #include <EnergyPlus/Autosizing/Base.hh>
57 : #include <EnergyPlus/Data/EnergyPlusData.hh>
58 : #include <EnergyPlus/DataAirSystems.hh>
59 : #include <EnergyPlus/DataConvergParams.hh>
60 : #include <EnergyPlus/DataEnvironment.hh>
61 : #include <EnergyPlus/DataHVACGlobals.hh>
62 : #include <EnergyPlus/DataLoopNode.hh>
63 : #include <EnergyPlus/DataPrecisionGlobals.hh>
64 : #include <EnergyPlus/DataSizing.hh>
65 : #include <EnergyPlus/DataSystemVariables.hh>
66 : #include <EnergyPlus/EMSManager.hh>
67 : #include <EnergyPlus/FaultsManager.hh>
68 : #include <EnergyPlus/FluidProperties.hh>
69 : #include <EnergyPlus/General.hh>
70 : #include <EnergyPlus/HVACControllers.hh>
71 : #include <EnergyPlus/IOFiles.hh>
72 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
73 : #include <EnergyPlus/MixedAir.hh>
74 : #include <EnergyPlus/NodeInputManager.hh>
75 : #include <EnergyPlus/PlantUtilities.hh>
76 : #include <EnergyPlus/RootFinder.hh>
77 : #include <EnergyPlus/SetPointManager.hh>
78 : #include <EnergyPlus/UtilityRoutines.hh>
79 : #include <EnergyPlus/WaterCoils.hh>
80 :
81 : namespace EnergyPlus::HVACControllers {
82 : // Module containing the controller simulation routines for the air loop
83 :
84 : // MODULE INFORMATION:
85 : // AUTHOR Richard J. Liesen
86 : // DATE WRITTEN July 1998
87 : // MODIFIED Feb 2006, Dimitri Curtil (LBNL)
88 : // - Added tracing mechanism for debugging convergence process.
89 : // - Trace operation of each individual controller in a file named
90 : // 'controller.<Controller Name>.csv'
91 : // - Trace operation of all controllers per air loop in a file named
92 : // 'controller.<Air Loop Name>.csv'
93 : // - Added operations to enable cold start/speculative warm restart
94 : // and final check.
95 : // MODIFIED March 2006, Dimitri Curtil (LBNL)
96 : // - Added mechanism to track runtime performance statistics.
97 : // - Added routine to dump controller statistics to a file named
98 : // 'statistics.HVACControllers.csv'
99 : // - Integrated smart root finder from MODULE RootFinder implemented in
100 : // file RootFinder.cc.
101 : // MODIFIED April 2006, Dimitri Curtil (LBNL)
102 : // - Added speedup optimization scheme to reuse air loop solution
103 : // obtained at the current HVAC iteration from solving the previous controller
104 : // on the loop (see ReuseIntermediateSolutionFlag). Of course this works only
105 : // if there are 2 or more controllers on the same air loop.
106 : // - Added speedup optimization scheme to reuse solution obtained
107 : // at the previous HVAC iteration for this controller during the
108 : // bracketing phase (see ReusePreviousSolutionFlag).
109 : // MODIFIED May 2006, Dimitri Curtil (LBNL)
110 : // - Added mechanism to monitor min/max bounds to ensure that they remain invariant
111 : // between successive controller iterations.
112 : // - Modified setpoint calculation to force the setpoint to be computed only once.
113 : // - Modified setpoint calculation for TEMPandHUMRAT control strategy to
114 : // force the setpoint to be computed once the air loop has been evaluated with
115 : // the max actuated value.
116 : // MODIFIED June 2006, Dimitri Curtil (LBNL)
117 : // - Renamed parameter variables so as to use lower caps.
118 : // - Replaced $ edit descriptor in WRITE statements with ADVANCE='No'
119 : // - Replaced the preprocessing directives TRACK_AIRLOOP, TRACE_AIRLOOP,
120 : // TRACE_CONTROLLER with corresponding environment variables defined
121 : // in DataSystemVariables.cc.
122 : // MODIFIED Feb. 2010, Brent Griffith (NREL)
123 : // - changed plant loop interactions, Demand Side Update Phase 3
124 :
125 : // PURPOSE OF THIS MODULE:
126 : // To encapsulate the data and algorithms required to manage the Controller System Component.
127 :
128 : // METHODOLOGY EMPLOYED:
129 : // The main entry point if the SUBROUTINE ManageControllers().
130 : // 1. For proper operation, the subroutine must first be called with either the
131 : // iControllerOpColdStart or iControllerOpWarmRestart operation code to initialize
132 : // the various controllers.
133 : // 2. Then the actuated variable for each controller is computed iteratively using
134 : // root finding techniques that aim at forcing the sensed variable to be
135 : // "equal" (within the user-specified tolerance) to the desired setpoint.
136 : // This step is achieved by calling ManageController() with the iControllerOpIterate
137 : // operation code.
138 : // 3. Finally, after all controllers have been successfully simulated, the subroutine has
139 : // to be called one last time with the iControllerOpEnd operation code to ensure that
140 : // the sequential solution indeed represents a valid global solution across all controllers
141 : // simultaneously.
142 : // The following pseudo-code shows the typical calling sequence for the SUBROUTINE
143 : // ManageControllers :
144 : // - for each controller on air loop
145 : // - CALL ManageControllers( Operation=<iControllerOpColdStart or iControllerOpWarmRestart> )
146 : // - simulate air loop components with the initial values for all actuated variables
147 : // - for each controller on air loop
148 : // - CALL ManageControllers( Operation=iControllerOpIterate, IsConvergedFlag )
149 : // - if NOT IsConvergedFlag then
150 : // - exit loop with error if too many iterations performed
151 : // - simulate air loop components with the new candidate value for the actuated variable of
152 : // the current controller
153 : // - simulate air loop components with the final values for all actuated variables
154 : // - for each controller on air loop
155 : // - CALL ManageControllers( Operation=iControllerOpEnd, IsConvergedFlag )
156 : // - if NOT IsConvergedFlag then
157 : // - exit loop with error indicating no "global" convergence with final solution.
158 : // Check the subroutines SolveAirLoopControllers() and ReSolveAirLoopControllers()
159 : // invoked in the subroutine SimAirLoop() for the actual calling sequences.
160 :
161 : // OTHER NOTES:
162 : // To enable runtime statistics tracking for each air loop, define the environment variable
163 : // TRACK_AIRLOOP=YES or TRACK_AIRLOOP=Y.
164 : // To enable generating a trace file with the converged solution for all controllers on each air loop,
165 : // define the environment variable TRACE_AIRLOOP=YES or TRACE_AIRLOOP=Y.
166 : // To enable generating an individual, detailed trace file for each controller, define the
167 : // environment variable TRACE_CONTROLLER=YES or TRACE_CONTROLLER=Y.
168 : // See DataSystemVariables.cc for the definitions of the environment variables used to debug
169 : // the air loop simulation.
170 :
171 : // Number of significant digits to display in error messages for floating-point numbers
172 : constexpr int NumSigDigits = 15;
173 : constexpr std::array<std::string_view, static_cast<int>(CtrlVarType::Num)> ctrlVarNamesUC = {
174 : "INVALID-NONE", "TEMPERATURE", "HUMIDITYRATIO", "TEMPERATUREANDHUMIDITYRATIO", "INVALID-FLOW"};
175 : constexpr std::array<std::string_view, static_cast<int>(ControllerAction::Num)> actionNamesUC = {"", "REVERSE", "NORMAL"};
176 :
177 3 : std::string ControlVariableTypes(CtrlVarType const &c)
178 : {
179 3 : switch (c) {
180 0 : case CtrlVarType::NoControlVariable:
181 0 : return "No control variable";
182 3 : case CtrlVarType::Temperature:
183 6 : return "Temperature";
184 0 : case CtrlVarType::HumidityRatio:
185 0 : return "Humidity ratio";
186 0 : case CtrlVarType::TemperatureAndHumidityRatio:
187 0 : return "Temperature and humidity ratio";
188 0 : case CtrlVarType::Flow:
189 0 : return "Flow rate";
190 0 : default:
191 0 : assert(false);
192 : }
193 : return "no controller type found";
194 : }
195 :
196 55412594 : void ManageControllers(EnergyPlusData &state,
197 : std::string const &ControllerName,
198 : int &ControllerIndex,
199 : bool const FirstHVACIteration,
200 : int const AirLoopNum,
201 : DataHVACControllers::ControllerOperation const Operation,
202 : bool &IsConvergedFlag,
203 : bool &IsUpToDateFlag,
204 : bool const BypassOAController,
205 : ObjexxFCL::Optional_bool AllowWarmRestartFlag)
206 : {
207 :
208 : // SUBROUTINE INFORMATION:
209 : // AUTHOR Richard Liesen
210 : // DATE WRITTEN July 1998
211 : // MODIFIED Dimitri Curtil, February 2006
212 : // - Added air loop information
213 : // - Added tracing to csv files
214 : // - Added primitive operations to replace mixed
215 : // bag of ResetController, FirstCallConvergenceTest, ...
216 :
217 : // PURPOSE OF THIS SUBROUTINE:
218 : // This subroutine manages Controller component simulation.
219 :
220 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
221 : // The Controller that you are currently loading input into
222 : int ControlNum;
223 :
224 : // Obtains and Allocates Controller related parameters from input file
225 55412594 : if (state.dataHVACControllers->GetControllerInputFlag) { // First time subroutine has been entered
226 0 : GetControllerInput(state);
227 0 : state.dataHVACControllers->GetControllerInputFlag = false;
228 : }
229 :
230 55412594 : if (ControllerIndex == 0) {
231 0 : ControlNum = Util::FindItemInList(ControllerName, state.dataHVACControllers->ControllerProps, &ControllerPropsType::ControllerName);
232 0 : if (ControlNum == 0) {
233 0 : ShowFatalError(
234 : state,
235 0 : format("ManageControllers: Invalid controller={}. The only valid controller type for an AirLoopHVAC is Controller:WaterCoil.",
236 : ControllerName));
237 : }
238 0 : ControllerIndex = ControlNum;
239 : } else {
240 55412594 : ControlNum = ControllerIndex;
241 55412594 : if (ControlNum > state.dataHVACControllers->NumControllers || ControlNum < 1) {
242 0 : ShowFatalError(state,
243 0 : format("ManageControllers: Invalid ControllerIndex passed={}, Number of controllers={}, Controller name={}",
244 : ControlNum,
245 0 : state.dataHVACControllers->NumControllers,
246 : ControllerName));
247 : }
248 55412594 : if (state.dataHVACControllers->CheckEquipName(ControlNum)) {
249 902 : if (ControllerName != state.dataHVACControllers->ControllerProps(ControlNum).ControllerName) {
250 0 : ShowFatalError(
251 : state,
252 0 : format("ManageControllers: Invalid ControllerIndex passed={}, Controller name={}, stored Controller Name for that index={}",
253 : ControlNum,
254 : ControllerName,
255 0 : state.dataHVACControllers->ControllerProps(ControlNum).ControllerName));
256 : }
257 902 : state.dataHVACControllers->CheckEquipName(ControlNum) = false;
258 : }
259 : }
260 :
261 55412594 : auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
262 :
263 55412594 : if (controllerProps.BypassControllerCalc && BypassOAController) {
264 1905678 : IsUpToDateFlag = true;
265 1905678 : IsConvergedFlag = true;
266 1905678 : if (present(AllowWarmRestartFlag)) {
267 635226 : AllowWarmRestartFlag = true;
268 : }
269 2224518 : return;
270 : }
271 :
272 : // Find the correct ControllerNumber with the AirLoop & CompNum from AirLoop Derived Type
273 : // ControlNum = AirLoopEquip(AirLoopNum)%ComponentOfTypeNum(CompNum)
274 :
275 : // detect if plant is locked and flow cannot change
276 53506916 : if (controllerProps.ActuatedNodePlantLoc.loopNum > 0) {
277 :
278 53506014 : if (state.dataPlnt->PlantLoop(controllerProps.ActuatedNodePlantLoc.loopNum)
279 53506014 : .LoopSide(controllerProps.ActuatedNodePlantLoc.loopSideNum)
280 53506014 : .FlowLock == DataPlant::FlowLock::Locked) {
281 : // plant is rigid so controller cannot change anything.
282 : // Update the current Controller to the outlet nodes
283 318840 : UpdateController(state, ControlNum);
284 :
285 318840 : IsConvergedFlag = true;
286 318840 : return;
287 : }
288 : }
289 :
290 : // Detect if speculative warm restart is supported by this computer
291 53188076 : if (present(AllowWarmRestartFlag)) {
292 : // NOTE: Never allow speculative warm restart with dual humidity ratio and temperature control
293 : // because the actual setpoint depends on the current temperature and max hum ratio at
294 : // the sensed node, and therefore might not be known until after one air loop simulation.
295 10395381 : if (controllerProps.ControlVar == CtrlVarType::TemperatureAndHumidityRatio) {
296 624430 : AllowWarmRestartFlag = false;
297 : } else {
298 9770951 : AllowWarmRestartFlag = true;
299 : }
300 : }
301 :
302 53188076 : if (controllerProps.InitFirstPass) {
303 : // Coil must first be sized to:
304 : // Initialize controllerProps%MinActuated and controllerProps%MaxActuated
305 902 : InitController(state, ControlNum, IsConvergedFlag);
306 902 : controllerProps.InitFirstPass = false;
307 : }
308 :
309 : // Perform requested operation
310 : // Note that InitController() is not called upon START/RESTART ops in order to avoid
311 : // side-effects on the calculation of Node(ActuatedNode)%MassFlowRateMaxAvail used to
312 : // determine controllerProps%MaxAvailActuated.
313 : // Plant upgrades for V7 added init to these cases because MassFlowRateMaxAvail is better controlled
314 53188076 : switch (Operation) {
315 10395381 : case DataHVACControllers::ControllerOperation::ColdStart: {
316 : // For temperature and humidity control reset humidity control override if it was set
317 10395381 : if (controllerProps.HumRatCtrlOverride) {
318 70166 : controllerProps.HumRatCtrlOverride = false;
319 : // Put the controller tolerance (offset) back to it's original value
320 70166 : RootFinder::SetupRootFinder(state,
321 70166 : state.dataHVACControllers->RootFinders(ControlNum),
322 : DataRootFinder::Slope::Decreasing,
323 : DataRootFinder::RootFinderMethod::Brent,
324 : DataPrecisionGlobals::constant_zero,
325 : 1.0e-6,
326 : controllerProps.Offset);
327 : }
328 :
329 : // If a iControllerOpColdStart call, reset the actuator inlet flows
330 10395381 : ResetController(state, ControlNum, false, IsConvergedFlag);
331 : // Update the current Controller to the outlet nodes
332 10395381 : UpdateController(state, ControlNum);
333 10395381 : } break;
334 5216889 : case DataHVACControllers::ControllerOperation::WarmRestart: {
335 : // If a iControllerOpWarmRestart call, set the actuator inlet flows to previous solution
336 5216889 : ResetController(state, ControlNum, true, IsConvergedFlag);
337 : // Update the current Controller to the outlet nodes
338 5216889 : UpdateController(state, ControlNum);
339 5216889 : } break;
340 21963536 : case DataHVACControllers::ControllerOperation::Iterate: {
341 : // With the correct ControlNum Initialize all Controller related parameters
342 21963536 : InitController(state, ControlNum, IsConvergedFlag);
343 :
344 : // No initialization needed: should have been done before
345 : // Simulate the correct Controller with the current ControlNum
346 21963536 : int ControllerType = controllerProps.ControllerType_Num;
347 :
348 21963536 : if (ControllerType == ControllerSimple_Type) { // 'Controller:WaterCoil'
349 21963536 : CalcSimpleController(state, ControlNum, FirstHVACIteration, IsConvergedFlag, IsUpToDateFlag, ControllerName);
350 : } else {
351 0 : ShowFatalError(state, format("Invalid controller type in ManageControllers={}", controllerProps.ControllerType));
352 : }
353 :
354 : // Update the current Controller to the outlet nodes
355 21963536 : UpdateController(state, ControlNum);
356 :
357 21963536 : CheckTempAndHumRatCtrl(state, ControlNum, IsConvergedFlag);
358 :
359 21963536 : } break;
360 15612270 : case DataHVACControllers::ControllerOperation::End: {
361 : // With the correct ControlNum Initialize all Controller related parameters
362 15612270 : InitController(state, ControlNum, IsConvergedFlag);
363 :
364 : // No initialization needed: should have been done before
365 : // Check convergence for the correct Controller with the current ControlNum
366 15612270 : int ControllerType = controllerProps.ControllerType_Num;
367 :
368 15612270 : if (ControllerType == ControllerSimple_Type) { // 'Controller:WaterCoil'
369 15612270 : CheckSimpleController(state, ControlNum, IsConvergedFlag);
370 15612270 : SaveSimpleController(state, ControlNum, FirstHVACIteration, IsConvergedFlag);
371 : } else {
372 0 : ShowFatalError(state, format("Invalid controller type in ManageControllers={}", controllerProps.ControllerType));
373 : }
374 :
375 15612270 : } break;
376 0 : default: {
377 0 : ShowFatalError(state, format("ManageControllers: Invalid Operation passed={}, Controller name={}", Operation, ControllerName));
378 0 : } break;
379 : }
380 :
381 : // Write detailed diagnostic for individual controller
382 : // To enable generating an individual, detailed trace file for each controller on each air loop,
383 : // define the environment variable TRACE_CONTROLLER=YES or TRACE_CONTROLLER=Y
384 53188076 : if (state.dataSysVars->TraceHVACControllerEnvFlag) {
385 0 : TraceIndividualController(
386 0 : state, ControlNum, FirstHVACIteration, state.dataAirLoop->AirLoopControlInfo(AirLoopNum).AirLoopPass, Operation, IsConvergedFlag);
387 : }
388 : }
389 :
390 : // Get Input Section of the Module
391 : //******************************************************************************
392 :
393 326 : void GetControllerInput(EnergyPlusData &state)
394 : {
395 :
396 : // SUBROUTINE INFORMATION:
397 : // AUTHOR Richard Liesen
398 : // DATE WRITTEN July 1998
399 : // MODIFIED February 2006, Dimitri Curtil
400 : // - Added processing for air loop controller stats
401 :
402 : // PURPOSE OF THIS SUBROUTINE:
403 : // This subroutine is the main routine to call other input routines and Get routines
404 :
405 : // METHODOLOGY EMPLOYED:
406 : // Uses the status flags to trigger events.
407 :
408 : // SUBROUTINE PARAMETER DEFINITIONS:
409 326 : int NumPrimaryAirSys = state.dataHVACGlobal->NumPrimaryAirSys;
410 : static constexpr std::string_view RoutineName("HVACControllers: GetControllerInput: "); // include trailing blank space
411 :
412 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
413 : int NumAlphas;
414 : int NumNums;
415 : int NumArgs;
416 : bool ActuatorNodeNotFound; // true if no water coil inlet node match for actuator node
417 326 : Array1D<Real64> NumArray;
418 326 : Array1D_string AlphArray;
419 326 : Array1D_string cAlphaFields; // Alpha field names
420 326 : Array1D_string cNumericFields; // Numeric field names
421 326 : Array1D_bool lAlphaBlanks; // Logical array, alpha field input BLANK = .TRUE.
422 326 : Array1D_bool lNumericBlanks; // Logical array, numeric field input BLANK = .TRUE.
423 326 : bool ErrorsFound(false);
424 :
425 : // All the controllers are loaded into the same derived type, both the PI and Limit
426 : // These controllers are separate objects and loaded sequentially, but will
427 : // be retrieved by name as they are needed.
428 :
429 326 : std::string const CurrentModuleObject = "Controller:WaterCoil";
430 326 : int NumSimpleControllers = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, CurrentModuleObject);
431 326 : state.dataHVACControllers->NumControllers = NumSimpleControllers;
432 :
433 : // Allocate stats data structure for each air loop and controller if needed
434 326 : if (state.dataSysVars->TrackAirLoopEnvFlag || state.dataSysVars->TraceAirLoopEnvFlag || state.dataSysVars->TraceHVACControllerEnvFlag) {
435 0 : if (NumPrimaryAirSys > 0) {
436 0 : state.dataHVACControllers->NumAirLoopStats = NumPrimaryAirSys;
437 0 : state.dataHVACControllers->AirLoopStats.allocate(state.dataHVACControllers->NumAirLoopStats);
438 :
439 : // Allocate controller statistics data for each controller on each air loop
440 0 : for (int AirLoopNum = 1; AirLoopNum <= NumPrimaryAirSys; ++AirLoopNum) {
441 0 : state.dataHVACControllers->AirLoopStats(AirLoopNum)
442 0 : .ControllerStats.allocate(state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers);
443 : }
444 : }
445 : }
446 :
447 326 : if (state.dataHVACControllers->NumControllers == 0) {
448 29 : return;
449 : }
450 : // Condition of no controllers will be taken care of elsewhere, if necessary
451 :
452 297 : state.dataHVACControllers->ControllerProps.allocate(state.dataHVACControllers->NumControllers);
453 297 : state.dataHVACControllers->RootFinders.allocate(state.dataHVACControllers->NumControllers);
454 297 : state.dataHVACControllers->CheckEquipName.dimension(state.dataHVACControllers->NumControllers, true);
455 :
456 297 : state.dataInputProcessing->inputProcessor->getObjectDefMaxArgs(state, CurrentModuleObject, NumArgs, NumAlphas, NumNums);
457 297 : AlphArray.allocate(NumAlphas);
458 297 : cAlphaFields.allocate(NumAlphas);
459 297 : cNumericFields.allocate(NumNums);
460 297 : NumArray.dimension(NumNums, 0.0);
461 297 : lAlphaBlanks.dimension(NumAlphas, true);
462 297 : lNumericBlanks.dimension(NumNums, true);
463 :
464 : // Now find and load all of the simple controllers.
465 297 : if (NumSimpleControllers > 0) {
466 : int IOStat;
467 1199 : for (int Num = 1; Num <= NumSimpleControllers; ++Num) {
468 902 : auto &controllerProps = state.dataHVACControllers->ControllerProps(Num);
469 902 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
470 : CurrentModuleObject,
471 : Num,
472 : AlphArray,
473 : NumAlphas,
474 : NumArray,
475 : NumNums,
476 : IOStat,
477 : lNumericBlanks,
478 : lAlphaBlanks,
479 : cAlphaFields,
480 : cNumericFields);
481 :
482 902 : controllerProps.ControllerName = AlphArray(1);
483 902 : controllerProps.ControllerType = CurrentModuleObject;
484 :
485 902 : controllerProps.ControlVar = static_cast<EnergyPlus::HVACControllers::CtrlVarType>(getEnumValue(ctrlVarNamesUC, AlphArray(2)));
486 902 : if (controllerProps.ControlVar == HVACControllers::CtrlVarType::Invalid) {
487 0 : ShowSevereError(state, format("{}{}=\"{}\".", RoutineName, CurrentModuleObject, AlphArray(1)));
488 0 : ShowContinueError(state,
489 0 : format("...Invalid {}=\"{}\", must be Temperature, HumidityRatio, or TemperatureAndHumidityRatio.",
490 : cAlphaFields(2),
491 : AlphArray(2)));
492 0 : ErrorsFound = true;
493 : }
494 :
495 902 : controllerProps.Action = static_cast<ControllerAction>(getEnumValue(actionNamesUC, AlphArray(3)));
496 902 : if (controllerProps.Action == ControllerAction::Invalid) {
497 0 : ShowSevereError(state, format("{}{}=\"{}\".", RoutineName, CurrentModuleObject, AlphArray(1)));
498 0 : ShowContinueError(state,
499 0 : format("...Invalid {}=\"{}{}", cAlphaFields(3), AlphArray(3), R"(", must be "Normal", "Reverse" or blank.)"));
500 0 : ErrorsFound = true;
501 : }
502 :
503 902 : if (AlphArray(4) == "FLOW") {
504 902 : controllerProps.ActuatorVar = HVACControllers::CtrlVarType::Flow;
505 : } else {
506 0 : ShowSevereError(state, format("{}{}=\"{}\".", RoutineName, CurrentModuleObject, AlphArray(1)));
507 0 : ShowContinueError(state, format("...Invalid {}=\"{}\", only FLOW is allowed.", cAlphaFields(4), AlphArray(4)));
508 0 : ErrorsFound = true;
509 : }
510 902 : controllerProps.SensedNode = NodeInputManager::GetOnlySingleNode(state,
511 902 : AlphArray(5),
512 : ErrorsFound,
513 : DataLoopNode::ConnectionObjectType::ControllerWaterCoil,
514 902 : AlphArray(1),
515 : DataLoopNode::NodeFluidType::Blank,
516 : DataLoopNode::ConnectionType::Sensor,
517 : NodeInputManager::CompFluidStream::Primary,
518 : DataLoopNode::ObjectIsNotParent);
519 902 : controllerProps.ActuatedNode = NodeInputManager::GetOnlySingleNode(state,
520 902 : AlphArray(6),
521 : ErrorsFound,
522 : DataLoopNode::ConnectionObjectType::ControllerWaterCoil,
523 902 : AlphArray(1),
524 : DataLoopNode::NodeFluidType::Blank,
525 : DataLoopNode::ConnectionType::Actuator,
526 : NodeInputManager::CompFluidStream::Primary,
527 : DataLoopNode::ObjectIsNotParent);
528 902 : controllerProps.Offset = NumArray(1);
529 902 : controllerProps.MaxVolFlowActuated = NumArray(2);
530 902 : controllerProps.MinVolFlowActuated = NumArray(3);
531 :
532 902 : if (!MixedAir::CheckForControllerWaterCoil(state, DataAirLoop::ControllerKind::WaterCoil, AlphArray(1))) {
533 0 : ShowSevereError(state,
534 0 : format("{}{}=\"{}\" not found on any AirLoopHVAC:ControllerList.", RoutineName, CurrentModuleObject, AlphArray(1)));
535 0 : ErrorsFound = true;
536 : }
537 :
538 902 : if (controllerProps.SensedNode > 0) {
539 :
540 : bool NodeNotFound; // flag true if the sensor node is on the coil air outlet node
541 902 : if (controllerProps.ControlVar == HVACControllers::CtrlVarType::HumidityRatio ||
542 902 : controllerProps.ControlVar == HVACControllers::CtrlVarType::TemperatureAndHumidityRatio) {
543 55 : SetPointManager::ResetHumidityRatioCtrlVarType(state, controllerProps.SensedNode);
544 : }
545 902 : WaterCoils::CheckForSensorAndSetPointNode(state, controllerProps.SensedNode, controllerProps.ControlVar, NodeNotFound);
546 :
547 902 : if (NodeNotFound) {
548 : // the sensor node is not on the water coil air outlet node
549 0 : ShowWarningError(state, format("{}{}=\"{}\". ", RoutineName, controllerProps.ControllerType, controllerProps.ControllerName));
550 0 : ShowContinueError(state, " ..Sensor node not found on water coil air outlet node.");
551 0 : ShowContinueError(state,
552 : " ..The sensor node may have been placed on a node downstream of the coil or on an airloop outlet node.");
553 : } else {
554 : // check if the setpoint is also on the same node where the sensor is placed on
555 902 : bool EMSSetPointErrorFlag = false;
556 902 : switch (controllerProps.ControlVar) {
557 847 : case HVACControllers::CtrlVarType::Temperature: {
558 847 : EMSManager::CheckIfNodeSetPointManagedByEMS(state, controllerProps.SensedNode, HVAC::CtrlVarType::Temp, EMSSetPointErrorFlag);
559 847 : state.dataLoopNodes->NodeSetpointCheck(controllerProps.SensedNode).needsSetpointChecking = false;
560 847 : if (EMSSetPointErrorFlag) {
561 638 : if (!SetPointManager::NodeHasSPMCtrlVarType(state, controllerProps.SensedNode, HVAC::CtrlVarType::Temp)) {
562 0 : ShowContinueError(state, " ..Temperature setpoint not found on coil air outlet node.");
563 0 : ShowContinueError(
564 : state, " ..The setpoint may have been placed on a node downstream of the coil or on an airloop outlet node.");
565 0 : ShowContinueError(state, " ..Specify the setpoint and the sensor on the coil air outlet node when possible.");
566 : }
567 : }
568 847 : } break;
569 0 : case HVACControllers::CtrlVarType::HumidityRatio: {
570 0 : EMSManager::CheckIfNodeSetPointManagedByEMS(
571 : state, controllerProps.SensedNode, HVAC::CtrlVarType::MaxHumRat, EMSSetPointErrorFlag);
572 0 : state.dataLoopNodes->NodeSetpointCheck(controllerProps.SensedNode).needsSetpointChecking = false;
573 0 : if (EMSSetPointErrorFlag) {
574 0 : if (!SetPointManager::NodeHasSPMCtrlVarType(state, controllerProps.SensedNode, HVAC::CtrlVarType::MaxHumRat)) {
575 0 : ShowContinueError(state, " ..Humidity ratio setpoint not found on coil air outlet node.");
576 0 : ShowContinueError(
577 : state, " ..The setpoint may have been placed on a node downstream of the coil or on an airloop outlet node.");
578 0 : ShowContinueError(state, " ..Specify the setpoint and the sensor on the coil air outlet node when possible.");
579 : }
580 : }
581 0 : } break;
582 55 : case HVACControllers::CtrlVarType::TemperatureAndHumidityRatio: {
583 55 : EMSManager::CheckIfNodeSetPointManagedByEMS(state, controllerProps.SensedNode, HVAC::CtrlVarType::Temp, EMSSetPointErrorFlag);
584 55 : state.dataLoopNodes->NodeSetpointCheck(controllerProps.SensedNode).needsSetpointChecking = false;
585 55 : if (EMSSetPointErrorFlag) {
586 30 : if (!SetPointManager::NodeHasSPMCtrlVarType(state, controllerProps.SensedNode, HVAC::CtrlVarType::Temp)) {
587 0 : ShowContinueError(state, " ..Temperature setpoint not found on coil air outlet node.");
588 0 : ShowContinueError(
589 : state, " ..The setpoint may have been placed on a node downstream of the coil or on an airloop outlet node.");
590 0 : ShowContinueError(state, " ..Specify the setpoint and the sensor on the coil air outlet node when possible.");
591 : }
592 : }
593 55 : EMSSetPointErrorFlag = false;
594 55 : EMSManager::CheckIfNodeSetPointManagedByEMS(
595 : state, controllerProps.SensedNode, HVAC::CtrlVarType::MaxHumRat, EMSSetPointErrorFlag);
596 55 : state.dataLoopNodes->NodeSetpointCheck(controllerProps.SensedNode).needsSetpointChecking = false;
597 55 : if (EMSSetPointErrorFlag) {
598 30 : if (!SetPointManager::NodeHasSPMCtrlVarType(state, controllerProps.SensedNode, HVAC::CtrlVarType::MaxHumRat)) {
599 0 : ShowContinueError(state, " ..Humidity ratio setpoint not found on coil air outlet node.");
600 0 : ShowContinueError(
601 : state, " ..The setpoint may have been placed on a node downstream of the coil or on an airloop outlet node.");
602 0 : ShowContinueError(state, " ..Specify the setpoint and the sensor on the coil air outlet node when possible.");
603 : }
604 : }
605 55 : } break;
606 0 : default:
607 0 : break;
608 : }
609 : }
610 : }
611 : }
612 : }
613 :
614 : // check that actuator nodes are matched by a water coil inlet node
615 1199 : for (int Num = 1; Num <= NumSimpleControllers; ++Num) {
616 902 : auto &controllerProps = state.dataHVACControllers->ControllerProps(Num);
617 902 : WaterCoils::CheckActuatorNode(state, controllerProps.ActuatedNode, controllerProps.WaterCoilType, ActuatorNodeNotFound);
618 902 : if (ActuatorNodeNotFound) {
619 0 : ErrorsFound = true;
620 0 : ShowSevereError(state, format("{}{}=\"{}\":", RoutineName, CurrentModuleObject, controllerProps.ControllerName));
621 0 : ShowContinueError(state, "...the actuator node must also be a water inlet node of a water coil");
622 : } else { // Node found, check type and action
623 902 : if (controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterCooling ||
624 593 : controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterDetailedFlatCooling) {
625 471 : if (controllerProps.Action == ControllerAction::NoAction) {
626 0 : controllerProps.Action = ControllerAction::Reverse;
627 471 : } else if (controllerProps.Action == ControllerAction::NormalAction) {
628 0 : ShowWarningError(state, format("{}{}=\"{}\":", RoutineName, CurrentModuleObject, controllerProps.ControllerName));
629 0 : ShowContinueError(state, "...Normal action has been specified for a cooling coil - should be Reverse.");
630 0 : ShowContinueError(state, "...overriding user input action with Reverse Action.");
631 0 : controllerProps.Action = ControllerAction::Reverse;
632 : }
633 431 : } else if (controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterSimpleHeating) {
634 431 : if (controllerProps.Action == ControllerAction::NoAction) {
635 0 : controllerProps.Action = ControllerAction::NormalAction;
636 431 : } else if (controllerProps.Action == ControllerAction::Reverse) {
637 0 : ShowWarningError(state, format("{}{}=\"{}\":", RoutineName, CurrentModuleObject, controllerProps.ControllerName));
638 0 : ShowContinueError(state, "...Reverse action has been specified for a heating coil - should be Normal.");
639 0 : ShowContinueError(state, "...overriding user input action with Normal Action.");
640 0 : controllerProps.Action = ControllerAction::NormalAction;
641 : }
642 : }
643 : }
644 : }
645 :
646 297 : AlphArray.deallocate();
647 297 : cAlphaFields.deallocate();
648 297 : cNumericFields.deallocate();
649 297 : NumArray.deallocate();
650 297 : lAlphaBlanks.deallocate();
651 297 : lNumericBlanks.deallocate();
652 :
653 : // CR 8253 check that the sensed nodes in the controllers are in flow order in controller List
654 297 : CheckControllerListOrder(state);
655 :
656 297 : if (ErrorsFound) {
657 0 : ShowFatalError(state, format("{}Errors found in getting {} input.", RoutineName, CurrentModuleObject));
658 : }
659 500 : }
660 :
661 : // End of Get Input subroutines for the Module
662 : //******************************************************************************
663 :
664 : // Beginning Initialization Section of the Module
665 : //******************************************************************************
666 :
667 15682453 : void ResetController(EnergyPlusData &state, int const ControlNum, bool const DoWarmRestartFlag, bool &IsConvergedFlag)
668 : {
669 :
670 : // SUBROUTINE INFORMATION:
671 : // AUTHOR Fred Buhl
672 : // DATE WRITTEN April 2004
673 : // MODIFIED Dimitri Curtil (LBNL), Feb 2006
674 : // - Added capability for speculative warm restart
675 : // Brent Griffith (NREL), Feb 2010
676 : // - use SetActuatedBranchFlowRate in Plant Utilities (honor hardware min > 0.0)
677 : // - add FirstHVACIteration logic, don't reset if false,
678 :
679 : // PURPOSE OF THIS SUBROUTINE:
680 : // This subroutine resets the actuator inlet flows.
681 :
682 15682453 : auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
683 15682453 : auto &rootFinders = state.dataHVACControllers->RootFinders(ControlNum);
684 :
685 15682453 : Real64 NoFlowResetValue = 0.0;
686 15682453 : PlantUtilities::SetActuatedBranchFlowRate(state, NoFlowResetValue, controllerProps.ActuatedNode, controllerProps.ActuatedNodePlantLoc, true);
687 :
688 : // ENDIF
689 :
690 : // Reset iteration counter and internal variables
691 15682453 : controllerProps.NumCalcCalls = 0;
692 15682453 : controllerProps.DeltaSensed = 0.0;
693 15682453 : controllerProps.SensedValue = 0.0;
694 15682453 : controllerProps.ActuatedValue = 0.0;
695 :
696 : // Reset setpoint-related quantities
697 15682453 : controllerProps.SetPointValue = 0.0;
698 15682453 : controllerProps.IsSetPointDefinedFlag = false;
699 :
700 : // MinAvailActuated and MaxAvailActuated set in InitController()
701 15682453 : controllerProps.MinAvailActuated = 0.0;
702 15682453 : controllerProps.MinAvailSensed = 0.0;
703 15682453 : controllerProps.MaxAvailActuated = 0.0;
704 15682453 : controllerProps.MaxAvailSensed = 0.0;
705 :
706 : // Restart from previous solution if speculative warm restart flag set
707 : // Keep same mode and next actuated value unchanged from last controller simulation.
708 15682453 : if (DoWarmRestartFlag) {
709 5216889 : controllerProps.DoWarmRestartFlag = true;
710 : } else {
711 10465564 : controllerProps.DoWarmRestartFlag = false;
712 : // If no speculative warm restart then reset stored mode and actucated value
713 10465564 : controllerProps.Mode = ControllerMode::None;
714 10465564 : controllerProps.NextActuatedValue = 0.0;
715 : }
716 :
717 : // Only set once per HVAC iteration.
718 : // Might be overwritten in the InitController() routine.
719 : // Allow reusing the previous solution while identifying brackets if
720 : // this is not the first HVAC step of the environment
721 15682453 : controllerProps.ReusePreviousSolutionFlag = true;
722 : // Always reset to false by default. Set in CalcSimpleController() on the first controller iteration.
723 15682453 : controllerProps.ReuseIntermediateSolutionFlag = false;
724 : // By default not converged
725 15682453 : IsConvergedFlag = false;
726 :
727 : // Reset root finder
728 : // This is independent of the processing in InitializeRootFinder() performed in Calc() routine.
729 15682453 : rootFinders.StatusFlag = DataRootFinder::RootFinderStatus::None;
730 15682453 : rootFinders.CurrentMethodType = DataRootFinder::RootFinderMethod::None;
731 :
732 15682453 : rootFinders.CurrentPoint.DefinedFlag = false;
733 15682453 : rootFinders.CurrentPoint.X = 0.0;
734 15682453 : rootFinders.CurrentPoint.Y = 0.0;
735 :
736 15682453 : rootFinders.MinPoint.DefinedFlag = false;
737 15682453 : rootFinders.MaxPoint.DefinedFlag = false;
738 15682453 : rootFinders.LowerPoint.DefinedFlag = false;
739 15682453 : rootFinders.UpperPoint.DefinedFlag = false;
740 15682453 : }
741 :
742 37576708 : void InitController(EnergyPlusData &state, int const ControlNum, bool &IsConvergedFlag)
743 : {
744 :
745 : // SUBROUTINE INFORMATION:
746 : // AUTHOR Richard J. Liesen
747 : // DATE WRITTEN July 1998
748 : // MODIFIED Jan. 2004, Shirey/Raustad (FSEC),
749 : // MODIFIED Feb. 2006, Dimitri Curtil (LBNL), Moved first call convergence test code to ResetController()
750 : // Jul. 2016, R. Zhang (LBNL), Applied the water coil supply air temperature sensor offset fault model
751 :
752 : // PURPOSE OF THIS SUBROUTINE:
753 : // This subroutine is for initializations of the Controller Components.
754 :
755 : // METHODOLOGY EMPLOYED:
756 : // Uses the status flags to trigger events.
757 :
758 : static constexpr std::string_view RoutineName("InitController");
759 :
760 37576708 : auto &thisController = state.dataHVACControllers->ControllerProps(ControlNum);
761 37576708 : Array1D_bool &MyEnvrnFlag(state.dataHVACControllers->MyEnvrnFlag);
762 37576708 : Array1D_bool &MySizeFlag(state.dataHVACControllers->MySizeFlag);
763 37576708 : Array1D_bool &MyPlantIndexsFlag(state.dataHVACControllers->MyPlantIndexsFlag);
764 :
765 37576708 : if (state.dataHVACControllers->InitControllerOneTimeFlag) {
766 :
767 297 : MyEnvrnFlag.allocate(state.dataHVACControllers->NumControllers);
768 297 : MySizeFlag.allocate(state.dataHVACControllers->NumControllers);
769 297 : MyPlantIndexsFlag.allocate(state.dataHVACControllers->NumControllers);
770 297 : MyEnvrnFlag = true;
771 297 : MySizeFlag = true;
772 297 : MyPlantIndexsFlag = true;
773 297 : state.dataHVACControllers->InitControllerOneTimeFlag = false;
774 : }
775 :
776 37576708 : if (!state.dataGlobal->SysSizingCalc && state.dataHVACControllers->InitControllerSetPointCheckFlag && state.dataHVACGlobal->DoSetPointTest) {
777 : // check for missing setpoints
778 1199 : for (int ControllerIndex = 1; ControllerIndex <= state.dataHVACControllers->NumControllers; ++ControllerIndex) {
779 902 : auto &controllerProps = state.dataHVACControllers->ControllerProps(ControllerIndex);
780 902 : int SensedNode = controllerProps.SensedNode;
781 902 : switch (controllerProps.ControlVar) {
782 847 : case HVACControllers::CtrlVarType::Temperature: { // 'Temperature'
783 847 : if (state.dataLoopNodes->Node(SensedNode).TempSetPoint == DataLoopNode::SensedNodeFlagValue) {
784 32 : if (!state.dataGlobal->AnyEnergyManagementSystemInModel) {
785 0 : ShowSevereError(state,
786 0 : format("HVACControllers: Missing temperature setpoint for controller type={} Name=\"{}\"",
787 0 : controllerProps.ControllerType,
788 0 : controllerProps.ControllerName));
789 0 : ShowContinueError(state, format("Node Referenced (by Controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
790 0 : ShowContinueError(state,
791 : " use a Setpoint Manager with Control Variable = \"Temperature\" to establish a setpoint at the "
792 : "controller sensed node.");
793 0 : state.dataHVACGlobal->SetPointErrorFlag = true;
794 : } else {
795 : // call to check node is actuated by EMS
796 32 : EMSManager::CheckIfNodeSetPointManagedByEMS(
797 32 : state, SensedNode, HVAC::CtrlVarType::Temp, state.dataHVACGlobal->SetPointErrorFlag);
798 32 : if (state.dataHVACGlobal->SetPointErrorFlag) {
799 0 : ShowSevereError(state,
800 0 : format("HVACControllers: Missing temperature setpoint for controller type={} Name=\"{}\"",
801 0 : controllerProps.ControllerType,
802 0 : controllerProps.ControllerName));
803 0 : ShowContinueError(state, format("Node Referenced (by Controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
804 0 : ShowContinueError(state,
805 : " use a Setpoint Manager with Control Variable = \"Temperature\" to establish a setpoint at "
806 : "the controller sensed node.");
807 0 : ShowContinueError(state, "Or add EMS Actuator to provide temperature setpoint at this node");
808 : }
809 : }
810 : } else {
811 : // Warn if humidity setpoint is detected (only for cooling coils) and control variable is TEMP.
812 815 : if (state.dataLoopNodes->Node(SensedNode).HumRatMax != DataLoopNode::SensedNodeFlagValue &&
813 0 : controllerProps.Action == ControllerAction::Reverse) {
814 0 : ShowWarningError(
815 : state,
816 0 : format(
817 : "HVACControllers: controller type={} Name=\"{}\" has detected a maximum humidity ratio setpoint at the control node.",
818 0 : controllerProps.ControllerType,
819 0 : controllerProps.ControllerName));
820 0 : ShowContinueError(state, format("Node referenced (by controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
821 0 : ShowContinueError(state,
822 : " set the controller control variable to TemperatureAndHumidityRatio if humidity control is desired.");
823 : // SetPointErrorFlag = .TRUE.
824 : }
825 : }
826 847 : } break;
827 0 : case HVACControllers::CtrlVarType::HumidityRatio: { // 'HumidityRatio'
828 0 : controllerProps.HumRatCntrlType = SetPointManager::GetHumidityRatioVariableType(state, SensedNode);
829 0 : if ((thisController.HumRatCntrlType == HVAC::CtrlVarType::HumRat &&
830 0 : state.dataLoopNodes->Node(SensedNode).HumRatSetPoint == DataLoopNode::SensedNodeFlagValue) ||
831 0 : (thisController.HumRatCntrlType == HVAC::CtrlVarType::MaxHumRat &&
832 0 : state.dataLoopNodes->Node(SensedNode).HumRatMax == DataLoopNode::SensedNodeFlagValue)) {
833 0 : if (!state.dataGlobal->AnyEnergyManagementSystemInModel) {
834 0 : ShowSevereError(state,
835 0 : format("HVACControllers: Missing humidity ratio setpoint for controller type={} Name=\"{}\"",
836 0 : controllerProps.ControllerType,
837 0 : controllerProps.ControllerName));
838 0 : ShowContinueError(state, format("Node referenced (by controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
839 0 : ShowContinueError(state,
840 : " use a SetpointManager with the field Control Variable = \"MaximumHumidityRatio\" to establish a "
841 : "setpoint at the controller sensed node.");
842 0 : state.dataHVACGlobal->SetPointErrorFlag = true;
843 : } else {
844 0 : EMSManager::CheckIfNodeSetPointManagedByEMS(
845 0 : state, SensedNode, HVAC::CtrlVarType::HumRat, state.dataHVACGlobal->SetPointErrorFlag);
846 0 : if (state.dataHVACGlobal->SetPointErrorFlag) {
847 0 : ShowSevereError(state,
848 0 : format("HVACControllers: Missing humidity ratio setpoint for controller type={} Name=\"{}\"",
849 0 : controllerProps.ControllerType,
850 0 : controllerProps.ControllerName));
851 0 : ShowContinueError(state, format("Node referenced (by controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
852 0 : ShowContinueError(state,
853 : " use a SetpointManager with the field Control Variable = \"MaximumHumidityRatio\" to "
854 : "establish a setpoint at the controller sensed node.");
855 0 : ShowContinueError(state, "Or add EMS Actuator to provide Humidity Ratio setpoint at this node");
856 : }
857 : }
858 :
859 0 : } else if (thisController.HumRatCntrlType == HVAC::CtrlVarType::MinHumRat) {
860 0 : ShowSevereError(state,
861 0 : format("HVACControllers: incorrect humidity ratio setpoint for controller type={} Name=\"{}\"",
862 0 : controllerProps.ControllerType,
863 0 : controllerProps.ControllerName));
864 0 : ShowContinueError(state, format("Node referenced (by controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
865 0 : ShowContinueError(state,
866 : " use a SetpointManager with the field Control Variable = \"MaximumHumidityRatio\" to establish a "
867 : "setpoint at the controller sensed node.");
868 0 : state.dataHVACGlobal->SetPointErrorFlag = true;
869 : }
870 0 : } break;
871 55 : case HVACControllers::CtrlVarType::TemperatureAndHumidityRatio: { // 'TemperatureAndHumidityRatio'
872 55 : if (state.dataLoopNodes->Node(SensedNode).TempSetPoint == DataLoopNode::SensedNodeFlagValue) {
873 0 : if (!state.dataGlobal->AnyEnergyManagementSystemInModel) {
874 0 : ShowSevereError(state,
875 0 : format("HVACControllers: Missing temperature setpoint for controller type={} Name=\"{}\"",
876 0 : controllerProps.ControllerType,
877 0 : controllerProps.ControllerName));
878 0 : ShowContinueError(state, format("Node Referenced (by Controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
879 0 : ShowContinueError(state,
880 : " use a Setpoint Manager with Control Variable = \"Temperature\" to establish a setpoint at the "
881 : "controller sensed node.");
882 0 : state.dataHVACGlobal->SetPointErrorFlag = true;
883 : } else {
884 : // call to check node is actuated by EMS
885 0 : EMSManager::CheckIfNodeSetPointManagedByEMS(
886 0 : state, SensedNode, HVAC::CtrlVarType::Temp, state.dataHVACGlobal->SetPointErrorFlag);
887 0 : if (state.dataHVACGlobal->SetPointErrorFlag) {
888 0 : ShowSevereError(state,
889 0 : format("HVACControllers: Missing temperature setpoint for controller type={} Name=\"{}\"",
890 0 : controllerProps.ControllerType,
891 0 : controllerProps.ControllerName));
892 0 : ShowContinueError(state, format("Node Referenced (by Controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
893 0 : ShowContinueError(state,
894 : " use a Setpoint Manager with Control Variable = \"Temperature\" to establish a setpoint at "
895 : "the controller sensed node.");
896 0 : ShowContinueError(state, "Or add EMS Actuator to provide temperature setpoint at this node");
897 : }
898 : }
899 : }
900 55 : if (state.dataLoopNodes->Node(SensedNode).HumRatMax == DataLoopNode::SensedNodeFlagValue) {
901 0 : if (!state.dataGlobal->AnyEnergyManagementSystemInModel) {
902 0 : ShowSevereError(state,
903 0 : format("HVACControllers: Missing maximum humidity ratio setpoint for controller type={} Name=\"{}\"",
904 0 : controllerProps.ControllerType,
905 0 : controllerProps.ControllerName));
906 0 : ShowContinueError(state, format("Node Referenced (by Controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
907 0 : ShowContinueError(state,
908 : " use a SetpointManager with the field Control Variable = \"MaximumHumidityRatio\" to establish a "
909 : "setpoint at the controller sensed node.");
910 0 : state.dataHVACGlobal->SetPointErrorFlag = true;
911 : } else {
912 : // call to check node is actuated by EMS
913 0 : EMSManager::CheckIfNodeSetPointManagedByEMS(
914 0 : state, SensedNode, HVAC::CtrlVarType::MaxHumRat, state.dataHVACGlobal->SetPointErrorFlag);
915 0 : if (state.dataHVACGlobal->SetPointErrorFlag) {
916 0 : ShowSevereError(state,
917 0 : format("HVACControllers: Missing maximum humidity ratio setpoint for controller type={} Name=\"{}\"",
918 0 : controllerProps.ControllerType,
919 0 : controllerProps.ControllerName));
920 0 : ShowContinueError(state, format("Node Referenced (by Controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
921 0 : ShowContinueError(state,
922 : " use a SetpointManager with the field Control Variable = \"MaximumHumidityRatio\" to "
923 : "establish a setpoint at the controller sensed node.");
924 0 : ShowContinueError(state, "Or add EMS Actuator to provide maximum Humidity Ratio setpoint at this node");
925 : }
926 : }
927 : }
928 55 : } break;
929 0 : case HVACControllers::CtrlVarType::Flow: { // 'Flow'
930 0 : if (state.dataLoopNodes->Node(SensedNode).MassFlowRateSetPoint == DataLoopNode::SensedNodeFlagValue) {
931 0 : if (!state.dataGlobal->AnyEnergyManagementSystemInModel) {
932 0 : ShowSevereError(state,
933 0 : format("HVACControllers: Missing mass flow rate setpoint for controller type={} Name=\"{}\"",
934 0 : controllerProps.ControllerType,
935 0 : controllerProps.ControllerName));
936 0 : ShowContinueError(state, format("Node Referenced (in Controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
937 0 : ShowContinueError(state,
938 : " use a SetpointManager with the field Control Variable = \"MassFlowRate\" to establish a "
939 : "setpoint at the controller sensed node.");
940 0 : state.dataHVACGlobal->SetPointErrorFlag = true;
941 : } else {
942 : // call to check node is actuated by EMS
943 0 : EMSManager::CheckIfNodeSetPointManagedByEMS(
944 0 : state, SensedNode, HVAC::CtrlVarType::MassFlowRate, state.dataHVACGlobal->SetPointErrorFlag);
945 0 : if (state.dataHVACGlobal->SetPointErrorFlag) {
946 0 : ShowSevereError(state,
947 0 : format("HVACControllers: Missing mass flow rate setpoint for controller type={} Name=\"{}\"",
948 0 : controllerProps.ControllerType,
949 0 : controllerProps.ControllerName));
950 0 : ShowContinueError(state, format("Node Referenced (in Controller)={}", state.dataLoopNodes->NodeID(SensedNode)));
951 0 : ShowContinueError(state,
952 : " use a SetpointManager with the field Control Variable = \"MassFlowRate\" to establish a "
953 : "setpoint at the controller sensed node.");
954 0 : ShowContinueError(state, "Or add EMS Actuator to provide Mass Flow Rate setpoint at this node");
955 : }
956 : }
957 : }
958 0 : } break;
959 0 : default:
960 0 : break;
961 : }
962 : }
963 :
964 297 : state.dataHVACControllers->InitControllerSetPointCheckFlag = false;
965 : }
966 :
967 37576708 : if (allocated(state.dataPlnt->PlantLoop) && MyPlantIndexsFlag(ControlNum)) {
968 1804 : PlantUtilities::ScanPlantLoopsForNodeNum(
969 902 : state, thisController.ControllerName, thisController.ActuatedNode, thisController.ActuatedNodePlantLoc);
970 902 : MyPlantIndexsFlag(ControlNum) = false;
971 : }
972 :
973 37576708 : if (!state.dataGlobal->SysSizingCalc && MySizeFlag(ControlNum)) {
974 :
975 902 : SizeController(state, ControlNum);
976 :
977 : // Check to make sure that the Minimum Flow rate is less than the max.
978 902 : if (thisController.MaxVolFlowActuated == 0.0) {
979 66 : ShowWarningError(state,
980 66 : format("{}: Controller:WaterCoil=\"{}\", Maximum Actuated Flow is zero.", RoutineName, thisController.ControllerName));
981 33 : thisController.MinVolFlowActuated = 0.0;
982 869 : } else if (thisController.MinVolFlowActuated >= thisController.MaxVolFlowActuated) {
983 0 : ShowFatalError(state,
984 0 : format("{}: Controller:WaterCoil=\"{}\", Minimum control flow is > or = Maximum control flow.",
985 : RoutineName,
986 0 : thisController.ControllerName));
987 : }
988 :
989 : // Setup root finder after sizing calculation
990 902 : switch (thisController.Action) {
991 431 : case ControllerAction::NormalAction: {
992 431 : RootFinder::SetupRootFinder(state,
993 431 : state.dataHVACControllers->RootFinders(ControlNum),
994 : DataRootFinder::Slope::Increasing,
995 : DataRootFinder::RootFinderMethod::Brent,
996 : DataPrecisionGlobals::constant_zero,
997 : 1.0e-6,
998 : thisController.Offset); // Slope type | Method type | TolX: no relative tolerance for X variables |
999 : // ATolX: absolute tolerance for X variables | ATolY: absolute tolerance for
1000 : // Y variables
1001 :
1002 431 : } break;
1003 471 : case ControllerAction::Reverse: {
1004 471 : RootFinder::SetupRootFinder(state,
1005 471 : state.dataHVACControllers->RootFinders(ControlNum),
1006 : DataRootFinder::Slope::Decreasing,
1007 : DataRootFinder::RootFinderMethod::Brent,
1008 : DataPrecisionGlobals::constant_zero,
1009 : 1.0e-6,
1010 : thisController.Offset); // Slope type | Method type | TolX: no relative tolerance for X variables |
1011 : // ATolX: absolute tolerance for X variables | ATolY: absolute tolerance for
1012 : // Y variables
1013 471 : } break;
1014 0 : default: {
1015 0 : ShowFatalError(state, R"(InitController: Invalid controller action. Valid choices are "Normal" or "Reverse")");
1016 0 : } break;
1017 : }
1018 :
1019 902 : MySizeFlag(ControlNum) = false;
1020 : }
1021 :
1022 : // Set the sensed and actuated node numbers
1023 37576708 : int ActuatedNode = thisController.ActuatedNode;
1024 37576708 : int SensedNode = thisController.SensedNode;
1025 :
1026 : // Do the Begin Environment initializations
1027 37576708 : if (state.dataGlobal->BeginEnvrnFlag && MyEnvrnFlag(ControlNum)) {
1028 :
1029 : Real64 rho =
1030 6112 : state.dataPlnt->PlantLoop(thisController.ActuatedNodePlantLoc.loopNum).glycol->getDensity(state, Constant::CWInitConvTemp, RoutineName);
1031 :
1032 6112 : thisController.MinActuated = rho * thisController.MinVolFlowActuated;
1033 6112 : thisController.MaxActuated = rho * thisController.MaxVolFlowActuated;
1034 :
1035 : // Turn off scheme to reuse previous solution obtained at last SimAirLoop() call
1036 6112 : thisController.ReusePreviousSolutionFlag = false;
1037 : // Reset solution trackers
1038 18336 : for (auto &e : thisController.SolutionTrackers) {
1039 12224 : e.DefinedFlag = false;
1040 12224 : e.Mode = ControllerMode::None;
1041 12224 : e.ActuatedValue = 0.0;
1042 : }
1043 :
1044 6112 : MyEnvrnFlag(ControlNum) = false;
1045 : }
1046 :
1047 37576708 : if (!state.dataGlobal->BeginEnvrnFlag) {
1048 37470298 : MyEnvrnFlag(ControlNum) = true;
1049 : }
1050 :
1051 37576708 : PlantUtilities::SetActuatedBranchFlowRate(state, thisController.NextActuatedValue, ActuatedNode, thisController.ActuatedNodePlantLoc, false);
1052 :
1053 : // Do the following initializations (every time step): This should be the info from
1054 : // the previous components outlets or the node data in this section.
1055 : // Load the node data in this section for the component simulation
1056 37576708 : IsConvergedFlag = false;
1057 :
1058 37576708 : switch (thisController.ControlVar) {
1059 35016410 : case HVACControllers::CtrlVarType::Temperature: { // 'Temperature'
1060 35016410 : thisController.SensedValue = state.dataLoopNodes->Node(SensedNode).Temp;
1061 : // Done once per HVAC step
1062 35016410 : if (!thisController.IsSetPointDefinedFlag) {
1063 14583703 : thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).TempSetPoint;
1064 14583703 : thisController.IsSetPointDefinedFlag = true;
1065 :
1066 : // If there is a fault of water coil SAT sensor
1067 14584586 : if (thisController.FaultyCoilSATFlag && (!state.dataGlobal->WarmupFlag) && (!state.dataGlobal->DoingSizing) &&
1068 883 : (!state.dataGlobal->KickOffSimulation)) {
1069 : // calculate the sensor offset using fault information
1070 883 : int FaultIndex = thisController.FaultyCoilSATIndex;
1071 883 : thisController.FaultyCoilSATOffset = state.dataFaultsMgr->FaultsCoilSATSensor(FaultIndex).CalFaultOffsetAct(state);
1072 : // update the SetPointValue
1073 883 : thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).TempSetPoint - thisController.FaultyCoilSATOffset;
1074 : }
1075 : }
1076 35016410 : } break;
1077 2560298 : case HVACControllers::CtrlVarType::TemperatureAndHumidityRatio: { // 'TemperatureAndHumidityRatio'
1078 2560298 : if (thisController.HumRatCtrlOverride) {
1079 : // Humidity ratio control
1080 278654 : thisController.SensedValue = state.dataLoopNodes->Node(SensedNode).HumRat;
1081 : } else {
1082 : // Temperature control
1083 2281644 : thisController.SensedValue = state.dataLoopNodes->Node(SensedNode).Temp;
1084 : }
1085 2560298 : if (!thisController.IsSetPointDefinedFlag) {
1086 694668 : if (thisController.HumRatCtrlOverride) {
1087 : // Humidity ratio control
1088 70183 : thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).HumRatMax;
1089 : } else {
1090 : // Pure temperature setpoint control strategy
1091 624485 : thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).TempSetPoint;
1092 : }
1093 : // Finally indicate thate the setpoint has been computed
1094 694668 : thisController.IsSetPointDefinedFlag = true;
1095 : }
1096 2560298 : } break;
1097 0 : case HVACControllers::CtrlVarType::HumidityRatio: { // 'HumidityRatio'
1098 0 : thisController.SensedValue = state.dataLoopNodes->Node(SensedNode).HumRat;
1099 : // Done once per HVAC step
1100 0 : if (!thisController.IsSetPointDefinedFlag) {
1101 0 : switch (thisController.HumRatCntrlType) {
1102 0 : case HVAC::CtrlVarType::MaxHumRat: {
1103 0 : thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).HumRatMax;
1104 0 : } break;
1105 0 : default: {
1106 0 : thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).HumRatSetPoint;
1107 0 : } break;
1108 : }
1109 0 : thisController.IsSetPointDefinedFlag = true;
1110 : }
1111 0 : } break;
1112 0 : case HVACControllers::CtrlVarType::Flow: { // 'Flow'
1113 0 : thisController.SensedValue = state.dataLoopNodes->Node(SensedNode).MassFlowRate;
1114 : // Done once per HVAC step
1115 0 : if (!thisController.IsSetPointDefinedFlag) {
1116 0 : thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).MassFlowRateSetPoint;
1117 0 : thisController.IsSetPointDefinedFlag = true;
1118 : }
1119 0 : } break;
1120 0 : default: {
1121 0 : ShowFatalError(state, format("Invalid Controller Variable Type={}", ControlVariableTypes(thisController.ControlVar)));
1122 0 : } break;
1123 : }
1124 :
1125 37576708 : switch (thisController.ActuatorVar) {
1126 37576708 : case HVACControllers::CtrlVarType::Flow: { // 'Flow'
1127 : // At the beginning of every time step the value is reset to the User Input
1128 : // The interface managers can reset the Max or Min to available values during the time step
1129 : // and these will then be the new setpoint limits for the controller to work within.
1130 37576708 : thisController.ActuatedValue = state.dataLoopNodes->Node(ActuatedNode).MassFlowRate;
1131 : // Compute the currently available min and max bounds for controller.
1132 : // Done only once per HVAC step, as it would not make any sense to modify the min/max
1133 : // bounds during successive iterations of the root finder.
1134 37576708 : if (thisController.NumCalcCalls == 0) {
1135 15278371 : thisController.MinAvailActuated = max(state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMinAvail, thisController.MinActuated);
1136 15278371 : thisController.MaxAvailActuated = min(state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMaxAvail, thisController.MaxActuated);
1137 : // MinActuated is user input for minimum actuated flow, use that value if allowed
1138 : // (i.e., reset MinAvailActuated based on Node%MassFlowRateMaxAvail)
1139 15278371 : thisController.MinAvailActuated = min(thisController.MinAvailActuated, thisController.MaxAvailActuated);
1140 : }
1141 37576708 : } break;
1142 0 : default: {
1143 0 : ShowFatalError(state, format("Invalid Actuator Variable Type={}", ControlVariableTypes(thisController.ActuatorVar)));
1144 0 : } break;
1145 : }
1146 :
1147 : // Compute residual for control function using desired setpoint value and current sensed value
1148 : // NOTE: The delta sensed value might be wrong if the setpoint has not yet been computed.
1149 : // Make sure not to use it until the setpoint has been computed.
1150 37576708 : if (thisController.IsSetPointDefinedFlag) {
1151 37576708 : thisController.DeltaSensed = thisController.SensedValue - thisController.SetPointValue;
1152 : } else {
1153 0 : thisController.DeltaSensed = 0.0;
1154 : }
1155 37576708 : }
1156 :
1157 902 : void SizeController(EnergyPlusData &state, int const ControlNum)
1158 : {
1159 :
1160 : // SUBROUTINE INFORMATION:
1161 : // AUTHOR Fred Buhl
1162 : // DATE WRITTEN November 2001
1163 :
1164 : // PURPOSE OF THIS SUBROUTINE:
1165 : // This subroutine is for sizing Controller Components for which max flow rates have not been
1166 : // specified in the input.
1167 :
1168 : // METHODOLOGY EMPLOYED:
1169 : // Obtains flow rates from the actuated node. Should have been set by the water coils.
1170 :
1171 902 : auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
1172 :
1173 902 : if (controllerProps.MaxVolFlowActuated == DataSizing::AutoSize) {
1174 49923 : for (int WaterCompNum = 1; WaterCompNum <= state.dataSize->SaveNumPlantComps; ++WaterCompNum) {
1175 49135 : if (state.dataSize->CompDesWaterFlow(WaterCompNum).SupNode == controllerProps.ActuatedNode) {
1176 755 : controllerProps.MaxVolFlowActuated = state.dataSize->CompDesWaterFlow(WaterCompNum).DesVolFlowRate;
1177 : }
1178 : }
1179 :
1180 788 : if (controllerProps.MaxVolFlowActuated < HVAC::SmallWaterVolFlow) {
1181 33 : controllerProps.MaxVolFlowActuated = 0.0;
1182 : }
1183 788 : BaseSizer::reportSizerOutput(state,
1184 : controllerProps.ControllerType,
1185 : controllerProps.ControllerName,
1186 : "Maximum Actuated Flow [m3/s]",
1187 : controllerProps.MaxVolFlowActuated);
1188 : }
1189 :
1190 902 : if (controllerProps.Offset == DataSizing::AutoSize) {
1191 : // 2100 = 0.5 * 4.2 * 1000/1.2 * 1.2 where 0.5 is the ratio of chilled water delta T to supply air delta T,
1192 : // 4.2 is the ratio of water density to air density, 1000/1.2 is the ratio of water specific heat to
1193 : // air specific heat, and 1.2 converts the result from air volumetric flow rate to air mass flow rate.
1194 : // The assumption is that a temperature tolerance of 0.001 C is good for an air mass flow rate of 1 kg/s.
1195 : // So we divide .001 by the air mass flow rate estimated from the water volumetric flow rate to come up
1196 : // with a temperature tolerance that won't exceed the loop energy error tolerance (10 W).
1197 : // Finally we need to take into account the fact that somebody might change the energy tolerance.
1198 285 : controllerProps.Offset =
1199 285 : (0.001 / (2100.0 * max(controllerProps.MaxVolFlowActuated, HVAC::SmallWaterVolFlow))) * (DataConvergParams::HVACEnergyToler / 10.0);
1200 : // do not let the controller tolerance exceed 1/10 of the loop temperature tolerance.
1201 285 : controllerProps.Offset = min(0.1 * DataConvergParams::HVACTemperatureToler, controllerProps.Offset);
1202 285 : BaseSizer::reportSizerOutput(
1203 : state, controllerProps.ControllerType, controllerProps.ControllerName, "Controller Convergence Tolerance", controllerProps.Offset);
1204 : }
1205 902 : }
1206 :
1207 21963536 : void CalcSimpleController(EnergyPlusData &state,
1208 : int const ControlNum,
1209 : bool const FirstHVACIteration,
1210 : bool &IsConvergedFlag,
1211 : bool &IsUpToDateFlag,
1212 : std::string const &ControllerName // used when errors occur
1213 : )
1214 : {
1215 :
1216 : // SUBROUTINE INFORMATION:
1217 : // AUTHOR Dimitri Curtil
1218 : // DATE WRITTEN May 2006
1219 : // MODIFIED Dimitri Curtil (LBNL), May 2006
1220 : // - Added IsPointFlagDefinedFlag to control when the setpoiont should be
1221 : // computed depending on the control strategy. This was needed to
1222 : // trigger the setpoint calculation for the dual temperature and
1223 : // humidity ratio control strategy only once the air loop has been
1224 : // evaluated with the max actuated flow.
1225 : // See the routine InitController() for more details on the setpoint
1226 : // calculation.
1227 : // MODIFIED Dimitri Curtil (LBNL), March 2006
1228 : // - Added IsUpToDateFlag to detect whether or not the air loop
1229 : // has been evaluated prior the first iteration, which allows
1230 : // to use the current node values as the first iterate for the root
1231 : // finder (for COLD RESTART ONLY).
1232 : // MODIFIED Dimitri Curtil (LBNL), Feb 2006
1233 : // - Added mode detection capability.
1234 : // - Now trying min actuated variable first to
1235 : // detect min-constrained cases in 1 iteration.
1236 : // - Trying max actuated variable second.
1237 : // Checks for max-constrained here instead of in
1238 : // NormActuatedCalc mode.
1239 : // - Checking for inactive mode as soon as min and max
1240 : // support points are known instead of in NormActuatedCalc
1241 : // mode.
1242 :
1243 : // SUBROUTINE ARGUMENT DEFINITIONS:
1244 : // Set to TRUE if current controller is converged; FALSE if more iteration are needed.
1245 : // Note that an error in the root finding process can be mapped onto IsConvergedFlag=TRUE
1246 : // to avoid continue iterating.
1247 : // TRUE if air loop is up-to-date meaning that the current node values are consistent (air loop evaluated)
1248 : // Only used within the Calc routines
1249 :
1250 21963536 : auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
1251 21963536 : auto &rootFinders = state.dataHVACControllers->RootFinders(ControlNum);
1252 :
1253 : // Increment counter
1254 21963536 : ++controllerProps.NumCalcCalls;
1255 :
1256 : // Check to see if the component is running; if not converged and return. This check will be done
1257 : // by looking at the component mass flow rate at the sensed node.
1258 21963536 : if (state.dataLoopNodes->Node(controllerProps.SensedNode).MassFlowRate == 0.0) {
1259 884114 : ExitCalcController(state, ControlNum, DataPrecisionGlobals::constant_zero, ControllerMode::Off, IsConvergedFlag, IsUpToDateFlag);
1260 884114 : return;
1261 : }
1262 :
1263 : // Initialize root finder
1264 21079422 : if (controllerProps.NumCalcCalls == 1) {
1265 : // Set min/max boundaries for root finder on first iteration
1266 9581450 : RootFinder::InitializeRootFinder(state, rootFinders, controllerProps.MinAvailActuated,
1267 : controllerProps.MaxAvailActuated); // XMin | XMax
1268 :
1269 : // Only allow to reuse initial evaluation if the air loop is up-to-date.
1270 : // Set in SolveAirLoopControllers()
1271 : // Only reuse initial evaluation if setpoint is already available for the current controller
1272 : // Note that in the case of dual temperature and humidity ratio control strategy since the
1273 : // setpoint at a later iteration, the initial solution cannot be reused.
1274 : // Make sure that the initial candidate value lies within range
1275 19040231 : controllerProps.ReuseIntermediateSolutionFlag = IsUpToDateFlag && controllerProps.IsSetPointDefinedFlag &&
1276 9458781 : RootFinder::CheckRootFinderCandidate(rootFinders, controllerProps.ActuatedValue);
1277 :
1278 9581450 : if (controllerProps.ReuseIntermediateSolutionFlag) {
1279 :
1280 : // Reuse intermediate solution obtained with previous controller for the current HVAC step
1281 : // and fire root finder to get next root candidate
1282 9458781 : FindRootSimpleController(state, ControlNum, FirstHVACIteration, IsConvergedFlag, IsUpToDateFlag, ControllerName);
1283 :
1284 : } else {
1285 : // Always start with min point by default
1286 122669 : controllerProps.NextActuatedValue = rootFinders.MinPoint.X;
1287 : }
1288 :
1289 : // Process current iterate and compute next candidate if needed
1290 : // We assume that after the first controller iteration:
1291 : // - the setpoint is defined
1292 : // - the min and max available bounds are defined
1293 : // NOTE: Not explicitly checked but the air mass flow rate must remain constant across successive
1294 : // controller iterations to ensure that the root finder converges.
1295 : } else {
1296 : // Check that the setpoint is defined
1297 11497972 : if (!controllerProps.IsSetPointDefinedFlag) {
1298 0 : ShowSevereError(state, format("CalcSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
1299 0 : ShowContinueError(state, format(" Controller name=\"{}\"", ControllerName));
1300 0 : ShowContinueError(state, " Setpoint is not available/defined.");
1301 0 : ShowFatalError(state, "Preceding error causes program termination.");
1302 : }
1303 : // Monitor invariants across successive controller iterations
1304 : // - min bound
1305 : // - max bound
1306 11497972 : if (rootFinders.MinPoint.X != controllerProps.MinAvailActuated) {
1307 0 : ShowSevereError(state, format("CalcSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
1308 0 : ShowContinueError(state, format(" Controller name=\"{}\"", ControllerName));
1309 0 : ShowContinueError(state, " Minimum bound must remain invariant during successive iterations.");
1310 0 : ShowContinueError(state, format(" Minimum root finder point={:.{}T}", rootFinders.MinPoint.X, NumSigDigits));
1311 0 : ShowContinueError(state, format(" Minimum avail actuated={:.{}T}", controllerProps.MinAvailActuated, NumSigDigits));
1312 0 : ShowFatalError(state, "Preceding error causes program termination.");
1313 : }
1314 11497972 : if (rootFinders.MaxPoint.X != controllerProps.MaxAvailActuated) {
1315 0 : ShowSevereError(state, format("CalcSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
1316 0 : ShowContinueError(state, format(" Controller name=\"{}\"", ControllerName));
1317 0 : ShowContinueError(state, " Maximum bound must remain invariant during successive iterations.");
1318 0 : ShowContinueError(state, format(" Maximum root finder point={:.{}T}", rootFinders.MaxPoint.X, NumSigDigits));
1319 0 : ShowContinueError(state, format(" Maximum avail actuated={:.{}T}", controllerProps.MaxAvailActuated, NumSigDigits));
1320 0 : ShowFatalError(state, "Preceding error causes program termination.");
1321 : }
1322 :
1323 : // Updates root finder with current iterate and computes next one if needed
1324 11497972 : FindRootSimpleController(state, ControlNum, FirstHVACIteration, IsConvergedFlag, IsUpToDateFlag, ControllerName);
1325 : }
1326 : }
1327 :
1328 20956753 : void FindRootSimpleController(EnergyPlusData &state,
1329 : int const ControlNum,
1330 : bool const FirstHVACIteration,
1331 : bool &IsConvergedFlag,
1332 : bool &IsUpToDateFlag,
1333 : std::string const &ControllerName // used when errors occur
1334 : )
1335 : {
1336 :
1337 : // SUBROUTINE INFORMATION:
1338 : // AUTHOR Dimitri Curtil (LBNL)
1339 : // DATE WRITTEN March 2006
1340 :
1341 : // PURPOSE OF THIS SUBROUTINE:
1342 : // New routine to fire the root finder using the current actuated and sensed values.
1343 : // - Updates IsConvergedFlag depending ou iteration status.
1344 : // - Sets next actuated value to try in ControllerProps(ControlNum)%NextActuatedValue
1345 :
1346 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1347 : bool IsDoneFlag; // TRUE if root finder needs to continue iterating, FALSE otherwise.
1348 : ControllerMode PreviousSolutionMode;
1349 : Real64 PreviousSolutionValue;
1350 :
1351 20956753 : auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
1352 20956753 : auto &rootFinders = state.dataHVACControllers->RootFinders(ControlNum);
1353 :
1354 : // Update root finder with latest solution point
1355 : // Check for unconstrained/constrained convergence
1356 : // Compute next candidate if not converged yet.
1357 20956753 : RootFinder::IterateRootFinder(state,
1358 : rootFinders,
1359 : controllerProps.ActuatedValue,
1360 : controllerProps.DeltaSensed,
1361 : IsDoneFlag); // root finder's data | X | Y | not used
1362 :
1363 : // Process root finder if converged or error
1364 : // Map root finder status onto controller mode
1365 20956753 : switch (rootFinders.StatusFlag) {
1366 11375320 : case DataRootFinder::RootFinderStatus::None:
1367 : case DataRootFinder::RootFinderStatus::WarningNonMonotonic:
1368 : case DataRootFinder::RootFinderStatus::WarningSingular: {
1369 : // We need to keep iterating...
1370 : int PreviousSolutionIndex;
1371 11375320 : IsConvergedFlag = false;
1372 :
1373 11375320 : if (FirstHVACIteration) {
1374 4390245 : PreviousSolutionIndex = 1;
1375 : } else {
1376 6985075 : PreviousSolutionIndex = 2;
1377 : }
1378 :
1379 11375320 : bool PreviousSolutionDefinedFlag = controllerProps.SolutionTrackers(PreviousSolutionIndex).DefinedFlag;
1380 11375320 : PreviousSolutionMode = controllerProps.SolutionTrackers(PreviousSolutionIndex).Mode;
1381 11375320 : PreviousSolutionValue = controllerProps.SolutionTrackers(PreviousSolutionIndex).ActuatedValue;
1382 :
1383 : // Attempt to use root at previous HVAC step in place of the candidate produced by the
1384 : // root finder.
1385 : // Set in InitController() depending on controller mode at previous HVAC step iteration
1386 : // Only attempted during bracketing phase of root finder.
1387 : // Check that a previous solution is available
1388 : // Make sure that mode of previous solution was active
1389 : // Make sure that proposed candidate does not conflict with current min/max range and lower/upper brackets
1390 16380754 : bool ReusePreviousSolutionFlag = controllerProps.ReusePreviousSolutionFlag &&
1391 5005434 : (rootFinders.CurrentMethodType == DataRootFinder::RootFinderMethod::Bracket) &&
1392 19536194 : PreviousSolutionDefinedFlag && (PreviousSolutionMode == ControllerMode::Active) &&
1393 3155440 : RootFinder::CheckRootFinderCandidate(rootFinders, PreviousSolutionValue);
1394 :
1395 11375320 : if (ReusePreviousSolutionFlag) {
1396 : // Try to reuse saved solution from previous call to SolveAirLoopControllers()
1397 : // instead of candidate proposed by the root finder
1398 3155440 : controllerProps.NextActuatedValue = PreviousSolutionValue;
1399 :
1400 : // Turn off flag since we can only use the previous solution once per HVAC iteration
1401 3155440 : controllerProps.ReusePreviousSolutionFlag = false;
1402 : } else {
1403 : // By default, use candidate value computed by root finder
1404 8219880 : controllerProps.NextActuatedValue = rootFinders.XCandidate;
1405 : }
1406 :
1407 11375320 : } break;
1408 4177876 : case DataRootFinder::RootFinderStatus::OK:
1409 : case DataRootFinder::RootFinderStatus::OKRoundOff: {
1410 : // Indicate convergence with base value (used to obtain DeltaSensed!)
1411 4177876 : ExitCalcController(state, ControlNum, rootFinders.XCandidate, ControllerMode::Active, IsConvergedFlag, IsUpToDateFlag);
1412 4177876 : } break;
1413 4406342 : case DataRootFinder::RootFinderStatus::OKMin: {
1414 : // Indicate convergence with min value
1415 : // Should be the same as ControllerProps(ControlNum)%MinAvailActuated
1416 4406342 : ExitCalcController(state, ControlNum, rootFinders.MinPoint.X, ControllerMode::MinActive, IsConvergedFlag, IsUpToDateFlag);
1417 4406342 : } break;
1418 594566 : case DataRootFinder::RootFinderStatus::OKMax: {
1419 : // Indicate convergence with max value
1420 : // Should be the same as ControllerProps(ControlNum)%MaxAvailActuated
1421 594566 : ExitCalcController(state, ControlNum, rootFinders.MaxPoint.X, ControllerMode::MaxActive, IsConvergedFlag, IsUpToDateFlag);
1422 :
1423 594566 : } break;
1424 402461 : case DataRootFinder::RootFinderStatus::ErrorSingular: {
1425 : // Indicate inactive mode with min actuated value
1426 : // NOTE: Original code returned Node(ActuatedNode)%MassFlowRateMinAvail
1427 : // This was not portable in case the actuated variable was NOT a mass flow rate!
1428 : // Replaced Node(ActuatedNode)%MassFlowRateMinAvail
1429 : // with rootFinders%MinPoint%X
1430 : // which is the same as (see SUBROUTINE InitController)
1431 : // ControllerProps(ControlNum)%MinAvailActuated
1432 402461 : ExitCalcController(state, ControlNum, rootFinders.MinPoint.X, ControllerMode::Inactive, IsConvergedFlag, IsUpToDateFlag);
1433 :
1434 : // Abnormal case: should never happen
1435 402461 : } break;
1436 0 : case DataRootFinder::RootFinderStatus::ErrorRange: {
1437 0 : ShowSevereError(state, format("FindRootSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
1438 0 : ShowContinueError(state, format(" Controller name=\"{}\"", ControllerName));
1439 0 : ShowContinueError(state,
1440 0 : format(" Root candidate x={:.{}T} does not lie within the min/max bounds.", controllerProps.ActuatedValue, NumSigDigits));
1441 0 : ShowContinueError(state, format(" Min bound is x={:.{}T}", rootFinders.MinPoint.X, NumSigDigits));
1442 0 : ShowContinueError(state, format(" Max bound is x={:.{}T}", rootFinders.MaxPoint.X, NumSigDigits));
1443 0 : ShowFatalError(state, "Preceding error causes program termination.");
1444 :
1445 : // Abnormal case: should never happen
1446 0 : } break;
1447 0 : case DataRootFinder::RootFinderStatus::ErrorBracket: {
1448 0 : ShowSevereError(state, format("FindRootSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
1449 0 : ShowContinueError(state, format(" Controller name={}", controllerProps.ControllerName));
1450 0 : ShowContinueError(state, fmt::format(" Controller action={}", state.dataHVACCtrl->ActionTypes[static_cast<int>(controllerProps.Action)]));
1451 0 : ShowContinueError(
1452 0 : state, format(" Root candidate x={:.{}T} does not lie within the lower/upper brackets.", controllerProps.ActuatedValue, NumSigDigits));
1453 0 : if (rootFinders.LowerPoint.DefinedFlag) {
1454 0 : ShowContinueError(state, format(" Lower bracket is x={:.{}T}", rootFinders.LowerPoint.X, NumSigDigits));
1455 : }
1456 0 : if (rootFinders.UpperPoint.DefinedFlag) {
1457 0 : ShowContinueError(state, format(" Upper bracket is x={:.{}T}", rootFinders.UpperPoint.X, NumSigDigits));
1458 : }
1459 0 : ShowFatalError(state, "Preceding error causes program termination.");
1460 :
1461 : // Detected control function with wrong action between the min and max points.
1462 : // Should never happen: probably indicative of some serious problems in IDFs
1463 : // NOTE: This approach is more robust and consistent than what was done in version 1.3.
1464 : // Indeed, such a function with the wrong action characteristic would have silently returned
1465 : // either of the following values depending on the specified action:
1466 : // - NORMAL ACTION:
1467 : // - If y(xMin) > ySetPoint && y(xMax) < y(xMin), then x = xMin
1468 : // - If y(xMin) < ySetPoint && y(xMax) < y(xMin), then x = xMax
1469 : // - REVERSE ACTION:
1470 : // - If y(xMin) < ySetPoint && y(xMax) > y(xMin), then x = xMin
1471 : // - If y(xMin) > ySetPoint && y(xMax) > y(xMin), then x = xMax
1472 0 : } break;
1473 188 : case DataRootFinder::RootFinderStatus::ErrorSlope: {
1474 188 : if (!state.dataGlobal->WarmupFlag && controllerProps.BadActionErrCount == 0) {
1475 3 : ++controllerProps.BadActionErrCount;
1476 3 : ShowSevereError(state, format("FindRootSimpleController: Controller error for controller = \"{}\"", ControllerName));
1477 6 : ShowContinueErrorTimeStamp(state, "");
1478 3 : ShowContinueError(state,
1479 3 : fmt::format(" Controller function is inconsistent with user specified controller action = {}",
1480 3 : state.dataHVACCtrl->ActionTypes[static_cast<int>(controllerProps.Action)]));
1481 6 : ShowContinueError(state, " Actuator will be set to maximum action");
1482 3 : ShowContinueError(state, format("Controller control type={}", ControlVariableTypes(controllerProps.ControlVar)));
1483 3 : if (controllerProps.ControlVar == CtrlVarType::Temperature) {
1484 3 : ShowContinueError(state, format("Controller temperature setpoint = {:.2T} [C]", controllerProps.SetPointValue));
1485 3 : ShowContinueError(state, format("Controller sensed temperature = {:.2T} [C]", controllerProps.SensedValue));
1486 0 : } else if (controllerProps.ControlVar == CtrlVarType::HumidityRatio) {
1487 0 : ShowContinueError(state, format("Controller humidity ratio setpoint = {:.2T} [kgWater/kgDryAir]", controllerProps.SetPointValue));
1488 0 : ShowContinueError(state, format("Controller sensed humidity ratio = {:.2T} [kgWater/kgDryAir]", controllerProps.SensedValue));
1489 0 : } else if (controllerProps.ControlVar == CtrlVarType::TemperatureAndHumidityRatio) {
1490 0 : if (controllerProps.HumRatCtrlOverride) {
1491 0 : ShowContinueError(state, "Humidity control is active.");
1492 0 : ShowContinueError(state, format("Controller humidity ratio setpoint = {:.2T} [kgWater/kgDryAir]", controllerProps.SetPointValue));
1493 0 : ShowContinueError(state, format("Controller sensed humidity ratio = {:.2T} [kgWater/kgDryAir]", controllerProps.SensedValue));
1494 0 : ShowContinueError(state,
1495 0 : format("Controller humidity ratio setpoint dew-point temperature = {:.2T} [C]",
1496 0 : Psychrometrics::PsyTdpFnWPb(state, controllerProps.SetPointValue, state.dataEnvrn->OutBaroPress)));
1497 0 : ShowContinueError(
1498 : state,
1499 0 : format("Controller temperature setpoint = {:.2T} [C]", state.dataLoopNodes->Node(controllerProps.SensedNode).TempSetPoint));
1500 0 : ShowContinueError(
1501 0 : state, format("Controller sensed temperature = {:.2T} [C]", state.dataLoopNodes->Node(controllerProps.SensedNode).Temp));
1502 : } else {
1503 0 : ShowContinueError(state, format("Controller temperature setpoint = {:.2T} [C]", controllerProps.SetPointValue));
1504 0 : ShowContinueError(state, format("Controller sensed temperature = {:.2T} [C]", controllerProps.SensedValue));
1505 : }
1506 0 : } else if (controllerProps.ControlVar == CtrlVarType::Flow) {
1507 0 : ShowContinueError(state, format("Controller mass flow rate setpoint = {:.2T} [kg/s]", controllerProps.SetPointValue));
1508 0 : ShowContinueError(state, format("Controller sensed mass flow rate = {:.2T} [kg/s]", controllerProps.SensedValue));
1509 : } else {
1510 : // bad control variable input checked in input routine
1511 : }
1512 3 : if (controllerProps.ActuatorVar == CtrlVarType::Flow) {
1513 3 : ShowContinueError(state, format("Controller actuator mass flow rate set to {:.2T} [kg/s]", controllerProps.MaxAvailActuated));
1514 3 : if (controllerProps.ControlVar == CtrlVarType::Temperature ||
1515 0 : controllerProps.ControlVar == CtrlVarType::TemperatureAndHumidityRatio) {
1516 6 : ShowContinueError(
1517 6 : state, format("Controller actuator temperature = {:.2T} [C]", state.dataLoopNodes->Node(controllerProps.ActuatedNode).Temp));
1518 3 : if (controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterCooling ||
1519 3 : controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterDetailedFlatCooling) {
1520 0 : if (controllerProps.HumRatCtrlOverride) {
1521 0 : ShowContinueError(state,
1522 : "The entering chilled water temperature (controller actuator temperature) should be below the humidity "
1523 : "ratio setpoint dew-point temperature.");
1524 : } else {
1525 0 : ShowContinueError(
1526 : state,
1527 : "The entering chilled water temperature (controller actuator temperature) should be below the setpoint temperature.");
1528 : }
1529 3 : } else if (controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterSimpleHeating) {
1530 9 : ShowContinueError(
1531 : state, "The entering hot water temperature (controller actuator temperature) should be above the setpoint temperature");
1532 : }
1533 : }
1534 : } else {
1535 : // bad actuator variable input checked in input routine
1536 : }
1537 185 : } else if (!state.dataGlobal->WarmupFlag) {
1538 0 : ++controllerProps.BadActionErrCount;
1539 0 : ShowRecurringSevereErrorAtEnd(state,
1540 0 : "FindRootSimpleController: Previous controller action error continues for controller = " + ControllerName,
1541 0 : controllerProps.BadActionErrIndex);
1542 : } else {
1543 : // do nothing
1544 : }
1545 : // Indicate convergence with min value
1546 : // Should be the same as ControllerProps(ControlNum)%MaxAvailActuated
1547 188 : ExitCalcController(state, ControlNum, rootFinders.MaxPoint.X, ControllerMode::MaxActive, IsConvergedFlag, IsUpToDateFlag);
1548 188 : } break;
1549 0 : default: {
1550 : // Should never happen
1551 0 : ShowSevereError(state, format("FindRootSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
1552 0 : ShowContinueError(state, format(" Controller name={}", ControllerName));
1553 0 : ShowContinueError(state, format(" Unrecognized root finder status flag={}", rootFinders.StatusFlag));
1554 0 : ShowFatalError(state, "Preceding error causes program termination.");
1555 0 : } break;
1556 : }
1557 20956753 : }
1558 :
1559 15612270 : void CheckSimpleController(EnergyPlusData &state, int const ControlNum, bool &IsConvergedFlag)
1560 : {
1561 :
1562 : // SUBROUTINE INFORMATION:
1563 : // AUTHOR Dimitri Curtil (LBNL)
1564 : // DATE WRITTEN Feb 2006
1565 :
1566 : // PURPOSE OF THIS SUBROUTINE:
1567 : // New routine used to detect whether controller can be considered converged
1568 : // depending on its mode of operation.
1569 : // Used after all controllers on an air loop have been solved in order
1570 : // to make sure that final air loop state still represents a converged
1571 : // state.
1572 : // PRECONDITION: Setpoint must be known. See ControllerProps%IsSetPointDefinedFlag
1573 :
1574 15612270 : auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
1575 15612270 : auto &rootFinders = state.dataHVACControllers->RootFinders(ControlNum);
1576 :
1577 : // Default initialization: assuming no convergence unless detected in the following code!
1578 15612270 : IsConvergedFlag = false;
1579 :
1580 15612270 : switch (controllerProps.Mode) {
1581 1665982 : case ControllerMode::Off: {
1582 : // Check whether the component is running
1583 : // This check is performed by looking at the component mass flow rate at the sensed node.
1584 : // Since the components have been simulated before getting here, if they are zero they should be OFF.
1585 1665982 : if (state.dataLoopNodes->Node(controllerProps.SensedNode).MassFlowRate == 0.0) {
1586 1665966 : if (controllerProps.ActuatedValue == 0.0) {
1587 1665966 : IsConvergedFlag = true;
1588 1665966 : return;
1589 : }
1590 : }
1591 16 : } break;
1592 617346 : case ControllerMode::Inactive: {
1593 : // Controller component NOT available (ie, inactive)
1594 : // Make sure that the actuated variable is still equal to the node min avail
1595 : // NOTE: Replaced Node(ActuatedNode)%MassFlowRateMinAvail in release 1.3
1596 : // with ControllerProps(ControlNum)%MinAvailActuated in release 1.4
1597 617346 : if (controllerProps.ActuatedValue == controllerProps.MinAvailActuated) {
1598 617346 : IsConvergedFlag = true;
1599 617346 : return;
1600 : }
1601 0 : } break;
1602 6287783 : case ControllerMode::MinActive: {
1603 : // Check for min constrained convergence
1604 6287783 : if (CheckMinActiveController(state, ControlNum)) {
1605 5447053 : IsConvergedFlag = true;
1606 5447053 : return;
1607 : }
1608 : // Check for unconstrained convergence assuming that there is more than one controller controlling
1609 : // the same sensed node and that the other controller was able to meet the setpoint although this one
1610 : // was min-constrained.
1611 840730 : if (RootFinder::CheckRootFinderConvergence(rootFinders, controllerProps.DeltaSensed)) {
1612 : // Indicate convergence with base value (used to compute DeltaSensed!)
1613 81683 : IsConvergedFlag = true;
1614 81683 : return;
1615 : }
1616 759047 : } break;
1617 942610 : case ControllerMode::MaxActive: {
1618 : // Check for max constrained convergence
1619 942610 : if (CheckMaxActiveController(state, ControlNum)) {
1620 821754 : IsConvergedFlag = true;
1621 821754 : return;
1622 : }
1623 : // Check for unconstrained convergence assuming that there is more than one controller controlling
1624 : // the same sensed node and that the other controller was able to meet the setpoint although this one
1625 : // was max-constrained.
1626 120856 : if (RootFinder::CheckRootFinderConvergence(rootFinders, controllerProps.DeltaSensed)) {
1627 : // Indicate convergence with base value (used to compute DeltaSensed!)
1628 92 : IsConvergedFlag = true;
1629 92 : return;
1630 : }
1631 120764 : } break;
1632 6098532 : case ControllerMode::Active: {
1633 : // Check min constraint on actuated variable
1634 6098532 : if (controllerProps.ActuatedValue < controllerProps.MinAvailActuated) {
1635 5123 : IsConvergedFlag = false;
1636 5123 : return;
1637 : }
1638 : // Check max constraint on actuated variable
1639 6093409 : if (controllerProps.ActuatedValue > controllerProps.MaxAvailActuated) {
1640 0 : IsConvergedFlag = false;
1641 0 : return;
1642 : }
1643 :
1644 : // Check for unconstrained convergence
1645 : // Equivalent to:
1646 : // IF ((ABS(ControllerProps(ControlNum)%DeltaSensed) .LE. ControllerProps(ControlNum)%Offset)) THEN
1647 : // NOTE: If setpoint has changed since last call, then the following test will most likely fail.
1648 6093409 : if (RootFinder::CheckRootFinderConvergence(rootFinders, controllerProps.DeltaSensed)) {
1649 : // Indicate convergence with base value (used to compute DeltaSensed!)
1650 4724190 : IsConvergedFlag = true;
1651 4724190 : return;
1652 : }
1653 : // Check for min constrained convergence
1654 1369219 : if (CheckMinActiveController(state, ControlNum)) {
1655 57994 : IsConvergedFlag = true;
1656 57994 : return;
1657 : }
1658 : // Check for max constrained convergence
1659 1311225 : if (CheckMaxActiveController(state, ControlNum)) {
1660 19770 : IsConvergedFlag = true;
1661 19770 : return;
1662 : }
1663 1291455 : } break;
1664 17 : default: {
1665 : // Can only happen if controller is not converged after MaxIter in SolveAirLoopControllers()
1666 : // which will produce ControllerProps(ControlNum)%Mode = iModeNone
1667 17 : IsConvergedFlag = false;
1668 17 : } break;
1669 : }
1670 : }
1671 :
1672 7657002 : bool CheckMinActiveController(EnergyPlusData &state, int const ControlNum)
1673 : {
1674 : // FUNCTION INFORMATION:
1675 : // AUTHOR Dimitri Curtil
1676 : // DATE WRITTEN May 2006
1677 :
1678 : // PURPOSE OF THIS FUNCTION:
1679 : // Returns true if controller is min-constrained. false otherwise.
1680 :
1681 7657002 : auto const &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
1682 :
1683 : // Check that actuated value is the min avail actuated value
1684 7657002 : if (controllerProps.ActuatedValue != controllerProps.MinAvailActuated) {
1685 1092627 : return false;
1686 : }
1687 :
1688 6564375 : switch (controllerProps.Action) {
1689 5201329 : case ControllerAction::NormalAction: { // "NORMAL"
1690 : // Check for min constrained convergence
1691 5201329 : if (controllerProps.SetPointValue <= controllerProps.SensedValue) {
1692 4203623 : return true;
1693 : }
1694 997706 : } break;
1695 1363046 : case ControllerAction::Reverse: { // "REVERSE"
1696 : // Check for min constrained convergence
1697 1363046 : if (controllerProps.SetPointValue >= controllerProps.SensedValue) {
1698 1301424 : return true;
1699 : }
1700 61622 : } break;
1701 0 : default: {
1702 : // Should never happen
1703 0 : ShowSevereError(state, format("CheckMinActiveController: Invalid controller action during {}.", CreateHVACStepFullString(state)));
1704 0 : ShowContinueError(state, format("CheckMinActiveController: Controller name={}", controllerProps.ControllerName));
1705 0 : ShowContinueError(state, R"(CheckMinActiveController: Valid choices are "NORMAL" or "REVERSE")");
1706 0 : ShowFatalError(state, "CheckMinActiveController: Preceding error causes program termination.");
1707 0 : } break;
1708 : }
1709 :
1710 1059328 : return false;
1711 : }
1712 :
1713 2253835 : bool CheckMaxActiveController(EnergyPlusData &state, int const ControlNum)
1714 : {
1715 : // FUNCTION INFORMATION:
1716 : // AUTHOR Dimitri Curtil
1717 : // DATE WRITTEN May 2006
1718 :
1719 : // PURPOSE OF THIS FUNCTION:
1720 : // Returns true if controller is max-constrained. false otherwise.
1721 :
1722 2253835 : auto &ControllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
1723 :
1724 : // Check that actuated value is the max avail actuated value
1725 2253835 : if (ControllerProps.ActuatedValue != ControllerProps.MaxAvailActuated) {
1726 1287259 : return false;
1727 : }
1728 :
1729 966576 : switch (ControllerProps.Action) {
1730 332531 : case ControllerAction::NormalAction: { // "NORMAL"
1731 : // Check for max constrained convergence
1732 332531 : if (ControllerProps.SetPointValue >= ControllerProps.SensedValue) {
1733 327655 : return true;
1734 : }
1735 4876 : } break;
1736 634045 : case ControllerAction::Reverse: { // "REVERSE"
1737 : // Check for max constrained convergence
1738 634045 : if (ControllerProps.SetPointValue <= ControllerProps.SensedValue) {
1739 513869 : return true;
1740 : }
1741 120176 : } break;
1742 0 : default: {
1743 : // Should never happen
1744 0 : ShowSevereError(state, format("CheckMaxActiveController: Invalid controller action during {}.", CreateHVACStepFullString(state)));
1745 0 : ShowContinueError(state, format("CheckMaxActiveController: Controller name={}", ControllerProps.ControllerName));
1746 0 : ShowContinueError(state, R"(CheckMaxActiveController: Valid choices are "NORMAL" or "REVERSE")");
1747 0 : ShowFatalError(state, "CheckMaxActiveController: Preceding error causes program termination.");
1748 0 : } break;
1749 : }
1750 :
1751 125052 : return false;
1752 : }
1753 :
1754 15612270 : void SaveSimpleController(EnergyPlusData &state, int const ControlNum, bool const FirstHVACIteration, bool const IsConvergedFlag)
1755 : {
1756 :
1757 : // SUBROUTINE INFORMATION:
1758 : // AUTHOR Dimitri Curtil
1759 : // DATE WRITTEN April 2006
1760 :
1761 : // PURPOSE OF THIS SUBROUTINE:
1762 : // Updates solution trackers if simple controller is converged.
1763 :
1764 15612270 : auto &ControllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
1765 :
1766 : // Save solution and mode for next call only if converged
1767 15612270 : if (IsConvergedFlag) {
1768 13435848 : int PreviousSolutionIndex = FirstHVACIteration ? 1 : 2;
1769 :
1770 13435848 : if (ControllerProps.Mode == ControllerMode::Active) {
1771 4801954 : ControllerProps.SolutionTrackers(PreviousSolutionIndex).DefinedFlag = true;
1772 4801954 : ControllerProps.SolutionTrackers(PreviousSolutionIndex).Mode = ControllerProps.Mode;
1773 4801954 : ControllerProps.SolutionTrackers(PreviousSolutionIndex).ActuatedValue = ControllerProps.NextActuatedValue;
1774 : } else {
1775 8633894 : ControllerProps.SolutionTrackers(PreviousSolutionIndex).DefinedFlag = false;
1776 8633894 : ControllerProps.SolutionTrackers(PreviousSolutionIndex).Mode = ControllerProps.Mode;
1777 8633894 : ControllerProps.SolutionTrackers(PreviousSolutionIndex).ActuatedValue = ControllerProps.NextActuatedValue;
1778 : }
1779 : }
1780 15612270 : }
1781 :
1782 37894646 : void UpdateController(EnergyPlusData &state, int const ControlNum)
1783 : {
1784 : // PURPOSE OF THIS SUBROUTINE:
1785 : // This subroutine updates the actuated node with the next candidate value.
1786 :
1787 37894646 : auto &ControllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
1788 :
1789 : // Set the actuated node of the Controller
1790 37894646 : switch (ControllerProps.ActuatorVar) {
1791 37894646 : case CtrlVarType::Flow: { // 'Flow'
1792 37894646 : PlantUtilities::SetActuatedBranchFlowRate(
1793 37894646 : state, ControllerProps.NextActuatedValue, ControllerProps.ActuatedNode, ControllerProps.ActuatedNodePlantLoc, false);
1794 : // Node(ActuatedNode)%MassFlowRate = ControllerProps(ControlNum)%NextActuatedValue
1795 37894646 : } break;
1796 0 : default: {
1797 0 : ShowFatalError(state, format("UpdateController: Invalid Actuator Variable Type={}", ControlVariableTypes(ControllerProps.ActuatorVar)));
1798 0 : } break;
1799 : }
1800 37894646 : }
1801 :
1802 21963536 : void CheckTempAndHumRatCtrl(EnergyPlusData &state, int const ControlNum, bool &IsConvergedFlag)
1803 : {
1804 : {
1805 21963536 : auto &thisController = state.dataHVACControllers->ControllerProps(ControlNum);
1806 21963536 : if (IsConvergedFlag) {
1807 10465547 : if (thisController.ControlVar == CtrlVarType::TemperatureAndHumidityRatio) {
1808 : // For temperature and humidity control, after temperature control is converged, check if humidity setpoint is met
1809 694596 : if (!thisController.HumRatCtrlOverride) {
1810 : // For humidity control tolerance, always use 0.0001 which is roughly equivalent to a 0.015C change in dewpoint
1811 624430 : if (state.dataLoopNodes->Node(thisController.SensedNode).HumRat >
1812 624430 : (state.dataLoopNodes->Node(thisController.SensedNode).HumRatMax + 1.0e-5)) {
1813 : // Turn on humidity control and restart controller
1814 70183 : IsConvergedFlag = false;
1815 70183 : thisController.HumRatCtrlOverride = true;
1816 70183 : if (thisController.Action == ControllerAction::Reverse) {
1817 : // Cooling coil controller should always be Reverse, but skip this if not
1818 70183 : RootFinder::SetupRootFinder(state,
1819 70183 : state.dataHVACControllers->RootFinders(ControlNum),
1820 : DataRootFinder::Slope::Decreasing,
1821 : DataRootFinder::RootFinderMethod::FalsePosition,
1822 : DataPrecisionGlobals::constant_zero,
1823 : 1.0e-6,
1824 : 1.0e-5);
1825 : }
1826 : // Do a cold start reset, same as iControllerOpColdStart
1827 70183 : ResetController(state, ControlNum, false, IsConvergedFlag);
1828 : }
1829 : }
1830 : }
1831 : }
1832 : }
1833 21963536 : }
1834 :
1835 10465547 : void ExitCalcController(EnergyPlusData &state,
1836 : int const ControlNum,
1837 : Real64 const NextActuatedValue,
1838 : ControllerMode const Mode,
1839 : bool &IsConvergedFlag,
1840 : bool &IsUpToDateFlag)
1841 : {
1842 :
1843 : // SUBROUTINE INFORMATION:
1844 : // AUTHOR Dimitri Curtil
1845 : // DATE WRITTEN February 06
1846 :
1847 : // PURPOSE OF THIS SUBROUTINE:
1848 : // Only called when controller is considered as "converged", meaning that we do no longer
1849 : // need to continue iterating.
1850 :
1851 : // METHODOLOGY EMPLOYED:
1852 : // Updates:
1853 : // - next actuated value
1854 : // - controller mode
1855 : // - IsConvergedFlag
1856 : // - IsUpToDateFlag
1857 :
1858 10465547 : auto &ControllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
1859 :
1860 10465547 : ControllerProps.NextActuatedValue = NextActuatedValue;
1861 10465547 : ControllerProps.Mode = Mode;
1862 10465547 : IsConvergedFlag = true;
1863 :
1864 : // Set IsUpToDateFlag upon exiting to indicate caller whether or not the air loop needs to be
1865 : // re-simulated with the current candidate value, ie ControllerProps(ControlNum)%NextActuatedValue
1866 10465547 : if (ControllerProps.ActuatedValue != ControllerProps.NextActuatedValue) {
1867 402461 : IsUpToDateFlag = false;
1868 : } else {
1869 10063086 : IsUpToDateFlag = true;
1870 : }
1871 10465547 : }
1872 :
1873 0 : void TrackAirLoopControllers(EnergyPlusData &state,
1874 : int const AirLoopNum,
1875 : DataHVACControllers::ControllerWarmRestart const WarmRestartStatus,
1876 : int const AirLoopIterMax,
1877 : int const AirLoopIterTot,
1878 : int const AirLoopNumCalls)
1879 : {
1880 :
1881 : // SUBROUTINE INFORMATION:
1882 : // AUTHOR Dimitri Curtil
1883 : // DATE WRITTEN April 2006
1884 :
1885 : // PURPOSE OF THIS SUBROUTINE:
1886 : // Updates runtime statistics for controllers on the specified air loop.
1887 : // Used to produce objective metrics when analyzing runtime performance
1888 : // of HVAC controllers for different implementations.
1889 :
1890 : // SUBROUTINE PARAMETER DEFINITIONS:
1891 : // See CONTROLLER_WARM_RESTART_<> parameters in DataHVACControllers.cc
1892 : // If Status<0, no speculative warm restart.
1893 : // If Status==0, speculative warm restart failed.
1894 : // If Status>0, speculative warm restart succeeded.
1895 : // Max number of iterations performed by controllers on this air loop (per call to SimAirLoop)
1896 : // Aggregated number of iterations performed by controllers on this air loop (per call to SimAirLoop)
1897 : // Number of times SimAirLoopComponents() has been invoked
1898 :
1899 0 : auto &airLoopStats = state.dataHVACControllers->AirLoopStats(AirLoopNum);
1900 :
1901 : // If no controllers on this air loop then we have nothing to do
1902 0 : if (state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers == 0) {
1903 0 : return;
1904 : }
1905 : // To avoid tracking statistics in case of no air loop or no HVAC controllers are defined
1906 0 : if (state.dataHVACControllers->NumAirLoopStats == 0) {
1907 0 : return;
1908 : }
1909 :
1910 : // Update performance statistics for air loop
1911 0 : ++airLoopStats.NumCalls;
1912 :
1913 0 : switch (WarmRestartStatus) {
1914 0 : case DataHVACControllers::ControllerWarmRestart::Success: {
1915 0 : ++airLoopStats.NumSuccessfulWarmRestarts;
1916 0 : } break;
1917 0 : case DataHVACControllers::ControllerWarmRestart::Fail: {
1918 0 : ++airLoopStats.NumFailedWarmRestarts;
1919 0 : } break;
1920 0 : default: {
1921 : // Nothing to do if no speculative warm restart used
1922 0 : } break;
1923 : }
1924 :
1925 0 : airLoopStats.TotSimAirLoopComponents += AirLoopNumCalls;
1926 :
1927 0 : airLoopStats.MaxSimAirLoopComponents = max(airLoopStats.MaxSimAirLoopComponents, AirLoopNumCalls);
1928 :
1929 0 : airLoopStats.TotIterations += AirLoopIterTot;
1930 :
1931 0 : airLoopStats.MaxIterations = max(airLoopStats.MaxIterations, AirLoopIterMax);
1932 :
1933 : // Update performance statistics for each controller on air loop
1934 0 : for (int ControllerNum = 1; ControllerNum <= state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers; ++ControllerNum) {
1935 0 : TrackAirLoopController(state, AirLoopNum, ControllerNum);
1936 : }
1937 : }
1938 :
1939 0 : void TrackAirLoopController(EnergyPlusData &state,
1940 : int const AirLoopNum, // Air loop index
1941 : int const AirLoopControlNum // Controller index on this air loop
1942 : )
1943 : {
1944 :
1945 : // SUBROUTINE INFORMATION:
1946 : // AUTHOR Dimitri Curtil
1947 : // DATE WRITTEN April 2006
1948 :
1949 : // PURPOSE OF THIS SUBROUTINE:
1950 : // Updates runtime statistics for the specified controller.
1951 : // Used to produce objective metrics when analyzing runtime performance
1952 : // of HVAC controllers for different implementations.
1953 :
1954 0 : auto &airLoopStats = state.dataHVACControllers->AirLoopStats(AirLoopNum);
1955 0 : int ControlIndex = state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).ControllerIndex(AirLoopControlNum);
1956 0 : auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlIndex);
1957 :
1958 : // We use NumCalcCalls instead of the iteration counter used in SolveAirLoopControllers()
1959 : // to avoid having to call TrackAirLoopController() directly from SolveAirLoopControllers().
1960 : // The 2 counters should be the same anyway as NumCalcCalls is first reset to zero and
1961 : // incremented each time ManageControllers() is invoked with iControllerOpIterate
1962 0 : int IterationCount = controllerProps.NumCalcCalls;
1963 0 : ControllerMode Mode = controllerProps.Mode;
1964 :
1965 0 : if (Mode != ControllerMode::None) {
1966 :
1967 0 : ++airLoopStats.ControllerStats(AirLoopControlNum).NumCalls(static_cast<int>(Mode));
1968 :
1969 0 : airLoopStats.ControllerStats(AirLoopControlNum).TotIterations(static_cast<int>(Mode)) += IterationCount;
1970 :
1971 0 : airLoopStats.ControllerStats(AirLoopControlNum).MaxIterations(static_cast<int>(Mode)) =
1972 0 : max(airLoopStats.ControllerStats(AirLoopControlNum).MaxIterations(static_cast<int>(Mode)), IterationCount);
1973 : }
1974 0 : }
1975 :
1976 799 : void DumpAirLoopStatistics(EnergyPlusData &state)
1977 : {
1978 :
1979 : // SUBROUTINE INFORMATION:
1980 : // AUTHOR Dimitri Curtil
1981 : // DATE WRITTEN April 2006
1982 :
1983 : // PURPOSE OF THIS SUBROUTINE:
1984 : // Writes runtime statistics for controllers on all air loops
1985 : // to a CSV file named "statistics.HVACControllers.csv".
1986 :
1987 : // Detect if statistics have been generated or not for this run
1988 799 : if (!state.dataSysVars->TrackAirLoopEnvFlag) {
1989 799 : return;
1990 : }
1991 :
1992 0 : InputOutputFilePath StatisticsFilePath{"statistics.HVACControllers.csv"};
1993 0 : auto statisticsFile = StatisticsFilePath.open(state, "DumpAirLoopStatistics"); // (THIS_AUTO_OK)
1994 :
1995 : // note that the AirLoopStats object does not seem to be initialized when this code
1996 : // is executed and it causes a crash here
1997 0 : for (int AirLoopNum = 1; AirLoopNum <= state.dataHVACGlobal->NumPrimaryAirSys; ++AirLoopNum) {
1998 0 : WriteAirLoopStatistics(
1999 0 : state, statisticsFile, state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum), state.dataHVACControllers->AirLoopStats(AirLoopNum));
2000 : }
2001 0 : }
2002 :
2003 0 : void WriteAirLoopStatistics(EnergyPlusData &state,
2004 : InputOutputFile &statisticsFile,
2005 : DefinePrimaryAirSystem const &ThisPrimaryAirSystem,
2006 : AirLoopStatsType const &ThisAirLoopStats)
2007 : {
2008 :
2009 : // SUBROUTINE INFORMATION:
2010 : // AUTHOR Dimitri Curtil
2011 : // DATE WRITTEN April 2006
2012 :
2013 : // PURPOSE OF THIS SUBROUTINE:
2014 : // Writes runtime statistics for controllers on the specified air loop
2015 : // to the specified file.
2016 :
2017 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
2018 : Real64 WarmRestartSuccessRatio;
2019 : Real64 AvgIterations;
2020 :
2021 0 : print(statisticsFile, "{},\n", ThisPrimaryAirSystem.Name);
2022 :
2023 : // Number of calls to SimAirLoop() has been invoked over the course of the simulation
2024 : // to simulate the specified air loop
2025 0 : print(statisticsFile, "NumCalls,{}\n", ThisAirLoopStats.NumCalls);
2026 :
2027 : // Warm restart success ratio
2028 0 : int NumWarmRestarts = ThisAirLoopStats.NumSuccessfulWarmRestarts + ThisAirLoopStats.NumFailedWarmRestarts;
2029 0 : if (NumWarmRestarts == 0) {
2030 0 : WarmRestartSuccessRatio = 0.0;
2031 : } else {
2032 0 : WarmRestartSuccessRatio = double(ThisAirLoopStats.NumSuccessfulWarmRestarts) / double(NumWarmRestarts);
2033 : }
2034 :
2035 0 : print(statisticsFile, "NumWarmRestarts,{}\n", NumWarmRestarts);
2036 0 : print(statisticsFile, "NumSuccessfulWarmRestarts,{}\n", ThisAirLoopStats.NumSuccessfulWarmRestarts);
2037 0 : print(statisticsFile, "NumFailedWarmRestarts,{}\n", ThisAirLoopStats.NumFailedWarmRestarts);
2038 0 : print(statisticsFile, "WarmRestartSuccessRatio,{:.10T}\n", WarmRestartSuccessRatio);
2039 :
2040 : // Total number of times SimAirLoopComponents() has been invoked over the course of the simulation
2041 : // to simulate the specified air loop
2042 0 : print(statisticsFile, "TotSimAirLoopComponents,{}\n", ThisAirLoopStats.TotSimAirLoopComponents);
2043 : // Maximum number of times SimAirLoopComponents() has been invoked over the course of the simulation
2044 : // to simulate the specified air loop
2045 0 : print(statisticsFile, "MaxSimAirLoopComponents,{}\n", ThisAirLoopStats.MaxSimAirLoopComponents);
2046 :
2047 : // Aggregated number of iterations needed by all controllers to simulate the specified air loop
2048 0 : print(statisticsFile, "TotIterations,{}\n", ThisAirLoopStats.TotIterations);
2049 : // Maximum number of iterations needed by controllers to simulate the specified air loop
2050 0 : print(statisticsFile, "MaxIterations,{}\n", ThisAirLoopStats.MaxIterations);
2051 :
2052 : // Average number of iterations needed by controllers to simulate the specified air loop
2053 0 : if (ThisAirLoopStats.NumCalls == 0) {
2054 0 : AvgIterations = 0.0;
2055 : } else {
2056 0 : AvgIterations = double(ThisAirLoopStats.TotIterations) / double(ThisAirLoopStats.NumCalls);
2057 : }
2058 :
2059 0 : print(statisticsFile, "AvgIterations,{:.10T}\n", AvgIterations);
2060 :
2061 : // Dump statistics for each controller on this air loop
2062 0 : for (int AirLoopControlNum = 1; AirLoopControlNum <= ThisPrimaryAirSystem.NumControllers; ++AirLoopControlNum) {
2063 :
2064 0 : print(statisticsFile, "{},\n", ThisPrimaryAirSystem.ControllerName(AirLoopControlNum));
2065 :
2066 : // Aggregate iteration trackers across all operating modes
2067 0 : int NumCalls = 0;
2068 0 : int TotIterations = 0;
2069 0 : int MaxIterations = 0;
2070 :
2071 0 : for (int iModeNum = iFirstMode; iModeNum <= iLastMode; ++iModeNum) {
2072 0 : NumCalls += ThisAirLoopStats.ControllerStats(AirLoopControlNum).NumCalls(iModeNum);
2073 :
2074 0 : TotIterations += ThisAirLoopStats.ControllerStats(AirLoopControlNum).TotIterations(iModeNum);
2075 :
2076 0 : MaxIterations = max(MaxIterations, ThisAirLoopStats.ControllerStats(AirLoopControlNum).MaxIterations(iModeNum));
2077 : }
2078 :
2079 : // Number of times this controller was simulated (should match air loop num calls)
2080 0 : print(statisticsFile, "NumCalls,{}\n", NumCalls);
2081 : // Aggregated number of iterations needed by this controller
2082 0 : print(statisticsFile, "TotIterations,{}\n", TotIterations);
2083 : // Aggregated number of iterations needed by this controller
2084 0 : print(statisticsFile, "MaxIterations,{}\n", MaxIterations);
2085 :
2086 : // Average number of iterations needed by controllers to simulate the specified air loop
2087 0 : if (NumCalls == 0) {
2088 0 : AvgIterations = 0.0;
2089 : } else {
2090 0 : AvgIterations = double(TotIterations) / double(NumCalls);
2091 : }
2092 0 : print(statisticsFile, "AvgIterations,{:.10T}\n", AvgIterations);
2093 :
2094 : // Dump iteration trackers for each operating mode
2095 0 : for (int iModeNum = iFirstMode; iModeNum <= iLastMode; ++iModeNum) {
2096 :
2097 0 : print(statisticsFile, "{},\n", state.dataHVACCtrl->ControllerModeTypes(iModeNum));
2098 :
2099 : // Number of times this controller operated in this mode
2100 0 : print(statisticsFile, "NumCalls,{}\n", ThisAirLoopStats.ControllerStats(AirLoopControlNum).NumCalls(iModeNum));
2101 :
2102 : // Aggregated number of iterations needed by this controller
2103 0 : print(statisticsFile, "TotIterations,{}\n", ThisAirLoopStats.ControllerStats(AirLoopControlNum).TotIterations(iModeNum));
2104 : // Aggregated number of iterations needed by this controller
2105 0 : print(statisticsFile, "MaxIterations,{}\n", ThisAirLoopStats.ControllerStats(AirLoopControlNum).MaxIterations(iModeNum));
2106 :
2107 : // Average number of iterations needed by controllers to simulate the specified air loop
2108 0 : if (ThisAirLoopStats.ControllerStats(AirLoopControlNum).NumCalls(iModeNum) == 0) {
2109 0 : AvgIterations = 0.0;
2110 : } else {
2111 0 : AvgIterations = double(ThisAirLoopStats.ControllerStats(AirLoopControlNum).TotIterations(iModeNum)) /
2112 0 : double(ThisAirLoopStats.ControllerStats(AirLoopControlNum).NumCalls(iModeNum));
2113 : }
2114 0 : print(statisticsFile, "AvgIterations,{:.10T}\n", AvgIterations);
2115 : }
2116 : }
2117 0 : }
2118 :
2119 0 : void SetupAirLoopControllersTracer(EnergyPlusData &state, int const AirLoopNum)
2120 : {
2121 :
2122 : // SUBROUTINE INFORMATION:
2123 : // AUTHOR Dimitri Curtil
2124 : // DATE WRITTEN February 2006
2125 :
2126 : // PURPOSE OF THIS SUBROUTINE:
2127 : // Opens main trace file for controllers on specific air loop
2128 : // and writes header row with titles.
2129 :
2130 : // Open main controller trace file for each air loop
2131 0 : const std::string TraceFilePath = fmt::format("controller.{}.csv", state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).Name);
2132 :
2133 0 : auto &airLoopStats = state.dataHVACControllers->AirLoopStats(AirLoopNum);
2134 :
2135 : // Store file unit in air loop stats
2136 0 : airLoopStats.TraceFile->filePath = TraceFilePath;
2137 0 : airLoopStats.TraceFile->open();
2138 :
2139 0 : if (!airLoopStats.TraceFile->good()) {
2140 0 : ShowFatalError(state, format("SetupAirLoopControllersTracer: Failed to open air loop trace file \"{}\" for output (write).", TraceFilePath));
2141 0 : return;
2142 : }
2143 :
2144 0 : auto &TraceFile = *airLoopStats.TraceFile;
2145 :
2146 : // List all controllers and their corresponding handles into main trace file
2147 0 : print(TraceFile, "Num,Name,\n");
2148 :
2149 0 : for (int ControllerNum = 1; ControllerNum <= state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers; ++ControllerNum) {
2150 0 : print(TraceFile, "{},{},\n", ControllerNum, state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).ControllerName(ControllerNum));
2151 : // SAME AS ControllerProps(ControllerIndex)%ControllerName BUT NOT YET AVAILABLE
2152 : }
2153 :
2154 : // Skip a bunch of lines
2155 0 : print(TraceFile, "\n\n\n");
2156 :
2157 : // Write column header in main controller trace file
2158 0 : print(TraceFile,
2159 : "ZoneSizingCalc,SysSizingCalc,EnvironmentNum,WarmupFlag,SysTimeStamp,SysTimeInterval,BeginTimeStepFlag,FirstTimeStepSysFlag,"
2160 : "FirstHVACIteration,AirLoopPass,AirLoopNumCallsTot,AirLoopConverged,");
2161 :
2162 : // Write headers for final state
2163 0 : for (int ControllerNum = 1; ControllerNum <= state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers; ++ControllerNum) {
2164 0 : print(
2165 : TraceFile, "Mode{},IterMax{},XRoot{},YRoot{},YSetPoint{},\n", ControllerNum, ControllerNum, ControllerNum, ControllerNum, ControllerNum);
2166 : }
2167 :
2168 0 : print(TraceFile, "\n");
2169 0 : }
2170 :
2171 0 : void TraceAirLoopControllers(EnergyPlusData &state,
2172 : bool const FirstHVACIteration,
2173 : int const AirLoopNum,
2174 : int const AirLoopPass,
2175 : bool const AirLoopConverged,
2176 : int const AirLoopNumCalls)
2177 : {
2178 :
2179 : // SUBROUTINE INFORMATION:
2180 : // AUTHOR Dimitri Curtil
2181 : // DATE WRITTEN January 2006
2182 :
2183 : // PURPOSE OF THIS SUBROUTINE:
2184 : // This subroutine writes diagnostic to the trace file attached to each air loop.
2185 :
2186 : // SUBROUTINE ARGUMENT DEFINITIONS:
2187 : // TRUE when primary air system & controllers simulation has converged;
2188 : // Number of times SimAirLoopComponents() has been invoked
2189 :
2190 0 : auto &airLoopStats = state.dataHVACControllers->AirLoopStats(AirLoopNum);
2191 :
2192 : // IF no controllers on this air loop then we have nothing to do
2193 0 : if (state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers == 0) {
2194 0 : return;
2195 : }
2196 : // To avoid tracking statistics in case of no air loop or no HVAC controllers are defined
2197 0 : if (state.dataHVACControllers->NumAirLoopStats == 0) {
2198 0 : return;
2199 : }
2200 :
2201 : // Setup trace file on first call only
2202 0 : if (airLoopStats.FirstTraceFlag) {
2203 0 : SetupAirLoopControllersTracer(state, AirLoopNum);
2204 :
2205 0 : airLoopStats.FirstTraceFlag = false;
2206 : }
2207 :
2208 0 : auto &TraceFile = *airLoopStats.TraceFile;
2209 :
2210 0 : if (!TraceFile.good()) {
2211 0 : return;
2212 : }
2213 :
2214 : // Write iteration stamp first
2215 0 : TraceIterationStamp(state, TraceFile, FirstHVACIteration, AirLoopPass, AirLoopConverged, AirLoopNumCalls);
2216 :
2217 : // Loop over the air sys controllers and write diagnostic to trace file
2218 0 : for (int ControllerNum = 1; ControllerNum <= state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers; ++ControllerNum) {
2219 0 : TraceAirLoopController(state, TraceFile, state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).ControllerIndex(ControllerNum));
2220 : }
2221 :
2222 : // Go to next line
2223 0 : print(TraceFile, "\n");
2224 : }
2225 :
2226 0 : void TraceIterationStamp(EnergyPlusData &state,
2227 : InputOutputFile &TraceFile,
2228 : bool const FirstHVACIteration,
2229 : int const AirLoopPass,
2230 : bool const AirLoopConverged,
2231 : int const AirLoopNumCalls)
2232 : {
2233 :
2234 : // SUBROUTINE INFORMATION:
2235 : // AUTHOR Dimitri Curtil
2236 : // DATE WRITTEN February 2006
2237 :
2238 : // PURPOSE OF THIS SUBROUTINE:
2239 : // Writes current iteration time stamp to specified trace file.
2240 :
2241 : // SUBROUTINE PARAMETER DEFINITIONS:
2242 : // TRUE when primary air system and controllers simulation has converged;
2243 : // Number of times SimAirLoopComponents() has been invoked
2244 :
2245 : // Write step stamp to air loop trace file after reset
2246 : // Note that we do not go to the next line
2247 0 : print(TraceFile,
2248 : "{},{},{},{},{},{},{},{},{},{},{},{},",
2249 0 : static_cast<int>(state.dataGlobal->ZoneSizingCalc),
2250 0 : static_cast<int>(state.dataGlobal->SysSizingCalc),
2251 0 : state.dataEnvrn->CurEnvirNum,
2252 0 : static_cast<int>(state.dataGlobal->WarmupFlag),
2253 0 : CreateHVACTimeString(state),
2254 0 : MakeHVACTimeIntervalString(state),
2255 0 : static_cast<int>(state.dataGlobal->BeginTimeStepFlag),
2256 0 : static_cast<int>(state.dataHVACGlobal->FirstTimeStepSysFlag),
2257 0 : static_cast<int>(FirstHVACIteration),
2258 : AirLoopPass,
2259 : AirLoopNumCalls,
2260 0 : static_cast<int>(AirLoopConverged));
2261 0 : }
2262 :
2263 0 : void TraceAirLoopController(EnergyPlusData &state, InputOutputFile &TraceFile, int const ControlNum)
2264 : {
2265 :
2266 : // SUBROUTINE INFORMATION:
2267 : // AUTHOR Dimitri Curtil
2268 : // DATE WRITTEN January 2006
2269 :
2270 : // PURPOSE OF THIS SUBROUTINE:
2271 : // This subroutine writes convergence diagnostic to the air loop trace file
2272 : // for the specified controller index.
2273 :
2274 0 : auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
2275 :
2276 0 : print(TraceFile,
2277 : "{},{},{:.10T},{:.10T},{:.10T},",
2278 0 : controllerProps.Mode,
2279 0 : controllerProps.NumCalcCalls,
2280 0 : state.dataLoopNodes->Node(controllerProps.ActuatedNode).MassFlowRate,
2281 0 : state.dataLoopNodes->Node(controllerProps.SensedNode).Temp,
2282 0 : state.dataLoopNodes->Node(controllerProps.SensedNode).TempSetPoint);
2283 0 : }
2284 :
2285 0 : void SetupIndividualControllerTracer(EnergyPlusData &state, int const ControlNum)
2286 : {
2287 :
2288 : // SUBROUTINE INFORMATION:
2289 : // AUTHOR Dimitri Curtil
2290 : // DATE WRITTEN February 2006
2291 :
2292 : // PURPOSE OF THIS SUBROUTINE:
2293 : // Opens individual controller trace file for the specified controller
2294 : // and writes header row.
2295 :
2296 0 : auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
2297 :
2298 0 : const std::string TraceFilePath = fmt::format("controller.{}.csv", controllerProps.ControllerName);
2299 0 : auto &TraceFile = *controllerProps.TraceFile;
2300 0 : TraceFile.filePath = TraceFilePath;
2301 0 : TraceFile.open();
2302 :
2303 0 : if (!TraceFile.good()) {
2304 0 : ShowFatalError(state,
2305 0 : format("SetupIndividualControllerTracer: Failed to open controller trace file \"{}\" for output (write).", TraceFilePath));
2306 0 : return;
2307 : }
2308 :
2309 : // Write header row
2310 : // Masss flow rate
2311 : // Convergence analysis
2312 0 : print(TraceFile,
2313 : "EnvironmentNum,WarmupFlag,SysTimeStamp,SysTimeInterval,AirLoopPass,FirstHVACIteration,Operation,NumCalcCalls,SensedNode%MassFlowRate,"
2314 : "ActuatedNode%MassFlowRateMinAvail,ActuatedNode%MassFlowRateMaxAvail,X,Y,Setpoint,DeltaSensed,Offset,Mode,IsConvergedFlag,"
2315 : "NextActuatedValue");
2316 :
2317 0 : RootFinder::WriteRootFinderTraceHeader(TraceFile);
2318 :
2319 : // Finally skip line
2320 0 : print(TraceFile, "\n");
2321 0 : }
2322 :
2323 0 : void TraceIndividualController(EnergyPlusData &state,
2324 : int const ControlNum,
2325 : bool const FirstHVACIteration,
2326 : int const AirLoopPass,
2327 : DataHVACControllers::ControllerOperation const Operation, // Operation to execute
2328 : bool const IsConvergedFlag)
2329 : {
2330 :
2331 : // SUBROUTINE INFORMATION:
2332 : // AUTHOR Dimitri Curtil
2333 : // DATE WRITTEN January 2006
2334 :
2335 : // PURPOSE OF THIS SUBROUTINE:
2336 : // This subroutine writes convergence diagnostic to the trace file for the specified controller.
2337 :
2338 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
2339 : bool SkipLineFlag;
2340 :
2341 0 : auto &ControllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
2342 :
2343 : // Setup individual trace file on first trace only
2344 0 : if (ControllerProps.FirstTraceFlag) {
2345 0 : SetupIndividualControllerTracer(state, ControlNum);
2346 :
2347 0 : ControllerProps.FirstTraceFlag = false;
2348 0 : SkipLineFlag = false;
2349 : } else {
2350 0 : SkipLineFlag = FirstHVACIteration && (ControllerProps.NumCalcCalls == 0);
2351 : }
2352 :
2353 0 : auto &TraceFile = *ControllerProps.TraceFile;
2354 :
2355 : // Nothing to do if trace file not registered
2356 0 : if (!TraceFile.good()) {
2357 0 : return;
2358 : }
2359 :
2360 : // Skip a line before each new HVAC step
2361 0 : if (SkipLineFlag) {
2362 0 : print(TraceFile, "\n");
2363 : }
2364 :
2365 : // Set the sensed and actuated node numbers
2366 0 : int ActuatedNode = ControllerProps.ActuatedNode;
2367 0 : int SensedNode = ControllerProps.SensedNode;
2368 :
2369 : // Write iteration stamp
2370 0 : print(TraceFile,
2371 : "{},{},{},{},{},{},{},{},",
2372 0 : state.dataEnvrn->CurEnvirNum,
2373 0 : static_cast<int>(state.dataGlobal->WarmupFlag),
2374 0 : CreateHVACTimeString(state),
2375 0 : MakeHVACTimeIntervalString(state),
2376 : AirLoopPass,
2377 0 : static_cast<int>(FirstHVACIteration),
2378 : Operation,
2379 0 : ControllerProps.NumCalcCalls);
2380 :
2381 : // Write detailed diagnostic
2382 0 : switch (Operation) {
2383 0 : case DataHVACControllers::ControllerOperation::ColdStart:
2384 : case DataHVACControllers::ControllerOperation::WarmRestart: {
2385 0 : print(TraceFile,
2386 : "{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{},{},{},{},{:.10T},",
2387 0 : state.dataLoopNodes->Node(SensedNode).MassFlowRate,
2388 0 : state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMinAvail,
2389 0 : state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMaxAvail,
2390 0 : ControllerProps.ActuatedValue,
2391 0 : state.dataLoopNodes->Node(SensedNode).Temp,
2392 0 : ControllerProps.SetPointValue,
2393 0 : ' ',
2394 0 : ' ',
2395 0 : ControllerProps.Mode,
2396 0 : static_cast<int>(IsConvergedFlag),
2397 0 : ControllerProps.NextActuatedValue);
2398 : // X | Y | setpoint | DeltaSensed = Y - YRoot | Offset | Mode | IsConvergedFlag
2399 :
2400 : // No trace available for root finder yet
2401 : // Skip call to WriteRootFinderTrace()
2402 :
2403 : // Finally skip line
2404 0 : print(TraceFile, "\n");
2405 0 : } break;
2406 0 : case DataHVACControllers::ControllerOperation::Iterate: {
2407 : // Masss flow rate
2408 : // Convergence analysis
2409 :
2410 0 : print(TraceFile,
2411 : "{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{},{},{:.10T},",
2412 0 : state.dataLoopNodes->Node(SensedNode).MassFlowRate,
2413 0 : state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMinAvail,
2414 0 : state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMaxAvail,
2415 0 : ControllerProps.ActuatedValue,
2416 0 : state.dataLoopNodes->Node(SensedNode).Temp,
2417 0 : ControllerProps.SetPointValue,
2418 0 : ControllerProps.DeltaSensed,
2419 0 : ControllerProps.Offset,
2420 0 : ControllerProps.Mode,
2421 0 : static_cast<int>(IsConvergedFlag),
2422 0 : ControllerProps.NextActuatedValue);
2423 :
2424 : // X | Y | setpoint | DeltaSensed = Y - YRoot | Offset | Mode | IsConvergedFlag
2425 :
2426 : // Append trace for root finder
2427 0 : RootFinder::WriteRootFinderTrace(TraceFile, state.dataHVACControllers->RootFinders(ControlNum));
2428 :
2429 : // Finally skip line
2430 0 : print(TraceFile, "\n");
2431 :
2432 0 : } break;
2433 0 : case DataHVACControllers::ControllerOperation::End: {
2434 : // Masss flow rate
2435 : // Convergence analysis
2436 0 : print(TraceFile,
2437 : "{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{},{},{:.10T},",
2438 0 : state.dataLoopNodes->Node(SensedNode).MassFlowRate,
2439 0 : state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMinAvail,
2440 0 : state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMaxAvail,
2441 0 : ControllerProps.ActuatedValue,
2442 0 : state.dataLoopNodes->Node(SensedNode).Temp,
2443 0 : ControllerProps.SetPointValue,
2444 0 : ControllerProps.DeltaSensed,
2445 0 : ControllerProps.Offset,
2446 0 : ControllerProps.Mode,
2447 0 : static_cast<int>(IsConvergedFlag),
2448 0 : ControllerProps.NextActuatedValue);
2449 :
2450 : // X | Y | setpoint | DeltaSensed = Y - YRoot | Offset | Mode | IsConvergedFlag
2451 :
2452 : // No trace available for root finder yet
2453 : // Skip call to WriteRootFinderTrace()
2454 :
2455 : // Finally skip line
2456 0 : print(TraceFile, "\n");
2457 :
2458 : // Skip an additional line to indicate end of current HVAC step
2459 0 : print(TraceFile, "\n");
2460 :
2461 0 : } break;
2462 0 : default: {
2463 : // Should never happen
2464 0 : ShowFatalError(
2465 0 : state, format("TraceIndividualController: Invalid Operation passed={}, Controller name={}", Operation, ControllerProps.ControllerName));
2466 0 : } break;
2467 : }
2468 :
2469 0 : TraceFile.flush();
2470 : }
2471 :
2472 0 : Real64 GetCurrentHVACTime(const EnergyPlusData &state)
2473 : {
2474 : // SUBROUTINE INFORMATION:
2475 : // AUTHOR Dimitri Curtil
2476 : // DATE WRITTEN November 2004
2477 : // MODIFIED na
2478 : // RE-ENGINEERED na
2479 :
2480 : // PURPOSE OF THIS FUNCTION:
2481 : // This routine returns the time in seconds at the end of the current HVAC step.
2482 :
2483 : // This is the correct formula that does not use MinutesPerSystemTimeStep, which would
2484 : // erronously truncate all sub-minute system time steps down to the closest full minute.
2485 : // Maybe later TimeStepZone, TimeStepSys and SysTimeElapsed could also be specified
2486 : // as real.
2487 : Real64 const CurrentHVACTime =
2488 0 : (state.dataGlobal->CurrentTime - state.dataGlobal->TimeStepZone) + state.dataHVACGlobal->SysTimeElapsed + state.dataHVACGlobal->TimeStepSys;
2489 0 : return CurrentHVACTime * Constant::rSecsInHour;
2490 : }
2491 :
2492 2854691 : Real64 GetPreviousHVACTime(const EnergyPlusData &state)
2493 : {
2494 : // SUBROUTINE INFORMATION:
2495 : // AUTHOR Dimitri Curtil
2496 : // DATE WRITTEN November 2004
2497 :
2498 : // PURPOSE OF THIS FUNCTION:
2499 : // This routine returns the time in seconds at the beginning of the current HVAC step.
2500 :
2501 : // This is the correct formula that does not use MinutesPerSystemTimeStep, which would
2502 : // erronously truncate all sub-minute system time steps down to the closest full minute.
2503 2854691 : Real64 const PreviousHVACTime = (state.dataGlobal->CurrentTime - state.dataGlobal->TimeStepZone) + state.dataHVACGlobal->SysTimeElapsed;
2504 2854691 : return PreviousHVACTime * Constant::rSecsInHour;
2505 : }
2506 :
2507 0 : std::string CreateHVACTimeString(const EnergyPlusData &state)
2508 : {
2509 :
2510 : // FUNCTION INFORMATION:
2511 : // AUTHOR Dimitri Curtil
2512 : // DATE WRITTEN January 2006
2513 :
2514 : // PURPOSE OF THIS FUNCTION:
2515 : // This function creates a string describing the current time stamp of the system
2516 : // time step.
2517 :
2518 0 : std::string Buffer = General::CreateTimeString(GetCurrentHVACTime(state));
2519 0 : return state.dataEnvrn->CurMnDy + ' ' + stripped(Buffer);
2520 0 : }
2521 :
2522 0 : std::string CreateHVACStepFullString(const EnergyPlusData &state)
2523 : {
2524 :
2525 : // FUNCTION INFORMATION:
2526 : // AUTHOR Dimitri Curtil
2527 : // DATE WRITTEN April 2006
2528 :
2529 : // PURPOSE OF THIS FUNCTION:
2530 : // This function creates a string describing the current HVAC step.
2531 : // It includes the environment name, the current day/month and the current
2532 : // time stamp for the system time step.
2533 : // It is used in error messages only.
2534 :
2535 0 : return state.dataEnvrn->EnvironmentName + ", " + MakeHVACTimeIntervalString(state);
2536 : }
2537 :
2538 0 : std::string MakeHVACTimeIntervalString(const EnergyPlusData &state)
2539 : {
2540 :
2541 : // FUNCTION INFORMATION:
2542 : // AUTHOR Dimitri Curtil
2543 : // DATE WRITTEN January 2006
2544 :
2545 : // PURPOSE OF THIS FUNCTION:
2546 : // This function creates a string describing the current time interval of the system
2547 : // time step.
2548 :
2549 0 : return format("{} - {}", General::CreateTimeString(GetPreviousHVACTime(state)), General::CreateTimeString(GetCurrentHVACTime(state)));
2550 : }
2551 :
2552 297 : void CheckControllerListOrder(EnergyPlusData &state)
2553 : {
2554 :
2555 : // SUBROUTINE INFORMATION:
2556 : // AUTHOR B. Griffith
2557 : // DATE WRITTEN Oct 10.
2558 :
2559 : // PURPOSE OF THIS SUBROUTINE:
2560 : // check that if multiple controllers on an air loop, that they aren't listed in a bad order
2561 : // CR 8253
2562 :
2563 : // METHODOLOGY EMPLOYED:
2564 : // setup data for sensed nodes and compare positions if on the same branch
2565 :
2566 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
2567 297 : Array2D_int ContrlSensedNodeNums; // array for storing sense node info
2568 :
2569 326 : for (int AirSysNum = 1; AirSysNum <= state.dataHVACGlobal->NumPrimaryAirSys; ++AirSysNum) {
2570 :
2571 29 : if (state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).NumControllers > 1) {
2572 : // first see how many are water coil controllers
2573 7 : int WaterCoilContrlCount = 0; // init
2574 21 : for (int ContrlNum = 1; ContrlNum <= state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).NumControllers; ++ContrlNum) {
2575 14 : if (Util::SameString(state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).ControllerType(ContrlNum), "CONTROLLER:WATERCOIL")) {
2576 14 : ++WaterCoilContrlCount;
2577 : }
2578 : }
2579 :
2580 7 : if (WaterCoilContrlCount > 1) {
2581 7 : ContrlSensedNodeNums.allocate(3, WaterCoilContrlCount);
2582 7 : ContrlSensedNodeNums = 0;
2583 7 : int SensedNodeIndex = 0;
2584 21 : for (int ContrlNum = 1; ContrlNum <= state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).NumControllers; ++ContrlNum) {
2585 14 : if (Util::SameString(state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).ControllerType(ContrlNum), "CONTROLLER:WATERCOIL")) {
2586 14 : ++SensedNodeIndex;
2587 14 : int foundControl = Util::FindItemInList(state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).ControllerName(ContrlNum),
2588 14 : state.dataHVACControllers->ControllerProps,
2589 : &ControllerPropsType::ControllerName);
2590 14 : if (foundControl > 0) {
2591 14 : ContrlSensedNodeNums(1, SensedNodeIndex) = state.dataHVACControllers->ControllerProps(foundControl).SensedNode;
2592 : }
2593 : }
2594 : }
2595 : }
2596 :
2597 : // fill branch index for sensed nodes
2598 7 : if (allocated(ContrlSensedNodeNums)) {
2599 26 : for (int BranchNum = 1; BranchNum <= state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).NumBranches; ++BranchNum) {
2600 57 : for (int SensedNodeIndex = 1; SensedNodeIndex <= WaterCoilContrlCount; ++SensedNodeIndex) {
2601 128 : for (int BranchNodeIndex = 1;
2602 128 : BranchNodeIndex <= state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).Branch(BranchNum).TotalNodes;
2603 : ++BranchNodeIndex) {
2604 90 : if (ContrlSensedNodeNums(1, SensedNodeIndex) ==
2605 90 : state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).Branch(BranchNum).NodeNum(BranchNodeIndex)) {
2606 14 : ContrlSensedNodeNums(2, SensedNodeIndex) = BranchNodeIndex;
2607 14 : ContrlSensedNodeNums(3, SensedNodeIndex) = BranchNum;
2608 : }
2609 : }
2610 : }
2611 : }
2612 : }
2613 : // check if flow order doesn't agree with controller order
2614 7 : if (allocated(ContrlSensedNodeNums)) {
2615 21 : for (int SensedNodeIndex = 1; SensedNodeIndex <= WaterCoilContrlCount; ++SensedNodeIndex) {
2616 14 : if (SensedNodeIndex == 1) {
2617 7 : continue;
2618 : }
2619 7 : if (ContrlSensedNodeNums(2, SensedNodeIndex) < ContrlSensedNodeNums(2, SensedNodeIndex - 1)) {
2620 : // now see if on the same branch
2621 1 : if (ContrlSensedNodeNums(3, SensedNodeIndex) == ContrlSensedNodeNums(3, SensedNodeIndex - 1)) {
2622 : // we have a flow order problem with water coil controllers
2623 0 : ShowSevereError(state, "CheckControllerListOrder: A water coil controller list has the wrong order");
2624 0 : ShowContinueError(state,
2625 0 : format("Check the AirLoopHVAC:ControllerList for the air loop called \"{}\"",
2626 0 : state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).Name));
2627 0 : ShowContinueError(state,
2628 : "When there are multiple Controller:WaterCoil objects for the same air loop, they need to be "
2629 : "listed in the proper order.");
2630 0 : ShowContinueError(state,
2631 : "The controllers should be listed in natural flow order with those for upstream coils listed "
2632 : "before those for downstream coils.");
2633 0 : ShowContinueError(state, "The sensed nodes specified for the respective controllers should also reflect this order.");
2634 : }
2635 : }
2636 : }
2637 : }
2638 :
2639 7 : if (allocated(ContrlSensedNodeNums)) {
2640 7 : ContrlSensedNodeNums.deallocate();
2641 : }
2642 :
2643 : } // controllers > 1
2644 : }
2645 297 : }
2646 :
2647 899 : void CheckCoilWaterInletNode(EnergyPlusData &state,
2648 : int const WaterInletNodeNum, // input actuator node number
2649 : bool &NodeNotFound // true if matching actuator node not found
2650 : )
2651 : {
2652 :
2653 : // SUBROUTINE INFORMATION:
2654 : // AUTHOR Heejin Cho
2655 : // DATE WRITTEN November 2010
2656 :
2657 : // PURPOSE OF THIS FUNCTION:
2658 : // This subroutine checks that the water inlet node number is matched by
2659 : // the actuator node number of some water coil
2660 :
2661 899 : if (state.dataHVACControllers->GetControllerInputFlag) {
2662 22 : GetControllerInput(state);
2663 22 : state.dataHVACControllers->GetControllerInputFlag = false;
2664 : }
2665 :
2666 899 : NodeNotFound = true;
2667 8737 : for (auto const &ControllerProps : state.dataHVACControllers->ControllerProps) {
2668 7838 : if (ControllerProps.ActuatedNode == WaterInletNodeNum) {
2669 897 : NodeNotFound = false;
2670 : }
2671 : }
2672 899 : }
2673 :
2674 3356 : void GetControllerNameAndIndex(EnergyPlusData &state,
2675 : int const WaterInletNodeNum, // input actuator node number
2676 : std::string &ControllerName, // controller name used by water coil
2677 : int &ControllerIndex, // controller index used by water coil
2678 : bool &ErrorsFound // true if matching actuator node not found
2679 : )
2680 : {
2681 :
2682 : // SUBROUTINE INFORMATION:
2683 : // AUTHOR Richard Raustad
2684 : // DATE WRITTEN June 2017
2685 :
2686 : // PURPOSE OF THIS FUNCTION:
2687 : // This subroutine checks that the water inlet node number is matched by
2688 : // the actuator node number of some water coil and passed back controller name and index
2689 :
2690 3356 : if (state.dataHVACControllers->GetControllerInputFlag) {
2691 302 : GetControllerInput(state);
2692 302 : state.dataHVACControllers->GetControllerInputFlag = false;
2693 : }
2694 :
2695 3356 : ControllerName = " ";
2696 3356 : ControllerIndex = 0;
2697 25988 : for (int ControlNum = 1; ControlNum <= state.dataHVACControllers->NumControllers; ++ControlNum) {
2698 23539 : if (state.dataHVACControllers->ControllerProps(ControlNum).ActuatedNode == WaterInletNodeNum) {
2699 907 : ControllerIndex = ControlNum;
2700 907 : ControllerName = state.dataHVACControllers->ControllerProps(ControlNum).ControllerName;
2701 907 : break;
2702 : }
2703 : }
2704 :
2705 3356 : if (ControllerIndex == 0) {
2706 2449 : ErrorsFound = true;
2707 : }
2708 3356 : }
2709 :
2710 58 : void GetControllerActuatorNodeNum(EnergyPlusData &state,
2711 : std::string const &ControllerName, // name of coil controller
2712 : int &WaterInletNodeNum, // input actuator node number
2713 : bool &NodeNotFound // true if matching actuator node not found
2714 : )
2715 : {
2716 :
2717 : // SUBROUTINE INFORMATION:
2718 : // AUTHOR Richard Raustad, FSEC
2719 : // DATE WRITTEN September 2013
2720 :
2721 : // PURPOSE OF THIS FUNCTION:
2722 : // This subroutine finds the controllers actuator node number
2723 :
2724 58 : if (state.dataHVACControllers->GetControllerInputFlag) {
2725 2 : GetControllerInput(state);
2726 2 : state.dataHVACControllers->GetControllerInputFlag = false;
2727 : }
2728 :
2729 58 : NodeNotFound = true;
2730 58 : int ControlNum = Util::FindItemInList(ControllerName, state.dataHVACControllers->ControllerProps, &ControllerPropsType::ControllerName);
2731 58 : if (ControlNum > 0 && ControlNum <= state.dataHVACControllers->NumControllers) {
2732 58 : WaterInletNodeNum = state.dataHVACControllers->ControllerProps(ControlNum).ActuatedNode;
2733 58 : NodeNotFound = false;
2734 : }
2735 58 : }
2736 :
2737 900 : int GetControllerIndex(EnergyPlusData &state, std::string const &ControllerName // name of coil controller
2738 : )
2739 : {
2740 :
2741 : // SUBROUTINE INFORMATION:
2742 : // AUTHOR Richard Raustad, FSEC
2743 : // DATE WRITTEN January 2018
2744 :
2745 : // This subroutine finds the controllers actuator node number
2746 :
2747 900 : if (state.dataHVACControllers->GetControllerInputFlag) {
2748 0 : GetControllerInput(state);
2749 0 : state.dataHVACControllers->GetControllerInputFlag = false;
2750 : }
2751 :
2752 900 : int ControllerIndex = Util::FindItemInList(ControllerName, state.dataHVACControllers->ControllerProps, &ControllerPropsType::ControllerName);
2753 900 : if (ControllerIndex == 0) {
2754 0 : ShowFatalError(state,
2755 0 : format("ManageControllers: Invalid controller={}. The only valid controller type for an AirLoopHVAC is Controller:WaterCoil.",
2756 : ControllerName));
2757 : }
2758 :
2759 900 : return ControllerIndex;
2760 : }
2761 :
2762 : } // namespace EnergyPlus::HVACControllers
|