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