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