Line data Source code
1 : // EnergyPlus, Copyright (c) 1996-2024, 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 4 : std::string ControlVariableTypes(CtrlVarType const &c)
178 : {
179 4 : switch (c) {
180 0 : case CtrlVarType::NoControlVariable:
181 0 : return "No control variable";
182 4 : case CtrlVarType::Temperature:
183 4 : 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 52803819 : 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 52803819 : if (state.dataHVACControllers->GetControllerInputFlag) { // First time subroutine has been entered
226 0 : GetControllerInput(state);
227 0 : state.dataHVACControllers->GetControllerInputFlag = false;
228 : }
229 :
230 52803819 : 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 52803819 : ControlNum = ControllerIndex;
241 52803819 : 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 52803819 : if (state.dataHVACControllers->CheckEquipName(ControlNum)) {
249 866 : 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 866 : state.dataHVACControllers->CheckEquipName(ControlNum) = false;
258 : }
259 : }
260 :
261 52803819 : auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
262 :
263 52803819 : if (controllerProps.BypassControllerCalc && BypassOAController) {
264 1905678 : IsUpToDateFlag = true;
265 1905678 : IsConvergedFlag = true;
266 1905678 : if (present(AllowWarmRestartFlag)) AllowWarmRestartFlag = true;
267 2218272 : 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 50898141 : if (controllerProps.ActuatedNodePlantLoc.loopNum > 0) {
275 :
276 50897275 : if (state.dataPlnt->PlantLoop(controllerProps.ActuatedNodePlantLoc.loopNum)
277 50897275 : .LoopSide(controllerProps.ActuatedNodePlantLoc.loopSideNum)
278 50897275 : .FlowLock == DataPlant::FlowLock::Locked) {
279 : // plant is rigid so controller cannot change anything.
280 : // Update the current Controller to the outlet nodes
281 312594 : UpdateController(state, ControlNum);
282 :
283 312594 : IsConvergedFlag = true;
284 312594 : return;
285 : }
286 : }
287 :
288 : // Detect if speculative warm restart is supported by this computer
289 50585547 : 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 9746213 : if (controllerProps.ControlVar == CtrlVarType::TemperatureAndHumidityRatio) {
294 424998 : AllowWarmRestartFlag = false;
295 : } else {
296 9321215 : AllowWarmRestartFlag = true;
297 : }
298 : }
299 :
300 50585547 : if (controllerProps.InitFirstPass) {
301 : // Coil must first be sized to:
302 : // Initialize controllerProps%MinActuated and controllerProps%MaxActuated
303 866 : InitController(state, ControlNum, IsConvergedFlag);
304 866 : 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 50585547 : switch (Operation) {
313 9746213 : case DataHVACControllers::ControllerOperation::ColdStart: {
314 : // For temperature and humidity control reset humidity control override if it was set
315 9746213 : if (controllerProps.HumRatCtrlOverride) {
316 62062 : controllerProps.HumRatCtrlOverride = false;
317 : // Put the controller tolerance (offset) back to it's original value
318 62062 : RootFinder::SetupRootFinder(state,
319 62062 : 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 9746213 : ResetController(state, ControlNum, false, IsConvergedFlag);
329 : // Update the current Controller to the outlet nodes
330 9746213 : UpdateController(state, ControlNum);
331 9746213 : } break;
332 5172357 : case DataHVACControllers::ControllerOperation::WarmRestart: {
333 : // If a iControllerOpWarmRestart call, set the actuator inlet flows to previous solution
334 5172357 : ResetController(state, ControlNum, true, IsConvergedFlag);
335 : // Update the current Controller to the outlet nodes
336 5172357 : UpdateController(state, ControlNum);
337 5172357 : } break;
338 20748407 : case DataHVACControllers::ControllerOperation::Iterate: {
339 : // With the correct ControlNum Initialize all Controller related parameters
340 20748407 : InitController(state, ControlNum, IsConvergedFlag);
341 :
342 : // No initialization needed: should have been done before
343 : // Simulate the correct Controller with the current ControlNum
344 20748407 : int ControllerType = controllerProps.ControllerType_Num;
345 :
346 20748407 : if (ControllerType == ControllerSimple_Type) { // 'Controller:WaterCoil'
347 20748407 : 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 20748407 : UpdateController(state, ControlNum);
354 :
355 20748407 : CheckTempAndHumRatCtrl(state, ControlNum, IsConvergedFlag);
356 :
357 20748407 : } break;
358 14918570 : case DataHVACControllers::ControllerOperation::End: {
359 : // With the correct ControlNum Initialize all Controller related parameters
360 14918570 : 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 14918570 : int ControllerType = controllerProps.ControllerType_Num;
365 :
366 14918570 : if (ControllerType == ControllerSimple_Type) { // 'Controller:WaterCoil'
367 14918570 : CheckSimpleController(state, ControlNum, IsConvergedFlag);
368 14918570 : SaveSimpleController(state, ControlNum, FirstHVACIteration, IsConvergedFlag);
369 : } else {
370 0 : ShowFatalError(state, format("Invalid controller type in ManageControllers={}", controllerProps.ControllerType));
371 : }
372 :
373 14918570 : } 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 50585547 : 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 325 : 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 325 : 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 325 : Array1D<Real64> NumArray;
416 325 : Array1D_string AlphArray;
417 325 : Array1D_string cAlphaFields; // Alpha field names
418 325 : Array1D_string cNumericFields; // Numeric field names
419 325 : Array1D_bool lAlphaBlanks; // Logical array, alpha field input BLANK = .TRUE.
420 325 : Array1D_bool lNumericBlanks; // Logical array, numeric field input BLANK = .TRUE.
421 325 : 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 325 : std::string const CurrentModuleObject = "Controller:WaterCoil";
428 325 : int NumSimpleControllers = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, CurrentModuleObject);
429 325 : state.dataHVACControllers->NumControllers = NumSimpleControllers;
430 :
431 : // Allocate stats data structure for each air loop and controller if needed
432 325 : 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 325 : if (state.dataHVACControllers->NumControllers == 0) return;
446 : // Condition of no controllers will be taken care of elsewhere, if necessary
447 :
448 296 : state.dataHVACControllers->ControllerProps.allocate(state.dataHVACControllers->NumControllers);
449 296 : state.dataHVACControllers->RootFinders.allocate(state.dataHVACControllers->NumControllers);
450 296 : state.dataHVACControllers->CheckEquipName.dimension(state.dataHVACControllers->NumControllers, true);
451 :
452 296 : state.dataInputProcessing->inputProcessor->getObjectDefMaxArgs(state, CurrentModuleObject, NumArgs, NumAlphas, NumNums);
453 296 : AlphArray.allocate(NumAlphas);
454 296 : cAlphaFields.allocate(NumAlphas);
455 296 : cNumericFields.allocate(NumNums);
456 296 : NumArray.dimension(NumNums, 0.0);
457 296 : lAlphaBlanks.dimension(NumAlphas, true);
458 296 : lNumericBlanks.dimension(NumNums, true);
459 :
460 : // Now find and load all of the simple controllers.
461 296 : if (NumSimpleControllers > 0) {
462 : int IOStat;
463 1162 : for (int Num = 1; Num <= NumSimpleControllers; ++Num) {
464 866 : auto &controllerProps = state.dataHVACControllers->ControllerProps(Num);
465 866 : 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 866 : controllerProps.ControllerName = AlphArray(1);
479 866 : controllerProps.ControllerType = CurrentModuleObject;
480 :
481 866 : controllerProps.ControlVar = static_cast<EnergyPlus::HVACControllers::CtrlVarType>(getEnumValue(ctrlVarNamesUC, AlphArray(2)));
482 866 : 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 866 : controllerProps.Action = static_cast<ControllerAction>(getEnumValue(actionNamesUC, AlphArray(3)));
492 866 : 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 866 : if (AlphArray(4) == "FLOW") {
500 866 : 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 866 : controllerProps.SensedNode = NodeInputManager::GetOnlySingleNode(state,
507 866 : AlphArray(5),
508 : ErrorsFound,
509 : DataLoopNode::ConnectionObjectType::ControllerWaterCoil,
510 866 : AlphArray(1),
511 : DataLoopNode::NodeFluidType::Blank,
512 : DataLoopNode::ConnectionType::Sensor,
513 : NodeInputManager::CompFluidStream::Primary,
514 : DataLoopNode::ObjectIsNotParent);
515 866 : controllerProps.ActuatedNode = NodeInputManager::GetOnlySingleNode(state,
516 866 : AlphArray(6),
517 : ErrorsFound,
518 : DataLoopNode::ConnectionObjectType::ControllerWaterCoil,
519 866 : AlphArray(1),
520 : DataLoopNode::NodeFluidType::Blank,
521 : DataLoopNode::ConnectionType::Actuator,
522 : NodeInputManager::CompFluidStream::Primary,
523 : DataLoopNode::ObjectIsNotParent);
524 866 : controllerProps.Offset = NumArray(1);
525 866 : controllerProps.MaxVolFlowActuated = NumArray(2);
526 866 : controllerProps.MinVolFlowActuated = NumArray(3);
527 :
528 866 : 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 866 : if (controllerProps.SensedNode > 0) {
535 :
536 : bool NodeNotFound; // flag true if the sensor node is on the coil air outlet node
537 866 : if (controllerProps.ControlVar == HVACControllers::CtrlVarType::HumidityRatio ||
538 866 : controllerProps.ControlVar == HVACControllers::CtrlVarType::TemperatureAndHumidityRatio) {
539 45 : SetPointManager::ResetHumidityRatioCtrlVarType(state, controllerProps.SensedNode);
540 : }
541 866 : WaterCoils::CheckForSensorAndSetPointNode(state, controllerProps.SensedNode, controllerProps.ControlVar, NodeNotFound);
542 :
543 866 : 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 866 : bool EMSSetPointErrorFlag = false;
552 866 : switch (controllerProps.ControlVar) {
553 821 : case HVACControllers::CtrlVarType::Temperature: {
554 821 : EMSManager::CheckIfNodeSetPointManagedByEMS(state, controllerProps.SensedNode, HVAC::CtrlVarType::Temp, EMSSetPointErrorFlag);
555 821 : state.dataLoopNodes->NodeSetpointCheck(controllerProps.SensedNode).needsSetpointChecking = false;
556 821 : if (EMSSetPointErrorFlag) {
557 638 : if (!SetPointManager::NodeHasSPMCtrlVarType(state, controllerProps.SensedNode, HVAC::CtrlVarType::Temp)) {
558 0 : ShowContinueError(state, " ..Temperature setpoint not found on coil air outlet node.");
559 0 : ShowContinueError(
560 : state, " ..The setpoint may have been placed on a node downstream of the coil or on an airloop outlet node.");
561 0 : ShowContinueError(state, " ..Specify the setpoint and the sensor on the coil air outlet node when possible.");
562 : }
563 : }
564 821 : } break;
565 0 : case HVACControllers::CtrlVarType::HumidityRatio: {
566 0 : EMSManager::CheckIfNodeSetPointManagedByEMS(
567 : state, controllerProps.SensedNode, HVAC::CtrlVarType::MaxHumRat, EMSSetPointErrorFlag);
568 0 : state.dataLoopNodes->NodeSetpointCheck(controllerProps.SensedNode).needsSetpointChecking = false;
569 0 : if (EMSSetPointErrorFlag) {
570 0 : 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 0 : } break;
578 45 : case HVACControllers::CtrlVarType::TemperatureAndHumidityRatio: {
579 45 : EMSManager::CheckIfNodeSetPointManagedByEMS(state, controllerProps.SensedNode, HVAC::CtrlVarType::Temp, EMSSetPointErrorFlag);
580 45 : state.dataLoopNodes->NodeSetpointCheck(controllerProps.SensedNode).needsSetpointChecking = false;
581 45 : if (EMSSetPointErrorFlag) {
582 30 : if (!SetPointManager::NodeHasSPMCtrlVarType(state, controllerProps.SensedNode, HVAC::CtrlVarType::Temp)) {
583 0 : ShowContinueError(state, " ..Temperature setpoint not found on coil air outlet node.");
584 0 : ShowContinueError(
585 : state, " ..The setpoint may have been placed on a node downstream of the coil or on an airloop outlet node.");
586 0 : ShowContinueError(state, " ..Specify the setpoint and the sensor on the coil air outlet node when possible.");
587 : }
588 : }
589 45 : EMSSetPointErrorFlag = false;
590 45 : EMSManager::CheckIfNodeSetPointManagedByEMS(
591 : state, controllerProps.SensedNode, HVAC::CtrlVarType::MaxHumRat, EMSSetPointErrorFlag);
592 45 : state.dataLoopNodes->NodeSetpointCheck(controllerProps.SensedNode).needsSetpointChecking = false;
593 45 : if (EMSSetPointErrorFlag) {
594 30 : if (!SetPointManager::NodeHasSPMCtrlVarType(state, controllerProps.SensedNode, HVAC::CtrlVarType::MaxHumRat)) {
595 0 : ShowContinueError(state, " ..Humidity ratio setpoint not found on coil air outlet node.");
596 0 : ShowContinueError(
597 : state, " ..The setpoint may have been placed on a node downstream of the coil or on an airloop outlet node.");
598 0 : ShowContinueError(state, " ..Specify the setpoint and the sensor on the coil air outlet node when possible.");
599 : }
600 : }
601 45 : } 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 1162 : for (int Num = 1; Num <= NumSimpleControllers; ++Num) {
612 866 : auto &controllerProps = state.dataHVACControllers->ControllerProps(Num);
613 866 : WaterCoils::CheckActuatorNode(state, controllerProps.ActuatedNode, controllerProps.WaterCoilType, ActuatorNodeNotFound);
614 866 : 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 866 : if (controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterCooling ||
620 557 : controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterDetailedFlatCooling) {
621 459 : if (controllerProps.Action == ControllerAction::NoAction) {
622 0 : controllerProps.Action = ControllerAction::Reverse;
623 459 : } 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 407 : } else if (controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterSimpleHeating) {
630 407 : if (controllerProps.Action == ControllerAction::NoAction) {
631 0 : controllerProps.Action = ControllerAction::NormalAction;
632 407 : } 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 296 : AlphArray.deallocate();
643 296 : cAlphaFields.deallocate();
644 296 : cNumericFields.deallocate();
645 296 : NumArray.deallocate();
646 296 : lAlphaBlanks.deallocate();
647 296 : lNumericBlanks.deallocate();
648 :
649 : // CR 8253 check that the sensed nodes in the controllers are in flow order in controller List
650 296 : CheckControllerListOrder(state);
651 :
652 296 : if (ErrorsFound) {
653 0 : ShowFatalError(state, format("{}Errors found in getting {} input.", RoutineName, CurrentModuleObject));
654 : }
655 499 : }
656 :
657 : // End of Get Input subroutines for the Module
658 : //******************************************************************************
659 :
660 : // Beginning Initialization Section of the Module
661 : //******************************************************************************
662 :
663 14980648 : 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 14980648 : auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
679 14980648 : auto &rootFinders = state.dataHVACControllers->RootFinders(ControlNum);
680 :
681 14980648 : Real64 NoFlowResetValue = 0.0;
682 14980648 : PlantUtilities::SetActuatedBranchFlowRate(state, NoFlowResetValue, controllerProps.ActuatedNode, controllerProps.ActuatedNodePlantLoc, true);
683 :
684 : // ENDIF
685 :
686 : // Reset iteration counter and internal variables
687 14980648 : controllerProps.NumCalcCalls = 0;
688 14980648 : controllerProps.DeltaSensed = 0.0;
689 14980648 : controllerProps.SensedValue = 0.0;
690 14980648 : controllerProps.ActuatedValue = 0.0;
691 :
692 : // Reset setpoint-related quantities
693 14980648 : controllerProps.SetPointValue = 0.0;
694 14980648 : controllerProps.IsSetPointDefinedFlag = false;
695 :
696 : // MinAvailActuated and MaxAvailActuated set in InitController()
697 14980648 : controllerProps.MinAvailActuated = 0.0;
698 14980648 : controllerProps.MinAvailSensed = 0.0;
699 14980648 : controllerProps.MaxAvailActuated = 0.0;
700 14980648 : 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 14980648 : if (DoWarmRestartFlag) {
705 5172357 : controllerProps.DoWarmRestartFlag = true;
706 : } else {
707 9808291 : controllerProps.DoWarmRestartFlag = false;
708 : // If no speculative warm restart then reset stored mode and actucated value
709 9808291 : controllerProps.Mode = ControllerMode::None;
710 9808291 : 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 14980648 : controllerProps.ReusePreviousSolutionFlag = true;
718 : // Always reset to false by default. Set in CalcSimpleController() on the first controller iteration.
719 14980648 : controllerProps.ReuseIntermediateSolutionFlag = false;
720 : // By default not converged
721 14980648 : IsConvergedFlag = false;
722 :
723 : // Reset root finder
724 : // This is independent of the processing in InitializeRootFinder() performed in Calc() routine.
725 14980648 : rootFinders.StatusFlag = DataRootFinder::RootFinderStatus::None;
726 14980648 : rootFinders.CurrentMethodType = DataRootFinder::RootFinderMethod::None;
727 :
728 14980648 : rootFinders.CurrentPoint.DefinedFlag = false;
729 14980648 : rootFinders.CurrentPoint.X = 0.0;
730 14980648 : rootFinders.CurrentPoint.Y = 0.0;
731 :
732 14980648 : rootFinders.MinPoint.DefinedFlag = false;
733 14980648 : rootFinders.MaxPoint.DefinedFlag = false;
734 14980648 : rootFinders.LowerPoint.DefinedFlag = false;
735 14980648 : rootFinders.UpperPoint.DefinedFlag = false;
736 14980648 : }
737 :
738 35667843 : 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 35667843 : auto &thisController = state.dataHVACControllers->ControllerProps(ControlNum);
757 35667843 : Array1D_bool &MyEnvrnFlag(state.dataHVACControllers->MyEnvrnFlag);
758 35667843 : Array1D_bool &MySizeFlag(state.dataHVACControllers->MySizeFlag);
759 35667843 : Array1D_bool &MyPlantIndexsFlag(state.dataHVACControllers->MyPlantIndexsFlag);
760 :
761 35667843 : if (state.dataHVACControllers->InitControllerOneTimeFlag) {
762 :
763 296 : MyEnvrnFlag.allocate(state.dataHVACControllers->NumControllers);
764 296 : MySizeFlag.allocate(state.dataHVACControllers->NumControllers);
765 296 : MyPlantIndexsFlag.allocate(state.dataHVACControllers->NumControllers);
766 296 : MyEnvrnFlag = true;
767 296 : MySizeFlag = true;
768 296 : MyPlantIndexsFlag = true;
769 296 : state.dataHVACControllers->InitControllerOneTimeFlag = false;
770 : }
771 :
772 35667843 : if (!state.dataGlobal->SysSizingCalc && state.dataHVACControllers->InitControllerSetPointCheckFlag && state.dataHVACGlobal->DoSetPointTest) {
773 : // check for missing setpoints
774 1162 : for (int ControllerIndex = 1; ControllerIndex <= state.dataHVACControllers->NumControllers; ++ControllerIndex) {
775 866 : auto &controllerProps = state.dataHVACControllers->ControllerProps(ControllerIndex);
776 866 : int SensedNode = controllerProps.SensedNode;
777 866 : switch (controllerProps.ControlVar) {
778 821 : case HVACControllers::CtrlVarType::Temperature: { // 'Temperature'
779 821 : if (state.dataLoopNodes->Node(SensedNode).TempSetPoint == DataLoopNode::SensedNodeFlagValue) {
780 32 : 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 32 : EMSManager::CheckIfNodeSetPointManagedByEMS(
793 32 : state, SensedNode, HVAC::CtrlVarType::Temp, state.dataHVACGlobal->SetPointErrorFlag);
794 32 : 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 789 : 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 821 : } 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 45 : case HVACControllers::CtrlVarType::TemperatureAndHumidityRatio: { // 'TemperatureAndHumidityRatio'
868 45 : 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 45 : 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 45 : } 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 296 : state.dataHVACControllers->InitControllerSetPointCheckFlag = false;
961 : }
962 :
963 35667843 : if (allocated(state.dataPlnt->PlantLoop) && MyPlantIndexsFlag(ControlNum)) {
964 1732 : PlantUtilities::ScanPlantLoopsForNodeNum(
965 866 : state, thisController.ControllerName, thisController.ActuatedNode, thisController.ActuatedNodePlantLoc);
966 866 : MyPlantIndexsFlag(ControlNum) = false;
967 : }
968 :
969 35667843 : if (!state.dataGlobal->SysSizingCalc && MySizeFlag(ControlNum)) {
970 :
971 866 : SizeController(state, ControlNum);
972 :
973 : // Check to make sure that the Minimum Flow rate is less than the max.
974 866 : if (thisController.MaxVolFlowActuated == 0.0) {
975 66 : ShowWarningError(state,
976 66 : format("{}: Controller:WaterCoil=\"{}\", Maximum Actuated Flow is zero.", RoutineName, thisController.ControllerName));
977 33 : thisController.MinVolFlowActuated = 0.0;
978 833 : } 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 866 : switch (thisController.Action) {
987 407 : case ControllerAction::NormalAction: {
988 407 : RootFinder::SetupRootFinder(state,
989 407 : 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 407 : } break;
999 459 : case ControllerAction::Reverse: {
1000 459 : RootFinder::SetupRootFinder(state,
1001 459 : 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 459 : } 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 866 : MySizeFlag(ControlNum) = false;
1016 : }
1017 :
1018 : // Set the sensed and actuated node numbers
1019 35667843 : int ActuatedNode = thisController.ActuatedNode;
1020 35667843 : int SensedNode = thisController.SensedNode;
1021 :
1022 : // Do the Begin Environment initializations
1023 35667843 : if (state.dataGlobal->BeginEnvrnFlag && MyEnvrnFlag(ControlNum)) {
1024 :
1025 5860 : Real64 rho = FluidProperties::GetDensityGlycol(state,
1026 5860 : state.dataPlnt->PlantLoop(thisController.ActuatedNodePlantLoc.loopNum).FluidName,
1027 : Constant::CWInitConvTemp,
1028 5860 : state.dataPlnt->PlantLoop(thisController.ActuatedNodePlantLoc.loopNum).FluidIndex,
1029 : RoutineName);
1030 :
1031 5860 : thisController.MinActuated = rho * thisController.MinVolFlowActuated;
1032 5860 : thisController.MaxActuated = rho * thisController.MaxVolFlowActuated;
1033 :
1034 : // Turn off scheme to reuse previous solution obtained at last SimAirLoop() call
1035 5860 : thisController.ReusePreviousSolutionFlag = false;
1036 : // Reset solution trackers
1037 17580 : for (auto &e : thisController.SolutionTrackers) {
1038 11720 : e.DefinedFlag = false;
1039 11720 : e.Mode = ControllerMode::None;
1040 11720 : e.ActuatedValue = 0.0;
1041 : }
1042 :
1043 5860 : MyEnvrnFlag(ControlNum) = false;
1044 : }
1045 :
1046 35667843 : if (!state.dataGlobal->BeginEnvrnFlag) {
1047 35566595 : MyEnvrnFlag(ControlNum) = true;
1048 : }
1049 :
1050 35667843 : PlantUtilities::SetActuatedBranchFlowRate(state, thisController.NextActuatedValue, ActuatedNode, thisController.ActuatedNodePlantLoc, false);
1051 :
1052 : // Do the following initializations (every time step): This should be the info from
1053 : // the previous components outlets or the node data in this section.
1054 : // Load the node data in this section for the component simulation
1055 35667843 : IsConvergedFlag = false;
1056 :
1057 35667843 : switch (thisController.ControlVar) {
1058 33909667 : case HVACControllers::CtrlVarType::Temperature: { // 'Temperature'
1059 33909667 : thisController.SensedValue = state.dataLoopNodes->Node(SensedNode).Temp;
1060 : // Done once per HVAC step
1061 33909667 : if (!thisController.IsSetPointDefinedFlag) {
1062 14089409 : thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).TempSetPoint;
1063 14089409 : thisController.IsSetPointDefinedFlag = true;
1064 :
1065 : // If there is a fault of water coil SAT sensor
1066 14090292 : if (thisController.FaultyCoilSATFlag && (!state.dataGlobal->WarmupFlag) && (!state.dataGlobal->DoingSizing) &&
1067 883 : (!state.dataGlobal->KickOffSimulation)) {
1068 : // calculate the sensor offset using fault information
1069 883 : int FaultIndex = thisController.FaultyCoilSATIndex;
1070 883 : thisController.FaultyCoilSATOffset = state.dataFaultsMgr->FaultsCoilSATSensor(FaultIndex).CalFaultOffsetAct(state);
1071 : // update the SetPointValue
1072 883 : thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).TempSetPoint - thisController.FaultyCoilSATOffset;
1073 : }
1074 : }
1075 33909667 : } break;
1076 1758176 : case HVACControllers::CtrlVarType::TemperatureAndHumidityRatio: { // 'TemperatureAndHumidityRatio'
1077 1758176 : if (thisController.HumRatCtrlOverride) {
1078 : // Humidity ratio control
1079 255492 : thisController.SensedValue = state.dataLoopNodes->Node(SensedNode).HumRat;
1080 : } else {
1081 : // Temperature control
1082 1502684 : thisController.SensedValue = state.dataLoopNodes->Node(SensedNode).Temp;
1083 : }
1084 1758176 : if (!thisController.IsSetPointDefinedFlag) {
1085 487121 : if (thisController.HumRatCtrlOverride) {
1086 : // Humidity ratio control
1087 62078 : thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).HumRatMax;
1088 : } else {
1089 : // Pure temperature setpoint control strategy
1090 425043 : thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).TempSetPoint;
1091 : }
1092 : // Finally indicate thate the setpoint has been computed
1093 487121 : thisController.IsSetPointDefinedFlag = true;
1094 : }
1095 1758176 : } break;
1096 0 : case HVACControllers::CtrlVarType::HumidityRatio: { // 'HumidityRatio'
1097 0 : thisController.SensedValue = state.dataLoopNodes->Node(SensedNode).HumRat;
1098 : // Done once per HVAC step
1099 0 : if (!thisController.IsSetPointDefinedFlag) {
1100 0 : switch (thisController.HumRatCntrlType) {
1101 0 : case HVAC::CtrlVarType::MaxHumRat: {
1102 0 : thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).HumRatMax;
1103 0 : } break;
1104 0 : default: {
1105 0 : thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).HumRatSetPoint;
1106 0 : } break;
1107 : }
1108 0 : thisController.IsSetPointDefinedFlag = true;
1109 : }
1110 0 : } break;
1111 0 : case HVACControllers::CtrlVarType::Flow: { // 'Flow'
1112 0 : thisController.SensedValue = state.dataLoopNodes->Node(SensedNode).MassFlowRate;
1113 : // Done once per HVAC step
1114 0 : if (!thisController.IsSetPointDefinedFlag) {
1115 0 : thisController.SetPointValue = state.dataLoopNodes->Node(SensedNode).MassFlowRateSetPoint;
1116 0 : thisController.IsSetPointDefinedFlag = true;
1117 : }
1118 0 : } break;
1119 0 : default: {
1120 0 : ShowFatalError(state, format("Invalid Controller Variable Type={}", ControlVariableTypes(thisController.ControlVar)));
1121 0 : } break;
1122 : }
1123 :
1124 35667843 : switch (thisController.ActuatorVar) {
1125 35667843 : case HVACControllers::CtrlVarType::Flow: { // 'Flow'
1126 : // At the beginning of every time step the value is reset to the User Input
1127 : // The interface managers can reset the Max or Min to available values during the time step
1128 : // and these will then be the new setpoint limits for the controller to work within.
1129 35667843 : thisController.ActuatedValue = state.dataLoopNodes->Node(ActuatedNode).MassFlowRate;
1130 : // Compute the currently available min and max bounds for controller.
1131 : // Done only once per HVAC step, as it would not make any sense to modify the min/max
1132 : // bounds during successive iterations of the root finder.
1133 35667843 : if (thisController.NumCalcCalls == 0) {
1134 14576530 : thisController.MinAvailActuated = max(state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMinAvail, thisController.MinActuated);
1135 14576530 : thisController.MaxAvailActuated = min(state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMaxAvail, thisController.MaxActuated);
1136 : // MinActuated is user input for minimum actuated flow, use that value if allowed
1137 : // (i.e., reset MinAvailActuated based on Node%MassFlowRateMaxAvail)
1138 14576530 : thisController.MinAvailActuated = min(thisController.MinAvailActuated, thisController.MaxAvailActuated);
1139 : }
1140 35667843 : } break;
1141 0 : default: {
1142 0 : ShowFatalError(state, format("Invalid Actuator Variable Type={}", ControlVariableTypes(thisController.ActuatorVar)));
1143 0 : } break;
1144 : }
1145 :
1146 : // Compute residual for control function using desired setpoint value and current sensed value
1147 : // NOTE: The delta sensed value might be wrong if the setpoint has not yet been computed.
1148 : // Make sure not to use it until the setpoint has been computed.
1149 35667843 : if (thisController.IsSetPointDefinedFlag) {
1150 35667843 : thisController.DeltaSensed = thisController.SensedValue - thisController.SetPointValue;
1151 : } else {
1152 0 : thisController.DeltaSensed = 0.0;
1153 : }
1154 35667843 : }
1155 :
1156 866 : void SizeController(EnergyPlusData &state, int const ControlNum)
1157 : {
1158 :
1159 : // SUBROUTINE INFORMATION:
1160 : // AUTHOR Fred Buhl
1161 : // DATE WRITTEN November 2001
1162 :
1163 : // PURPOSE OF THIS SUBROUTINE:
1164 : // This subroutine is for sizing Controller Components for which max flow rates have not been
1165 : // specified in the input.
1166 :
1167 : // METHODOLOGY EMPLOYED:
1168 : // Obtains flow rates from the actuated node. Should have been set by the water coils.
1169 :
1170 866 : auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
1171 :
1172 866 : if (controllerProps.MaxVolFlowActuated == DataSizing::AutoSize) {
1173 35163 : for (int WaterCompNum = 1; WaterCompNum <= state.dataSize->SaveNumPlantComps; ++WaterCompNum) {
1174 34411 : if (state.dataSize->CompDesWaterFlow(WaterCompNum).SupNode == controllerProps.ActuatedNode) {
1175 719 : controllerProps.MaxVolFlowActuated = state.dataSize->CompDesWaterFlow(WaterCompNum).DesVolFlowRate;
1176 : }
1177 : }
1178 :
1179 752 : if (controllerProps.MaxVolFlowActuated < HVAC::SmallWaterVolFlow) {
1180 33 : controllerProps.MaxVolFlowActuated = 0.0;
1181 : }
1182 752 : BaseSizer::reportSizerOutput(state,
1183 : controllerProps.ControllerType,
1184 : controllerProps.ControllerName,
1185 : "Maximum Actuated Flow [m3/s]",
1186 : controllerProps.MaxVolFlowActuated);
1187 : }
1188 :
1189 866 : if (controllerProps.Offset == DataSizing::AutoSize) {
1190 : // 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,
1191 : // 4.2 is the ratio of water density to air density, 1000/1.2 is the ratio of water specific heat to
1192 : // air specific heat, and 1.2 converts the result from air volumetric flow rate to air mass flow rate.
1193 : // The assumption is that a temperature tolerance of 0.001 C is good for an air mass flow rate of 1 kg/s.
1194 : // So we divide .001 by the air mass flow rate estimated from the water volumetric flow rate to come up
1195 : // with a temperature tolerance that won't exceed the loop energy error tolerance (10 W).
1196 : // Finally we need to take into account the fact that somebody might change the energy tolerance.
1197 273 : controllerProps.Offset =
1198 273 : (0.001 / (2100.0 * max(controllerProps.MaxVolFlowActuated, HVAC::SmallWaterVolFlow))) * (DataConvergParams::HVACEnergyToler / 10.0);
1199 : // do not let the controller tolerance exceed 1/10 of the loop temperature tolerance.
1200 273 : controllerProps.Offset = min(0.1 * DataConvergParams::HVACTemperatureToler, controllerProps.Offset);
1201 273 : BaseSizer::reportSizerOutput(
1202 : state, controllerProps.ControllerType, controllerProps.ControllerName, "Controller Convergence Tolerance", controllerProps.Offset);
1203 : }
1204 866 : }
1205 :
1206 20748407 : void CalcSimpleController(EnergyPlusData &state,
1207 : int const ControlNum,
1208 : bool const FirstHVACIteration,
1209 : bool &IsConvergedFlag,
1210 : bool &IsUpToDateFlag,
1211 : std::string const &ControllerName // used when errors occur
1212 : )
1213 : {
1214 :
1215 : // SUBROUTINE INFORMATION:
1216 : // AUTHOR Dimitri Curtil
1217 : // DATE WRITTEN May 2006
1218 : // MODIFIED Dimitri Curtil (LBNL), May 2006
1219 : // - Added IsPointFlagDefinedFlag to control when the setpoiont should be
1220 : // computed depending on the control strategy. This was needed to
1221 : // trigger the setpoint calculation for the dual temperature and
1222 : // humidity ratio control strategy only once the air loop has been
1223 : // evaluated with the max actuated flow.
1224 : // See the routine InitController() for more details on the setpoint
1225 : // calculation.
1226 : // MODIFIED Dimitri Curtil (LBNL), March 2006
1227 : // - Added IsUpToDateFlag to detect whether or not the air loop
1228 : // has been evaluated prior the first iteration, which allows
1229 : // to use the current node values as the first iterate for the root
1230 : // finder (for COLD RESTART ONLY).
1231 : // MODIFIED Dimitri Curtil (LBNL), Feb 2006
1232 : // - Added mode detection capability.
1233 : // - Now trying min actuated variable first to
1234 : // detect min-constrained cases in 1 iteration.
1235 : // - Trying max actuated variable second.
1236 : // Checks for max-constrained here instead of in
1237 : // NormActuatedCalc mode.
1238 : // - Checking for inactive mode as soon as min and max
1239 : // support points are known instead of in NormActuatedCalc
1240 : // mode.
1241 :
1242 : // SUBROUTINE ARGUMENT DEFINITIONS:
1243 : // Set to TRUE if current controller is converged; FALSE if more iteration are needed.
1244 : // Note that an error in the root finding process can be mapped onto IsConvergedFlag=TRUE
1245 : // to avoid continue iterating.
1246 : // TRUE if air loop is up-to-date meaning that the current node values are consistent (air loop evaluated)
1247 : // Only used within the Calc routines
1248 :
1249 20748407 : auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
1250 20748407 : auto &rootFinders = state.dataHVACControllers->RootFinders(ControlNum);
1251 :
1252 : // Increment counter
1253 20748407 : ++controllerProps.NumCalcCalls;
1254 :
1255 : // Check to see if the component is running; if not converged and return. This check will be done
1256 : // by looking at the component mass flow rate at the sensed node.
1257 20748407 : if (state.dataLoopNodes->Node(controllerProps.SensedNode).MassFlowRate == 0.0) {
1258 885993 : ExitCalcController(state, ControlNum, DataPrecisionGlobals::constant_zero, ControllerMode::Off, IsConvergedFlag, IsUpToDateFlag);
1259 885993 : return;
1260 : }
1261 :
1262 : // Initialize root finder
1263 19862414 : if (controllerProps.NumCalcCalls == 1) {
1264 : // Set min/max boundaries for root finder on first iteration
1265 8922298 : RootFinder::InitializeRootFinder(state, rootFinders, controllerProps.MinAvailActuated,
1266 : controllerProps.MaxAvailActuated); // XMin | XMax
1267 :
1268 : // Only allow to reuse initial evaluation if the air loop is up-to-date.
1269 : // Set in SolveAirLoopControllers()
1270 : // Only reuse initial evaluation if setpoint is already available for the current controller
1271 : // Note that in the case of dual temperature and humidity ratio control strategy since the
1272 : // setpoint at a later iteration, the initial solution cannot be reused.
1273 : // Make sure that the initial candidate value lies within range
1274 17727555 : controllerProps.ReuseIntermediateSolutionFlag = IsUpToDateFlag && controllerProps.IsSetPointDefinedFlag &&
1275 8805257 : RootFinder::CheckRootFinderCandidate(rootFinders, controllerProps.ActuatedValue);
1276 :
1277 8922298 : if (controllerProps.ReuseIntermediateSolutionFlag) {
1278 :
1279 : // Reuse intermediate solution obtained with previous controller for the current HVAC step
1280 : // and fire root finder to get next root candidate
1281 8805257 : FindRootSimpleController(state, ControlNum, FirstHVACIteration, IsConvergedFlag, IsUpToDateFlag, ControllerName);
1282 :
1283 : } else {
1284 : // Always start with min point by default
1285 117041 : controllerProps.NextActuatedValue = rootFinders.MinPoint.X;
1286 : }
1287 :
1288 : // Process current iterate and compute next candidate if needed
1289 : // We assume that after the first controller iteration:
1290 : // - the setpoint is defined
1291 : // - the min and max available bounds are defined
1292 : // NOTE: Not explicitly checked but the air mass flow rate must remain constant across successive
1293 : // controller iterations to ensure that the root finder converges.
1294 : } else {
1295 : // Check that the setpoint is defined
1296 10940116 : if (!controllerProps.IsSetPointDefinedFlag) {
1297 0 : ShowSevereError(state, format("CalcSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
1298 0 : ShowContinueError(state, format(" Controller name=\"{}\"", ControllerName));
1299 0 : ShowContinueError(state, " Setpoint is not available/defined.");
1300 0 : ShowFatalError(state, "Preceding error causes program termination.");
1301 : }
1302 : // Monitor invariants across successive controller iterations
1303 : // - min bound
1304 : // - max bound
1305 10940116 : if (rootFinders.MinPoint.X != controllerProps.MinAvailActuated) {
1306 0 : ShowSevereError(state, format("CalcSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
1307 0 : ShowContinueError(state, format(" Controller name=\"{}\"", ControllerName));
1308 0 : ShowContinueError(state, " Minimum bound must remain invariant during successive iterations.");
1309 0 : ShowContinueError(state, format(" Minimum root finder point={:.{}T}", rootFinders.MinPoint.X, NumSigDigits));
1310 0 : ShowContinueError(state, format(" Minimum avail actuated={:.{}T}", controllerProps.MinAvailActuated, NumSigDigits));
1311 0 : ShowFatalError(state, "Preceding error causes program termination.");
1312 : }
1313 10940116 : if (rootFinders.MaxPoint.X != controllerProps.MaxAvailActuated) {
1314 0 : ShowSevereError(state, format("CalcSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
1315 0 : ShowContinueError(state, format(" Controller name=\"{}\"", ControllerName));
1316 0 : ShowContinueError(state, " Maximum bound must remain invariant during successive iterations.");
1317 0 : ShowContinueError(state, format(" Maximum root finder point={:.{}T}", rootFinders.MaxPoint.X, NumSigDigits));
1318 0 : ShowContinueError(state, format(" Maximum avail actuated={:.{}T}", controllerProps.MaxAvailActuated, NumSigDigits));
1319 0 : ShowFatalError(state, "Preceding error causes program termination.");
1320 : }
1321 :
1322 : // Updates root finder with current iterate and computes next one if needed
1323 10940116 : FindRootSimpleController(state, ControlNum, FirstHVACIteration, IsConvergedFlag, IsUpToDateFlag, ControllerName);
1324 : }
1325 : }
1326 :
1327 19745373 : void FindRootSimpleController(EnergyPlusData &state,
1328 : int const ControlNum,
1329 : bool const FirstHVACIteration,
1330 : bool &IsConvergedFlag,
1331 : bool &IsUpToDateFlag,
1332 : std::string const &ControllerName // used when errors occur
1333 : )
1334 : {
1335 :
1336 : // SUBROUTINE INFORMATION:
1337 : // AUTHOR Dimitri Curtil (LBNL)
1338 : // DATE WRITTEN March 2006
1339 :
1340 : // PURPOSE OF THIS SUBROUTINE:
1341 : // New routine to fire the root finder using the current actuated and sensed values.
1342 : // - Updates IsConvergedFlag depending ou iteration status.
1343 : // - Sets next actuated value to try in ControllerProps(ControlNum)%NextActuatedValue
1344 :
1345 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1346 : bool IsDoneFlag; // TRUE if root finder needs to continue iterating, FALSE otherwise.
1347 : ControllerMode PreviousSolutionMode;
1348 : Real64 PreviousSolutionValue;
1349 :
1350 19745373 : auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
1351 19745373 : auto &rootFinders = state.dataHVACControllers->RootFinders(ControlNum);
1352 :
1353 : // Update root finder with latest solution point
1354 : // Check for unconstrained/constrained convergence
1355 : // Compute next candidate if not converged yet.
1356 19745373 : RootFinder::IterateRootFinder(state,
1357 : rootFinders,
1358 : controllerProps.ActuatedValue,
1359 : controllerProps.DeltaSensed,
1360 : IsDoneFlag); // root finder's data | X | Y | not used
1361 :
1362 : // Process root finder if converged or error
1363 : // Map root finder status onto controller mode
1364 19745373 : switch (rootFinders.StatusFlag) {
1365 10823090 : case DataRootFinder::RootFinderStatus::None:
1366 : case DataRootFinder::RootFinderStatus::WarningNonMonotonic:
1367 : case DataRootFinder::RootFinderStatus::WarningSingular: {
1368 : // We need to keep iterating...
1369 : int PreviousSolutionIndex;
1370 10823090 : IsConvergedFlag = false;
1371 :
1372 10823090 : if (FirstHVACIteration) {
1373 4047016 : PreviousSolutionIndex = 1;
1374 : } else {
1375 6776074 : PreviousSolutionIndex = 2;
1376 : }
1377 :
1378 10823090 : bool PreviousSolutionDefinedFlag = controllerProps.SolutionTrackers(PreviousSolutionIndex).DefinedFlag;
1379 10823090 : PreviousSolutionMode = controllerProps.SolutionTrackers(PreviousSolutionIndex).Mode;
1380 10823090 : PreviousSolutionValue = controllerProps.SolutionTrackers(PreviousSolutionIndex).ActuatedValue;
1381 :
1382 : // Attempt to use root at previous HVAC step in place of the candidate produced by the
1383 : // root finder.
1384 : // Set in InitController() depending on controller mode at previous HVAC step iteration
1385 : // Only attempted during bracketing phase of root finder.
1386 : // Check that a previous solution is available
1387 : // Make sure that mode of previous solution was active
1388 : // Make sure that proposed candidate does not conflict with current min/max range and lower/upper brackets
1389 15577534 : bool ReusePreviousSolutionFlag = controllerProps.ReusePreviousSolutionFlag &&
1390 4754444 : (rootFinders.CurrentMethodType == DataRootFinder::RootFinderMethod::Bracket) &&
1391 18626566 : PreviousSolutionDefinedFlag && (PreviousSolutionMode == ControllerMode::Active) &&
1392 3049032 : RootFinder::CheckRootFinderCandidate(rootFinders, PreviousSolutionValue);
1393 :
1394 10823090 : if (ReusePreviousSolutionFlag) {
1395 : // Try to reuse saved solution from previous call to SolveAirLoopControllers()
1396 : // instead of candidate proposed by the root finder
1397 3049032 : controllerProps.NextActuatedValue = PreviousSolutionValue;
1398 :
1399 : // Turn off flag since we can only use the previous solution once per HVAC iteration
1400 3049032 : controllerProps.ReusePreviousSolutionFlag = false;
1401 : } else {
1402 : // By default, use candidate value computed by root finder
1403 7774058 : controllerProps.NextActuatedValue = rootFinders.XCandidate;
1404 : }
1405 :
1406 10823090 : } break;
1407 3985360 : case DataRootFinder::RootFinderStatus::OK:
1408 : case DataRootFinder::RootFinderStatus::OKRoundOff: {
1409 : // Indicate convergence with base value (used to obtain DeltaSensed!)
1410 3985360 : ExitCalcController(state, ControlNum, rootFinders.XCandidate, ControllerMode::Active, IsConvergedFlag, IsUpToDateFlag);
1411 3985360 : } break;
1412 4062331 : case DataRootFinder::RootFinderStatus::OKMin: {
1413 : // Indicate convergence with min value
1414 : // Should be the same as ControllerProps(ControlNum)%MinAvailActuated
1415 4062331 : ExitCalcController(state, ControlNum, rootFinders.MinPoint.X, ControllerMode::MinActive, IsConvergedFlag, IsUpToDateFlag);
1416 4062331 : } break;
1417 481208 : case DataRootFinder::RootFinderStatus::OKMax: {
1418 : // Indicate convergence with max value
1419 : // Should be the same as ControllerProps(ControlNum)%MaxAvailActuated
1420 481208 : ExitCalcController(state, ControlNum, rootFinders.MaxPoint.X, ControllerMode::MaxActive, IsConvergedFlag, IsUpToDateFlag);
1421 :
1422 481208 : } break;
1423 392731 : case DataRootFinder::RootFinderStatus::ErrorSingular: {
1424 : // Indicate inactive mode with min actuated value
1425 : // NOTE: Original code returned Node(ActuatedNode)%MassFlowRateMinAvail
1426 : // This was not portable in case the actuated variable was NOT a mass flow rate!
1427 : // Replaced Node(ActuatedNode)%MassFlowRateMinAvail
1428 : // with rootFinders%MinPoint%X
1429 : // which is the same as (see SUBROUTINE InitController)
1430 : // ControllerProps(ControlNum)%MinAvailActuated
1431 392731 : ExitCalcController(state, ControlNum, rootFinders.MinPoint.X, ControllerMode::Inactive, IsConvergedFlag, IsUpToDateFlag);
1432 :
1433 : // Abnormal case: should never happen
1434 392731 : } break;
1435 0 : case DataRootFinder::RootFinderStatus::ErrorRange: {
1436 0 : ShowSevereError(state, format("FindRootSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
1437 0 : ShowContinueError(state, format(" Controller name=\"{}\"", ControllerName));
1438 0 : ShowContinueError(state,
1439 0 : format(" Root candidate x={:.{}T} does not lie within the min/max bounds.", controllerProps.ActuatedValue, NumSigDigits));
1440 0 : ShowContinueError(state, format(" Min bound is x={:.{}T}", rootFinders.MinPoint.X, NumSigDigits));
1441 0 : ShowContinueError(state, format(" Max bound is x={:.{}T}", rootFinders.MaxPoint.X, NumSigDigits));
1442 0 : ShowFatalError(state, "Preceding error causes program termination.");
1443 :
1444 : // Abnormal case: should never happen
1445 0 : } break;
1446 0 : case DataRootFinder::RootFinderStatus::ErrorBracket: {
1447 0 : ShowSevereError(state, format("FindRootSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
1448 0 : ShowContinueError(state, format(" Controller name={}", controllerProps.ControllerName));
1449 0 : ShowContinueError(state, fmt::format(" Controller action={}", state.dataHVACCtrl->ActionTypes[static_cast<int>(controllerProps.Action)]));
1450 0 : ShowContinueError(
1451 0 : state, format(" Root candidate x={:.{}T} does not lie within the lower/upper brackets.", controllerProps.ActuatedValue, NumSigDigits));
1452 0 : if (rootFinders.LowerPoint.DefinedFlag) {
1453 0 : ShowContinueError(state, format(" Lower bracket is x={:.{}T}", rootFinders.LowerPoint.X, NumSigDigits));
1454 : }
1455 0 : if (rootFinders.UpperPoint.DefinedFlag) {
1456 0 : ShowContinueError(state, format(" Upper bracket is x={:.{}T}", rootFinders.UpperPoint.X, NumSigDigits));
1457 : }
1458 0 : ShowFatalError(state, "Preceding error causes program termination.");
1459 :
1460 : // Detected control function with wrong action between the min and max points.
1461 : // Should never happen: probably indicative of some serious problems in IDFs
1462 : // NOTE: This approach is more robust and consistent than what was done in version 1.3.
1463 : // Indeed, such a function with the wrong action characteristic would have silently returned
1464 : // either of the following values depending on the specified action:
1465 : // - NORMAL 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 : // - REVERSE ACTION:
1469 : // - If y(xMin) < ySetPoint && y(xMax) > y(xMin), then x = xMin
1470 : // - If y(xMin) > ySetPoint && y(xMax) > y(xMin), then x = xMax
1471 0 : } break;
1472 653 : case DataRootFinder::RootFinderStatus::ErrorSlope: {
1473 653 : if (!state.dataGlobal->WarmupFlag && controllerProps.BadActionErrCount == 0) {
1474 4 : ++controllerProps.BadActionErrCount;
1475 4 : ShowSevereError(state, format("FindRootSimpleController: Controller error for controller = \"{}\"", ControllerName));
1476 4 : ShowContinueErrorTimeStamp(state, "");
1477 4 : ShowContinueError(state,
1478 4 : fmt::format(" Controller function is inconsistent with user specified controller action = {}",
1479 4 : state.dataHVACCtrl->ActionTypes[static_cast<int>(controllerProps.Action)]));
1480 4 : ShowContinueError(state, " Actuator will be set to maximum action");
1481 4 : ShowContinueError(state, format("Controller control type={}", ControlVariableTypes(controllerProps.ControlVar)));
1482 4 : if (controllerProps.ControlVar == CtrlVarType::Temperature) {
1483 4 : ShowContinueError(state, format("Controller temperature setpoint = {:.2T} [C]", controllerProps.SetPointValue));
1484 4 : ShowContinueError(state, format("Controller sensed temperature = {:.2T} [C]", controllerProps.SensedValue));
1485 0 : } else if (controllerProps.ControlVar == CtrlVarType::HumidityRatio) {
1486 0 : ShowContinueError(state, format("Controller humidity ratio setpoint = {:.2T} [kgWater/kgDryAir]", controllerProps.SetPointValue));
1487 0 : ShowContinueError(state, format("Controller sensed humidity ratio = {:.2T} [kgWater/kgDryAir]", controllerProps.SensedValue));
1488 0 : } else if (controllerProps.ControlVar == CtrlVarType::TemperatureAndHumidityRatio) {
1489 0 : if (controllerProps.HumRatCtrlOverride) {
1490 0 : ShowContinueError(state, "Humidity control is active.");
1491 0 : ShowContinueError(state, format("Controller humidity ratio setpoint = {:.2T} [kgWater/kgDryAir]", controllerProps.SetPointValue));
1492 0 : ShowContinueError(state, format("Controller sensed humidity ratio = {:.2T} [kgWater/kgDryAir]", controllerProps.SensedValue));
1493 0 : ShowContinueError(state,
1494 0 : format("Controller humidity ratio setpoint dew-point temperature = {:.2T} [C]",
1495 0 : Psychrometrics::PsyTdpFnWPb(state, controllerProps.SetPointValue, state.dataEnvrn->OutBaroPress)));
1496 0 : ShowContinueError(
1497 : state,
1498 0 : format("Controller temperature setpoint = {:.2T} [C]", state.dataLoopNodes->Node(controllerProps.SensedNode).TempSetPoint));
1499 0 : ShowContinueError(
1500 0 : state, format("Controller sensed temperature = {:.2T} [C]", state.dataLoopNodes->Node(controllerProps.SensedNode).Temp));
1501 : } else {
1502 0 : ShowContinueError(state, format("Controller temperature setpoint = {:.2T} [C]", controllerProps.SetPointValue));
1503 0 : ShowContinueError(state, format("Controller sensed temperature = {:.2T} [C]", controllerProps.SensedValue));
1504 : }
1505 0 : } else if (controllerProps.ControlVar == CtrlVarType::Flow) {
1506 0 : ShowContinueError(state, format("Controller mass flow rate setpoint = {:.2T} [kg/s]", controllerProps.SetPointValue));
1507 0 : ShowContinueError(state, format("Controller sensed mass flow rate = {:.2T} [kg/s]", controllerProps.SensedValue));
1508 : } else {
1509 : // bad control variable input checked in input routine
1510 : }
1511 4 : if (controllerProps.ActuatorVar == CtrlVarType::Flow) {
1512 4 : ShowContinueError(state, format("Controller actuator mass flow rate set to {:.2T} [kg/s]", controllerProps.MaxAvailActuated));
1513 4 : if (controllerProps.ControlVar == CtrlVarType::Temperature ||
1514 0 : controllerProps.ControlVar == CtrlVarType::TemperatureAndHumidityRatio) {
1515 8 : ShowContinueError(
1516 8 : state, format("Controller actuator temperature = {:.2T} [C]", state.dataLoopNodes->Node(controllerProps.ActuatedNode).Temp));
1517 4 : if (controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterCooling ||
1518 3 : controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterDetailedFlatCooling) {
1519 1 : if (controllerProps.HumRatCtrlOverride) {
1520 0 : ShowContinueError(state,
1521 : "The entering chilled water temperature (controller actuator temperature) should be below the humidity "
1522 : "ratio setpoint dew-point temperature.");
1523 : } else {
1524 1 : ShowContinueError(
1525 : state,
1526 : "The entering chilled water temperature (controller actuator temperature) should be below the setpoint temperature.");
1527 : }
1528 3 : } else if (controllerProps.WaterCoilType == DataPlant::PlantEquipmentType::CoilWaterSimpleHeating) {
1529 3 : ShowContinueError(
1530 : state, "The entering hot water temperature (controller actuator temperature) should be above the setpoint temperature");
1531 : }
1532 : }
1533 : } else {
1534 : // bad actuator variable input checked in input routine
1535 : }
1536 649 : } else if (!state.dataGlobal->WarmupFlag) {
1537 0 : ++controllerProps.BadActionErrCount;
1538 0 : ShowRecurringSevereErrorAtEnd(state,
1539 0 : "FindRootSimpleController: Previous controller action error continues for controller = " + ControllerName,
1540 0 : controllerProps.BadActionErrIndex);
1541 : } else {
1542 : // do nothing
1543 : }
1544 : // Indicate convergence with min value
1545 : // Should be the same as ControllerProps(ControlNum)%MaxAvailActuated
1546 653 : ExitCalcController(state, ControlNum, rootFinders.MaxPoint.X, ControllerMode::MaxActive, IsConvergedFlag, IsUpToDateFlag);
1547 653 : } break;
1548 0 : default: {
1549 : // Should never happen
1550 0 : ShowSevereError(state, format("FindRootSimpleController: Root finder failed at {}", CreateHVACStepFullString(state)));
1551 0 : ShowContinueError(state, format(" Controller name={}", ControllerName));
1552 0 : ShowContinueError(state, format(" Unrecognized root finder status flag={}", rootFinders.StatusFlag));
1553 0 : ShowFatalError(state, "Preceding error causes program termination.");
1554 0 : } break;
1555 : }
1556 19745373 : }
1557 :
1558 14918570 : void CheckSimpleController(EnergyPlusData &state, int const ControlNum, bool &IsConvergedFlag)
1559 : {
1560 :
1561 : // SUBROUTINE INFORMATION:
1562 : // AUTHOR Dimitri Curtil (LBNL)
1563 : // DATE WRITTEN Feb 2006
1564 :
1565 : // PURPOSE OF THIS SUBROUTINE:
1566 : // New routine used to detect whether controller can be considered converged
1567 : // depending on its mode of operation.
1568 : // Used after all controllers on an air loop have been solved in order
1569 : // to make sure that final air loop state still represents a converged
1570 : // state.
1571 : // PRECONDITION: Setpoint must be known. See ControllerProps%IsSetPointDefinedFlag
1572 :
1573 14918570 : auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
1574 14918570 : auto &rootFinders = state.dataHVACControllers->RootFinders(ControlNum);
1575 :
1576 : // Default initialization: assuming no convergence unless detected in the following code!
1577 14918570 : IsConvergedFlag = false;
1578 :
1579 14918570 : switch (controllerProps.Mode) {
1580 1672272 : case ControllerMode::Off: {
1581 : // Check whether the component is running
1582 : // This check is performed by looking at the component mass flow rate at the sensed node.
1583 : // Since the components have been simulated before getting here, if they are zero they should be OFF.
1584 1672272 : if (state.dataLoopNodes->Node(controllerProps.SensedNode).MassFlowRate == 0.0) {
1585 1672256 : if (controllerProps.ActuatedValue == 0.0) {
1586 1672256 : IsConvergedFlag = true;
1587 1672256 : return;
1588 : }
1589 : }
1590 16 : } break;
1591 611300 : case ControllerMode::Inactive: {
1592 : // Controller component NOT available (ie, inactive)
1593 : // Make sure that the actuated variable is still equal to the node min avail
1594 : // NOTE: Replaced Node(ActuatedNode)%MassFlowRateMinAvail in release 1.3
1595 : // with ControllerProps(ControlNum)%MinAvailActuated in release 1.4
1596 611300 : if (controllerProps.ActuatedValue == controllerProps.MinAvailActuated) {
1597 611300 : IsConvergedFlag = true;
1598 611300 : return;
1599 : }
1600 0 : } break;
1601 5916793 : case ControllerMode::MinActive: {
1602 : // Check for min constrained convergence
1603 5916793 : if (CheckMinActiveController(state, ControlNum)) {
1604 5070034 : IsConvergedFlag = true;
1605 5070034 : return;
1606 : }
1607 : // Check for unconstrained convergence assuming that there is more than one controller controlling
1608 : // the same sensed node and that the other controller was able to meet the setpoint although this one
1609 : // was min-constrained.
1610 846759 : if (RootFinder::CheckRootFinderConvergence(rootFinders, controllerProps.DeltaSensed)) {
1611 : // Indicate convergence with base value (used to compute DeltaSensed!)
1612 82260 : IsConvergedFlag = true;
1613 82260 : return;
1614 : }
1615 764499 : } break;
1616 826080 : case ControllerMode::MaxActive: {
1617 : // Check for max constrained convergence
1618 826080 : if (CheckMaxActiveController(state, ControlNum)) {
1619 705579 : IsConvergedFlag = true;
1620 705579 : return;
1621 : }
1622 : // Check for unconstrained convergence assuming that there is more than one controller controlling
1623 : // the same sensed node and that the other controller was able to meet the setpoint although this one
1624 : // was max-constrained.
1625 120501 : if (RootFinder::CheckRootFinderConvergence(rootFinders, controllerProps.DeltaSensed)) {
1626 : // Indicate convergence with base value (used to compute DeltaSensed!)
1627 93 : IsConvergedFlag = true;
1628 93 : return;
1629 : }
1630 120408 : } break;
1631 5892110 : case ControllerMode::Active: {
1632 : // Check min constraint on actuated variable
1633 5892110 : if (controllerProps.ActuatedValue < controllerProps.MinAvailActuated) {
1634 5123 : IsConvergedFlag = false;
1635 5123 : return;
1636 : }
1637 : // Check max constraint on actuated variable
1638 5886987 : if (controllerProps.ActuatedValue > controllerProps.MaxAvailActuated) {
1639 0 : IsConvergedFlag = false;
1640 0 : return;
1641 : }
1642 :
1643 : // Check for unconstrained convergence
1644 : // Equivalent to:
1645 : // IF ((ABS(ControllerProps(ControlNum)%DeltaSensed) .LE. ControllerProps(ControlNum)%Offset)) THEN
1646 : // NOTE: If setpoint has changed since last call, then the following test will most likely fail.
1647 5886987 : if (RootFinder::CheckRootFinderConvergence(rootFinders, controllerProps.DeltaSensed)) {
1648 : // Indicate convergence with base value (used to compute DeltaSensed!)
1649 4535898 : IsConvergedFlag = true;
1650 4535898 : return;
1651 : }
1652 : // Check for min constrained convergence
1653 1351089 : if (CheckMinActiveController(state, ControlNum)) {
1654 57755 : IsConvergedFlag = true;
1655 57755 : return;
1656 : }
1657 : // Check for max constrained convergence
1658 1293334 : if (CheckMaxActiveController(state, ControlNum)) {
1659 19784 : IsConvergedFlag = true;
1660 19784 : return;
1661 : }
1662 1273550 : } break;
1663 15 : default: {
1664 : // Can only happen if controller is not converged after MaxIter in SolveAirLoopControllers()
1665 : // which will produce ControllerProps(ControlNum)%Mode = iModeNone
1666 15 : IsConvergedFlag = false;
1667 15 : } break;
1668 : }
1669 : }
1670 :
1671 7267882 : bool CheckMinActiveController(EnergyPlusData &state, int const ControlNum)
1672 : {
1673 : // FUNCTION INFORMATION:
1674 : // AUTHOR Dimitri Curtil
1675 : // DATE WRITTEN May 2006
1676 :
1677 : // PURPOSE OF THIS FUNCTION:
1678 : // Returns true if controller is min-constrained. false otherwise.
1679 :
1680 7267882 : auto const &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
1681 :
1682 : // Check that actuated value is the min avail actuated value
1683 7267882 : if (controllerProps.ActuatedValue != controllerProps.MinAvailActuated) {
1684 1074757 : return false;
1685 : }
1686 :
1687 6193125 : switch (controllerProps.Action) {
1688 4862438 : case ControllerAction::NormalAction: { // "NORMAL"
1689 : // Check for min constrained convergence
1690 4862438 : if (controllerProps.SetPointValue <= controllerProps.SensedValue) {
1691 3858775 : return true;
1692 : }
1693 1003663 : } break;
1694 1330687 : case ControllerAction::Reverse: { // "REVERSE"
1695 : // Check for min constrained convergence
1696 1330687 : if (controllerProps.SetPointValue >= controllerProps.SensedValue) {
1697 1269014 : return true;
1698 : }
1699 61673 : } break;
1700 0 : default: {
1701 : // Should never happen
1702 0 : ShowSevereError(state, format("CheckMinActiveController: Invalid controller action during {}.", CreateHVACStepFullString(state)));
1703 0 : ShowContinueError(state, format("CheckMinActiveController: Controller name={}", controllerProps.ControllerName));
1704 0 : ShowContinueError(state, R"(CheckMinActiveController: Valid choices are "NORMAL" or "REVERSE")");
1705 0 : ShowFatalError(state, "CheckMinActiveController: Preceding error causes program termination.");
1706 0 : } break;
1707 : }
1708 :
1709 1065336 : return false;
1710 : }
1711 :
1712 2119414 : bool CheckMaxActiveController(EnergyPlusData &state, int const ControlNum)
1713 : {
1714 : // FUNCTION INFORMATION:
1715 : // AUTHOR Dimitri Curtil
1716 : // DATE WRITTEN May 2006
1717 :
1718 : // PURPOSE OF THIS FUNCTION:
1719 : // Returns true if controller is max-constrained. false otherwise.
1720 :
1721 2119414 : auto &ControllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
1722 :
1723 : // Check that actuated value is the max avail actuated value
1724 2119414 : if (ControllerProps.ActuatedValue != ControllerProps.MaxAvailActuated) {
1725 1269356 : return false;
1726 : }
1727 :
1728 850058 : switch (ControllerProps.Action) {
1729 242939 : case ControllerAction::NormalAction: { // "NORMAL"
1730 : // Check for max constrained convergence
1731 242939 : if (ControllerProps.SetPointValue >= ControllerProps.SensedValue) {
1732 238243 : return true;
1733 : }
1734 4696 : } break;
1735 607119 : case ControllerAction::Reverse: { // "REVERSE"
1736 : // Check for max constrained convergence
1737 607119 : if (ControllerProps.SetPointValue <= ControllerProps.SensedValue) {
1738 487120 : return true;
1739 : }
1740 119999 : } break;
1741 0 : default: {
1742 : // Should never happen
1743 0 : ShowSevereError(state, format("CheckMaxActiveController: Invalid controller action during {}.", CreateHVACStepFullString(state)));
1744 0 : ShowContinueError(state, format("CheckMaxActiveController: Controller name={}", ControllerProps.ControllerName));
1745 0 : ShowContinueError(state, R"(CheckMaxActiveController: Valid choices are "NORMAL" or "REVERSE")");
1746 0 : ShowFatalError(state, "CheckMaxActiveController: Preceding error causes program termination.");
1747 0 : } break;
1748 : }
1749 :
1750 124695 : return false;
1751 : }
1752 :
1753 14918570 : void SaveSimpleController(EnergyPlusData &state, int const ControlNum, bool const FirstHVACIteration, bool const IsConvergedFlag)
1754 : {
1755 :
1756 : // SUBROUTINE INFORMATION:
1757 : // AUTHOR Dimitri Curtil
1758 : // DATE WRITTEN April 2006
1759 :
1760 : // PURPOSE OF THIS SUBROUTINE:
1761 : // Updates solution trackers if simple controller is converged.
1762 :
1763 14918570 : auto &ControllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
1764 :
1765 : // Save solution and mode for next call only if converged
1766 14918570 : if (IsConvergedFlag) {
1767 12754959 : int PreviousSolutionIndex = FirstHVACIteration ? 1 : 2;
1768 :
1769 12754959 : if (ControllerProps.Mode == ControllerMode::Active) {
1770 4613437 : ControllerProps.SolutionTrackers(PreviousSolutionIndex).DefinedFlag = true;
1771 4613437 : ControllerProps.SolutionTrackers(PreviousSolutionIndex).Mode = ControllerProps.Mode;
1772 4613437 : ControllerProps.SolutionTrackers(PreviousSolutionIndex).ActuatedValue = ControllerProps.NextActuatedValue;
1773 : } else {
1774 8141522 : ControllerProps.SolutionTrackers(PreviousSolutionIndex).DefinedFlag = false;
1775 8141522 : ControllerProps.SolutionTrackers(PreviousSolutionIndex).Mode = ControllerProps.Mode;
1776 8141522 : ControllerProps.SolutionTrackers(PreviousSolutionIndex).ActuatedValue = ControllerProps.NextActuatedValue;
1777 : }
1778 : }
1779 14918570 : }
1780 :
1781 35979571 : void UpdateController(EnergyPlusData &state, int const ControlNum)
1782 : {
1783 : // PURPOSE OF THIS SUBROUTINE:
1784 : // This subroutine updates the actuated node with the next candidate value.
1785 :
1786 35979571 : auto &ControllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
1787 :
1788 : // Set the actuated node of the Controller
1789 35979571 : switch (ControllerProps.ActuatorVar) {
1790 35979571 : case CtrlVarType::Flow: { // 'Flow'
1791 35979571 : PlantUtilities::SetActuatedBranchFlowRate(
1792 35979571 : state, ControllerProps.NextActuatedValue, ControllerProps.ActuatedNode, ControllerProps.ActuatedNodePlantLoc, false);
1793 : // Node(ActuatedNode)%MassFlowRate = ControllerProps(ControlNum)%NextActuatedValue
1794 35979571 : } break;
1795 0 : default: {
1796 0 : ShowFatalError(state, format("UpdateController: Invalid Actuator Variable Type={}", ControlVariableTypes(ControllerProps.ActuatorVar)));
1797 0 : } break;
1798 : }
1799 35979571 : }
1800 :
1801 20748407 : void CheckTempAndHumRatCtrl(EnergyPlusData &state, int const ControlNum, bool &IsConvergedFlag)
1802 : {
1803 : {
1804 20748407 : auto &thisController = state.dataHVACControllers->ControllerProps(ControlNum);
1805 20748407 : if (IsConvergedFlag) {
1806 9808276 : if (thisController.ControlVar == CtrlVarType::TemperatureAndHumidityRatio) {
1807 : // For temperature and humidity control, after temperature control is converged, check if humidity setpoint is met
1808 487061 : if (!thisController.HumRatCtrlOverride) {
1809 : // For humidity control tolerance, always use 0.0001 which is roughly equivalent to a 0.015C change in dewpoint
1810 424998 : if (state.dataLoopNodes->Node(thisController.SensedNode).HumRat >
1811 424998 : (state.dataLoopNodes->Node(thisController.SensedNode).HumRatMax + 1.0e-5)) {
1812 : // Turn on humidity control and restart controller
1813 62078 : IsConvergedFlag = false;
1814 62078 : thisController.HumRatCtrlOverride = true;
1815 62078 : if (thisController.Action == ControllerAction::Reverse) {
1816 : // Cooling coil controller should always be Reverse, but skip this if not
1817 62078 : RootFinder::SetupRootFinder(state,
1818 62078 : state.dataHVACControllers->RootFinders(ControlNum),
1819 : DataRootFinder::Slope::Decreasing,
1820 : DataRootFinder::RootFinderMethod::FalsePosition,
1821 : DataPrecisionGlobals::constant_zero,
1822 : 1.0e-6,
1823 : 1.0e-5);
1824 : }
1825 : // Do a cold start reset, same as iControllerOpColdStart
1826 62078 : ResetController(state, ControlNum, false, IsConvergedFlag);
1827 : }
1828 : }
1829 : }
1830 : }
1831 : }
1832 20748407 : }
1833 :
1834 9808276 : void ExitCalcController(EnergyPlusData &state,
1835 : int const ControlNum,
1836 : Real64 const NextActuatedValue,
1837 : ControllerMode const Mode,
1838 : bool &IsConvergedFlag,
1839 : bool &IsUpToDateFlag)
1840 : {
1841 :
1842 : // SUBROUTINE INFORMATION:
1843 : // AUTHOR Dimitri Curtil
1844 : // DATE WRITTEN February 06
1845 :
1846 : // PURPOSE OF THIS SUBROUTINE:
1847 : // Only called when controller is considered as "converged", meaning that we do no longer
1848 : // need to continue iterating.
1849 :
1850 : // METHODOLOGY EMPLOYED:
1851 : // Updates:
1852 : // - next actuated value
1853 : // - controller mode
1854 : // - IsConvergedFlag
1855 : // - IsUpToDateFlag
1856 :
1857 9808276 : auto &ControllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
1858 :
1859 9808276 : ControllerProps.NextActuatedValue = NextActuatedValue;
1860 9808276 : ControllerProps.Mode = Mode;
1861 9808276 : IsConvergedFlag = true;
1862 :
1863 : // Set IsUpToDateFlag upon exiting to indicate caller whether or not the air loop needs to be
1864 : // re-simulated with the current candidate value, ie ControllerProps(ControlNum)%NextActuatedValue
1865 9808276 : if (ControllerProps.ActuatedValue != ControllerProps.NextActuatedValue) {
1866 392731 : IsUpToDateFlag = false;
1867 : } else {
1868 9415545 : IsUpToDateFlag = true;
1869 : }
1870 9808276 : }
1871 :
1872 0 : void TrackAirLoopControllers(EnergyPlusData &state,
1873 : int const AirLoopNum,
1874 : DataHVACControllers::ControllerWarmRestart const WarmRestartStatus,
1875 : int const AirLoopIterMax,
1876 : int const AirLoopIterTot,
1877 : int const AirLoopNumCalls)
1878 : {
1879 :
1880 : // SUBROUTINE INFORMATION:
1881 : // AUTHOR Dimitri Curtil
1882 : // DATE WRITTEN April 2006
1883 :
1884 : // PURPOSE OF THIS SUBROUTINE:
1885 : // Updates runtime statistics for controllers on the specified air loop.
1886 : // Used to produce objective metrics when analyzing runtime performance
1887 : // of HVAC controllers for different implementations.
1888 :
1889 : // SUBROUTINE PARAMETER DEFINITIONS:
1890 : // See CONTROLLER_WARM_RESTART_<> parameters in DataHVACControllers.cc
1891 : // If Status<0, no speculative warm restart.
1892 : // If Status==0, speculative warm restart failed.
1893 : // If Status>0, speculative warm restart succeeded.
1894 : // Max number of iterations performed by controllers on this air loop (per call to SimAirLoop)
1895 : // Aggregated number of iterations performed by controllers on this air loop (per call to SimAirLoop)
1896 : // Number of times SimAirLoopComponents() has been invoked
1897 :
1898 0 : auto &airLoopStats = state.dataHVACControllers->AirLoopStats(AirLoopNum);
1899 :
1900 : // If no controllers on this air loop then we have nothing to do
1901 0 : if (state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers == 0) return;
1902 : // To avoid tracking statistics in case of no air loop or no HVAC controllers are defined
1903 0 : if (state.dataHVACControllers->NumAirLoopStats == 0) return;
1904 :
1905 : // Update performance statistics for air loop
1906 0 : ++airLoopStats.NumCalls;
1907 :
1908 0 : switch (WarmRestartStatus) {
1909 0 : case DataHVACControllers::ControllerWarmRestart::Success: {
1910 0 : ++airLoopStats.NumSuccessfulWarmRestarts;
1911 0 : } break;
1912 0 : case DataHVACControllers::ControllerWarmRestart::Fail: {
1913 0 : ++airLoopStats.NumFailedWarmRestarts;
1914 0 : } break;
1915 0 : default: {
1916 : // Nothing to do if no speculative warm restart used
1917 0 : } break;
1918 : }
1919 :
1920 0 : airLoopStats.TotSimAirLoopComponents += AirLoopNumCalls;
1921 :
1922 0 : airLoopStats.MaxSimAirLoopComponents = max(airLoopStats.MaxSimAirLoopComponents, AirLoopNumCalls);
1923 :
1924 0 : airLoopStats.TotIterations += AirLoopIterTot;
1925 :
1926 0 : airLoopStats.MaxIterations = max(airLoopStats.MaxIterations, AirLoopIterMax);
1927 :
1928 : // Update performance statistics for each controller on air loop
1929 0 : for (int ControllerNum = 1; ControllerNum <= state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers; ++ControllerNum) {
1930 0 : TrackAirLoopController(state, AirLoopNum, ControllerNum);
1931 : }
1932 : }
1933 :
1934 0 : void TrackAirLoopController(EnergyPlusData &state,
1935 : int const AirLoopNum, // Air loop index
1936 : int const AirLoopControlNum // Controller index on this air loop
1937 : )
1938 : {
1939 :
1940 : // SUBROUTINE INFORMATION:
1941 : // AUTHOR Dimitri Curtil
1942 : // DATE WRITTEN April 2006
1943 :
1944 : // PURPOSE OF THIS SUBROUTINE:
1945 : // Updates runtime statistics for the specified controller.
1946 : // Used to produce objective metrics when analyzing runtime performance
1947 : // of HVAC controllers for different implementations.
1948 :
1949 0 : auto &airLoopStats = state.dataHVACControllers->AirLoopStats(AirLoopNum);
1950 0 : int ControlIndex = state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).ControllerIndex(AirLoopControlNum);
1951 0 : auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlIndex);
1952 :
1953 : // We use NumCalcCalls instead of the iteration counter used in SolveAirLoopControllers()
1954 : // to avoid having to call TrackAirLoopController() directly from SolveAirLoopControllers().
1955 : // The 2 counters should be the same anyway as NumCalcCalls is first reset to zero and
1956 : // incremented each time ManageControllers() is invoked with iControllerOpIterate
1957 0 : int IterationCount = controllerProps.NumCalcCalls;
1958 0 : ControllerMode Mode = controllerProps.Mode;
1959 :
1960 0 : if (Mode != ControllerMode::None) {
1961 :
1962 0 : ++airLoopStats.ControllerStats(AirLoopControlNum).NumCalls(static_cast<int>(Mode));
1963 :
1964 0 : airLoopStats.ControllerStats(AirLoopControlNum).TotIterations(static_cast<int>(Mode)) += IterationCount;
1965 :
1966 0 : airLoopStats.ControllerStats(AirLoopControlNum).MaxIterations(static_cast<int>(Mode)) =
1967 0 : max(airLoopStats.ControllerStats(AirLoopControlNum).MaxIterations(static_cast<int>(Mode)), IterationCount);
1968 : }
1969 0 : }
1970 :
1971 794 : void DumpAirLoopStatistics(EnergyPlusData &state)
1972 : {
1973 :
1974 : // SUBROUTINE INFORMATION:
1975 : // AUTHOR Dimitri Curtil
1976 : // DATE WRITTEN April 2006
1977 :
1978 : // PURPOSE OF THIS SUBROUTINE:
1979 : // Writes runtime statistics for controllers on all air loops
1980 : // to a CSV file named "statistics.HVACControllers.csv".
1981 :
1982 : // Detect if statistics have been generated or not for this run
1983 794 : if (!state.dataSysVars->TrackAirLoopEnvFlag) {
1984 794 : return;
1985 : }
1986 :
1987 0 : InputOutputFilePath StatisticsFilePath{"statistics.HVACControllers.csv"};
1988 0 : auto statisticsFile = StatisticsFilePath.open(state, "DumpAirLoopStatistics"); // (THIS_AUTO_OK)
1989 :
1990 : // note that the AirLoopStats object does not seem to be initialized when this code
1991 : // is executed and it causes a crash here
1992 0 : for (int AirLoopNum = 1; AirLoopNum <= state.dataHVACGlobal->NumPrimaryAirSys; ++AirLoopNum) {
1993 0 : WriteAirLoopStatistics(
1994 0 : state, statisticsFile, state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum), state.dataHVACControllers->AirLoopStats(AirLoopNum));
1995 : }
1996 0 : }
1997 :
1998 0 : void WriteAirLoopStatistics(EnergyPlusData &state,
1999 : InputOutputFile &statisticsFile,
2000 : DefinePrimaryAirSystem const &ThisPrimaryAirSystem,
2001 : AirLoopStatsType const &ThisAirLoopStats)
2002 : {
2003 :
2004 : // SUBROUTINE INFORMATION:
2005 : // AUTHOR Dimitri Curtil
2006 : // DATE WRITTEN April 2006
2007 :
2008 : // PURPOSE OF THIS SUBROUTINE:
2009 : // Writes runtime statistics for controllers on the specified air loop
2010 : // to the specified file.
2011 :
2012 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
2013 : Real64 WarmRestartSuccessRatio;
2014 : Real64 AvgIterations;
2015 :
2016 0 : print(statisticsFile, "{},\n", ThisPrimaryAirSystem.Name);
2017 :
2018 : // Number of calls to SimAirLoop() has been invoked over the course of the simulation
2019 : // to simulate the specified air loop
2020 0 : print(statisticsFile, "NumCalls,{}\n", ThisAirLoopStats.NumCalls);
2021 :
2022 : // Warm restart success ratio
2023 0 : int NumWarmRestarts = ThisAirLoopStats.NumSuccessfulWarmRestarts + ThisAirLoopStats.NumFailedWarmRestarts;
2024 0 : if (NumWarmRestarts == 0) {
2025 0 : WarmRestartSuccessRatio = 0.0;
2026 : } else {
2027 0 : WarmRestartSuccessRatio = double(ThisAirLoopStats.NumSuccessfulWarmRestarts) / double(NumWarmRestarts);
2028 : }
2029 :
2030 0 : print(statisticsFile, "NumWarmRestarts,{}\n", NumWarmRestarts);
2031 0 : print(statisticsFile, "NumSuccessfulWarmRestarts,{}\n", ThisAirLoopStats.NumSuccessfulWarmRestarts);
2032 0 : print(statisticsFile, "NumFailedWarmRestarts,{}\n", ThisAirLoopStats.NumFailedWarmRestarts);
2033 0 : print(statisticsFile, "WarmRestartSuccessRatio,{:.10T}\n", WarmRestartSuccessRatio);
2034 :
2035 : // Total number of times SimAirLoopComponents() has been invoked over the course of the simulation
2036 : // to simulate the specified air loop
2037 0 : print(statisticsFile, "TotSimAirLoopComponents,{}\n", ThisAirLoopStats.TotSimAirLoopComponents);
2038 : // Maximum number of times SimAirLoopComponents() has been invoked over the course of the simulation
2039 : // to simulate the specified air loop
2040 0 : print(statisticsFile, "MaxSimAirLoopComponents,{}\n", ThisAirLoopStats.MaxSimAirLoopComponents);
2041 :
2042 : // Aggregated number of iterations needed by all controllers to simulate the specified air loop
2043 0 : print(statisticsFile, "TotIterations,{}\n", ThisAirLoopStats.TotIterations);
2044 : // Maximum number of iterations needed by controllers to simulate the specified air loop
2045 0 : print(statisticsFile, "MaxIterations,{}\n", ThisAirLoopStats.MaxIterations);
2046 :
2047 : // Average number of iterations needed by controllers to simulate the specified air loop
2048 0 : if (ThisAirLoopStats.NumCalls == 0) {
2049 0 : AvgIterations = 0.0;
2050 : } else {
2051 0 : AvgIterations = double(ThisAirLoopStats.TotIterations) / double(ThisAirLoopStats.NumCalls);
2052 : }
2053 :
2054 0 : print(statisticsFile, "AvgIterations,{:.10T}\n", AvgIterations);
2055 :
2056 : // Dump statistics for each controller on this air loop
2057 0 : for (int AirLoopControlNum = 1; AirLoopControlNum <= ThisPrimaryAirSystem.NumControllers; ++AirLoopControlNum) {
2058 :
2059 0 : print(statisticsFile, "{},\n", ThisPrimaryAirSystem.ControllerName(AirLoopControlNum));
2060 :
2061 : // Aggregate iteration trackers across all operating modes
2062 0 : int NumCalls = 0;
2063 0 : int TotIterations = 0;
2064 0 : int MaxIterations = 0;
2065 :
2066 0 : for (int iModeNum = iFirstMode; iModeNum <= iLastMode; ++iModeNum) {
2067 0 : NumCalls += ThisAirLoopStats.ControllerStats(AirLoopControlNum).NumCalls(iModeNum);
2068 :
2069 0 : TotIterations += ThisAirLoopStats.ControllerStats(AirLoopControlNum).TotIterations(iModeNum);
2070 :
2071 0 : MaxIterations = max(MaxIterations, ThisAirLoopStats.ControllerStats(AirLoopControlNum).MaxIterations(iModeNum));
2072 : }
2073 :
2074 : // Number of times this controller was simulated (should match air loop num calls)
2075 0 : print(statisticsFile, "NumCalls,{}\n", NumCalls);
2076 : // Aggregated number of iterations needed by this controller
2077 0 : print(statisticsFile, "TotIterations,{}\n", TotIterations);
2078 : // Aggregated number of iterations needed by this controller
2079 0 : print(statisticsFile, "MaxIterations,{}\n", MaxIterations);
2080 :
2081 : // Average number of iterations needed by controllers to simulate the specified air loop
2082 0 : if (NumCalls == 0) {
2083 0 : AvgIterations = 0.0;
2084 : } else {
2085 0 : AvgIterations = double(TotIterations) / double(NumCalls);
2086 : }
2087 0 : print(statisticsFile, "AvgIterations,{:.10T}\n", AvgIterations);
2088 :
2089 : // Dump iteration trackers for each operating mode
2090 0 : for (int iModeNum = iFirstMode; iModeNum <= iLastMode; ++iModeNum) {
2091 :
2092 0 : print(statisticsFile, "{},\n", state.dataHVACCtrl->ControllerModeTypes(iModeNum));
2093 :
2094 : // Number of times this controller operated in this mode
2095 0 : print(statisticsFile, "NumCalls,{}\n", ThisAirLoopStats.ControllerStats(AirLoopControlNum).NumCalls(iModeNum));
2096 :
2097 : // Aggregated number of iterations needed by this controller
2098 0 : print(statisticsFile, "TotIterations,{}\n", ThisAirLoopStats.ControllerStats(AirLoopControlNum).TotIterations(iModeNum));
2099 : // Aggregated number of iterations needed by this controller
2100 0 : print(statisticsFile, "MaxIterations,{}\n", ThisAirLoopStats.ControllerStats(AirLoopControlNum).MaxIterations(iModeNum));
2101 :
2102 : // Average number of iterations needed by controllers to simulate the specified air loop
2103 0 : if (ThisAirLoopStats.ControllerStats(AirLoopControlNum).NumCalls(iModeNum) == 0) {
2104 0 : AvgIterations = 0.0;
2105 : } else {
2106 0 : AvgIterations = double(ThisAirLoopStats.ControllerStats(AirLoopControlNum).TotIterations(iModeNum)) /
2107 0 : double(ThisAirLoopStats.ControllerStats(AirLoopControlNum).NumCalls(iModeNum));
2108 : }
2109 0 : print(statisticsFile, "AvgIterations,{:.10T}\n", AvgIterations);
2110 : }
2111 : }
2112 0 : }
2113 :
2114 0 : void SetupAirLoopControllersTracer(EnergyPlusData &state, int const AirLoopNum)
2115 : {
2116 :
2117 : // SUBROUTINE INFORMATION:
2118 : // AUTHOR Dimitri Curtil
2119 : // DATE WRITTEN February 2006
2120 :
2121 : // PURPOSE OF THIS SUBROUTINE:
2122 : // Opens main trace file for controllers on specific air loop
2123 : // and writes header row with titles.
2124 :
2125 : // Open main controller trace file for each air loop
2126 0 : const std::string TraceFilePath = fmt::format("controller.{}.csv", state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).Name);
2127 :
2128 0 : auto &airLoopStats = state.dataHVACControllers->AirLoopStats(AirLoopNum);
2129 :
2130 : // Store file unit in air loop stats
2131 0 : airLoopStats.TraceFile->filePath = TraceFilePath;
2132 0 : airLoopStats.TraceFile->open();
2133 :
2134 0 : if (!airLoopStats.TraceFile->good()) {
2135 0 : ShowFatalError(state, format("SetupAirLoopControllersTracer: Failed to open air loop trace file \"{}\" for output (write).", TraceFilePath));
2136 0 : return;
2137 : }
2138 :
2139 0 : auto &TraceFile = *airLoopStats.TraceFile;
2140 :
2141 : // List all controllers and their corresponding handles into main trace file
2142 0 : print(TraceFile, "Num,Name,\n");
2143 :
2144 0 : for (int ControllerNum = 1; ControllerNum <= state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers; ++ControllerNum) {
2145 0 : print(TraceFile, "{},{},\n", ControllerNum, state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).ControllerName(ControllerNum));
2146 : // SAME AS ControllerProps(ControllerIndex)%ControllerName BUT NOT YET AVAILABLE
2147 : }
2148 :
2149 : // Skip a bunch of lines
2150 0 : print(TraceFile, "\n\n\n");
2151 :
2152 : // Write column header in main controller trace file
2153 0 : print(TraceFile,
2154 : "ZoneSizingCalc,SysSizingCalc,EnvironmentNum,WarmupFlag,SysTimeStamp,SysTimeInterval,BeginTimeStepFlag,FirstTimeStepSysFlag,"
2155 : "FirstHVACIteration,AirLoopPass,AirLoopNumCallsTot,AirLoopConverged,");
2156 :
2157 : // Write headers for final state
2158 0 : for (int ControllerNum = 1; ControllerNum <= state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers; ++ControllerNum) {
2159 0 : print(
2160 : TraceFile, "Mode{},IterMax{},XRoot{},YRoot{},YSetPoint{},\n", ControllerNum, ControllerNum, ControllerNum, ControllerNum, ControllerNum);
2161 : }
2162 :
2163 0 : print(TraceFile, "\n");
2164 0 : }
2165 :
2166 0 : void TraceAirLoopControllers(EnergyPlusData &state,
2167 : bool const FirstHVACIteration,
2168 : int const AirLoopNum,
2169 : int const AirLoopPass,
2170 : bool const AirLoopConverged,
2171 : int const AirLoopNumCalls)
2172 : {
2173 :
2174 : // SUBROUTINE INFORMATION:
2175 : // AUTHOR Dimitri Curtil
2176 : // DATE WRITTEN January 2006
2177 :
2178 : // PURPOSE OF THIS SUBROUTINE:
2179 : // This subroutine writes diagnostic to the trace file attached to each air loop.
2180 :
2181 : // SUBROUTINE ARGUMENT DEFINITIONS:
2182 : // TRUE when primary air system & controllers simulation has converged;
2183 : // Number of times SimAirLoopComponents() has been invoked
2184 :
2185 0 : auto &airLoopStats = state.dataHVACControllers->AirLoopStats(AirLoopNum);
2186 :
2187 : // IF no controllers on this air loop then we have nothing to do
2188 0 : if (state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers == 0) return;
2189 : // To avoid tracking statistics in case of no air loop or no HVAC controllers are defined
2190 0 : if (state.dataHVACControllers->NumAirLoopStats == 0) return;
2191 :
2192 : // Setup trace file on first call only
2193 0 : if (airLoopStats.FirstTraceFlag) {
2194 0 : SetupAirLoopControllersTracer(state, AirLoopNum);
2195 :
2196 0 : airLoopStats.FirstTraceFlag = false;
2197 : }
2198 :
2199 0 : auto &TraceFile = *airLoopStats.TraceFile;
2200 :
2201 0 : if (!TraceFile.good()) return;
2202 :
2203 : // Write iteration stamp first
2204 0 : TraceIterationStamp(state, TraceFile, FirstHVACIteration, AirLoopPass, AirLoopConverged, AirLoopNumCalls);
2205 :
2206 : // Loop over the air sys controllers and write diagnostic to trace file
2207 0 : for (int ControllerNum = 1; ControllerNum <= state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).NumControllers; ++ControllerNum) {
2208 0 : TraceAirLoopController(state, TraceFile, state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).ControllerIndex(ControllerNum));
2209 : }
2210 :
2211 : // Go to next line
2212 0 : print(TraceFile, "\n");
2213 : }
2214 :
2215 0 : void TraceIterationStamp(EnergyPlusData &state,
2216 : InputOutputFile &TraceFile,
2217 : bool const FirstHVACIteration,
2218 : int const AirLoopPass,
2219 : bool const AirLoopConverged,
2220 : int const AirLoopNumCalls)
2221 : {
2222 :
2223 : // SUBROUTINE INFORMATION:
2224 : // AUTHOR Dimitri Curtil
2225 : // DATE WRITTEN February 2006
2226 :
2227 : // PURPOSE OF THIS SUBROUTINE:
2228 : // Writes current iteration time stamp to specified trace file.
2229 :
2230 : // SUBROUTINE PARAMETER DEFINITIONS:
2231 : // TRUE when primary air system and controllers simulation has converged;
2232 : // Number of times SimAirLoopComponents() has been invoked
2233 :
2234 : // Write step stamp to air loop trace file after reset
2235 : // Note that we do not go to the next line
2236 0 : print(TraceFile,
2237 : "{},{},{},{},{},{},{},{},{},{},{},{},",
2238 0 : static_cast<int>(state.dataGlobal->ZoneSizingCalc),
2239 0 : static_cast<int>(state.dataGlobal->SysSizingCalc),
2240 0 : state.dataEnvrn->CurEnvirNum,
2241 0 : static_cast<int>(state.dataGlobal->WarmupFlag),
2242 0 : CreateHVACTimeString(state),
2243 0 : MakeHVACTimeIntervalString(state),
2244 0 : static_cast<int>(state.dataGlobal->BeginTimeStepFlag),
2245 0 : static_cast<int>(state.dataHVACGlobal->FirstTimeStepSysFlag),
2246 0 : static_cast<int>(FirstHVACIteration),
2247 : AirLoopPass,
2248 : AirLoopNumCalls,
2249 0 : static_cast<int>(AirLoopConverged));
2250 0 : }
2251 :
2252 0 : void TraceAirLoopController(EnergyPlusData &state, InputOutputFile &TraceFile, int const ControlNum)
2253 : {
2254 :
2255 : // SUBROUTINE INFORMATION:
2256 : // AUTHOR Dimitri Curtil
2257 : // DATE WRITTEN January 2006
2258 :
2259 : // PURPOSE OF THIS SUBROUTINE:
2260 : // This subroutine writes convergence diagnostic to the air loop trace file
2261 : // for the specified controller index.
2262 :
2263 0 : auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
2264 :
2265 0 : print(TraceFile,
2266 : "{},{},{:.10T},{:.10T},{:.10T},",
2267 0 : controllerProps.Mode,
2268 0 : controllerProps.NumCalcCalls,
2269 0 : state.dataLoopNodes->Node(controllerProps.ActuatedNode).MassFlowRate,
2270 0 : state.dataLoopNodes->Node(controllerProps.SensedNode).Temp,
2271 0 : state.dataLoopNodes->Node(controllerProps.SensedNode).TempSetPoint);
2272 0 : }
2273 :
2274 0 : void SetupIndividualControllerTracer(EnergyPlusData &state, int const ControlNum)
2275 : {
2276 :
2277 : // SUBROUTINE INFORMATION:
2278 : // AUTHOR Dimitri Curtil
2279 : // DATE WRITTEN February 2006
2280 :
2281 : // PURPOSE OF THIS SUBROUTINE:
2282 : // Opens individual controller trace file for the specified controller
2283 : // and writes header row.
2284 :
2285 0 : auto &controllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
2286 :
2287 0 : const std::string TraceFilePath = fmt::format("controller.{}.csv", controllerProps.ControllerName);
2288 0 : auto &TraceFile = *controllerProps.TraceFile;
2289 0 : TraceFile.filePath = TraceFilePath;
2290 0 : TraceFile.open();
2291 :
2292 0 : if (!TraceFile.good()) {
2293 0 : ShowFatalError(state,
2294 0 : format("SetupIndividualControllerTracer: Failed to open controller trace file \"{}\" for output (write).", TraceFilePath));
2295 0 : return;
2296 : }
2297 :
2298 : // Write header row
2299 : // Masss flow rate
2300 : // Convergence analysis
2301 0 : print(TraceFile,
2302 : "EnvironmentNum,WarmupFlag,SysTimeStamp,SysTimeInterval,AirLoopPass,FirstHVACIteration,Operation,NumCalcCalls,SensedNode%MassFlowRate,"
2303 : "ActuatedNode%MassFlowRateMinAvail,ActuatedNode%MassFlowRateMaxAvail,X,Y,Setpoint,DeltaSensed,Offset,Mode,IsConvergedFlag,"
2304 : "NextActuatedValue");
2305 :
2306 0 : RootFinder::WriteRootFinderTraceHeader(TraceFile);
2307 :
2308 : // Finally skip line
2309 0 : print(TraceFile, "\n");
2310 0 : }
2311 :
2312 0 : void TraceIndividualController(EnergyPlusData &state,
2313 : int const ControlNum,
2314 : bool const FirstHVACIteration,
2315 : int const AirLoopPass,
2316 : DataHVACControllers::ControllerOperation const Operation, // Operation to execute
2317 : bool const IsConvergedFlag)
2318 : {
2319 :
2320 : // SUBROUTINE INFORMATION:
2321 : // AUTHOR Dimitri Curtil
2322 : // DATE WRITTEN January 2006
2323 :
2324 : // PURPOSE OF THIS SUBROUTINE:
2325 : // This subroutine writes convergence diagnostic to the trace file for the specified controller.
2326 :
2327 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
2328 : bool SkipLineFlag;
2329 :
2330 0 : auto &ControllerProps = state.dataHVACControllers->ControllerProps(ControlNum);
2331 :
2332 : // Setup individual trace file on first trace only
2333 0 : if (ControllerProps.FirstTraceFlag) {
2334 0 : SetupIndividualControllerTracer(state, ControlNum);
2335 :
2336 0 : ControllerProps.FirstTraceFlag = false;
2337 0 : SkipLineFlag = false;
2338 : } else {
2339 0 : SkipLineFlag = FirstHVACIteration && (ControllerProps.NumCalcCalls == 0);
2340 : }
2341 :
2342 0 : auto &TraceFile = *ControllerProps.TraceFile;
2343 :
2344 : // Nothing to do if trace file not registered
2345 0 : if (!TraceFile.good()) return;
2346 :
2347 : // Skip a line before each new HVAC step
2348 0 : if (SkipLineFlag) {
2349 0 : print(TraceFile, "\n");
2350 : }
2351 :
2352 : // Set the sensed and actuated node numbers
2353 0 : int ActuatedNode = ControllerProps.ActuatedNode;
2354 0 : int SensedNode = ControllerProps.SensedNode;
2355 :
2356 : // Write iteration stamp
2357 0 : print(TraceFile,
2358 : "{},{},{},{},{},{},{},{},",
2359 0 : state.dataEnvrn->CurEnvirNum,
2360 0 : static_cast<int>(state.dataGlobal->WarmupFlag),
2361 0 : CreateHVACTimeString(state),
2362 0 : MakeHVACTimeIntervalString(state),
2363 : AirLoopPass,
2364 0 : static_cast<int>(FirstHVACIteration),
2365 : Operation,
2366 0 : ControllerProps.NumCalcCalls);
2367 :
2368 : // Write detailed diagnostic
2369 0 : switch (Operation) {
2370 0 : case DataHVACControllers::ControllerOperation::ColdStart:
2371 : case DataHVACControllers::ControllerOperation::WarmRestart: {
2372 0 : print(TraceFile,
2373 : "{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{},{},{},{},{:.10T},",
2374 0 : state.dataLoopNodes->Node(SensedNode).MassFlowRate,
2375 0 : state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMinAvail,
2376 0 : state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMaxAvail,
2377 0 : ControllerProps.ActuatedValue,
2378 0 : state.dataLoopNodes->Node(SensedNode).Temp,
2379 0 : ControllerProps.SetPointValue,
2380 0 : ' ',
2381 0 : ' ',
2382 0 : ControllerProps.Mode,
2383 0 : static_cast<int>(IsConvergedFlag),
2384 0 : ControllerProps.NextActuatedValue);
2385 : // X | Y | setpoint | DeltaSensed = Y - YRoot | Offset | Mode | IsConvergedFlag
2386 :
2387 : // No trace available for root finder yet
2388 : // Skip call to WriteRootFinderTrace()
2389 :
2390 : // Finally skip line
2391 0 : print(TraceFile, "\n");
2392 0 : } break;
2393 0 : case DataHVACControllers::ControllerOperation::Iterate: {
2394 : // Masss flow rate
2395 : // Convergence analysis
2396 :
2397 0 : print(TraceFile,
2398 : "{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{},{},{:.10T},",
2399 0 : state.dataLoopNodes->Node(SensedNode).MassFlowRate,
2400 0 : state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMinAvail,
2401 0 : state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMaxAvail,
2402 0 : ControllerProps.ActuatedValue,
2403 0 : state.dataLoopNodes->Node(SensedNode).Temp,
2404 0 : ControllerProps.SetPointValue,
2405 0 : ControllerProps.DeltaSensed,
2406 0 : ControllerProps.Offset,
2407 0 : ControllerProps.Mode,
2408 0 : static_cast<int>(IsConvergedFlag),
2409 0 : ControllerProps.NextActuatedValue);
2410 :
2411 : // X | Y | setpoint | DeltaSensed = Y - YRoot | Offset | Mode | IsConvergedFlag
2412 :
2413 : // Append trace for root finder
2414 0 : RootFinder::WriteRootFinderTrace(TraceFile, state.dataHVACControllers->RootFinders(ControlNum));
2415 :
2416 : // Finally skip line
2417 0 : print(TraceFile, "\n");
2418 :
2419 0 : } break;
2420 0 : case DataHVACControllers::ControllerOperation::End: {
2421 : // Masss flow rate
2422 : // Convergence analysis
2423 0 : print(TraceFile,
2424 : "{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{:.10T},{},{},{:.10T},",
2425 0 : state.dataLoopNodes->Node(SensedNode).MassFlowRate,
2426 0 : state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMinAvail,
2427 0 : state.dataLoopNodes->Node(ActuatedNode).MassFlowRateMaxAvail,
2428 0 : ControllerProps.ActuatedValue,
2429 0 : state.dataLoopNodes->Node(SensedNode).Temp,
2430 0 : ControllerProps.SetPointValue,
2431 0 : ControllerProps.DeltaSensed,
2432 0 : ControllerProps.Offset,
2433 0 : ControllerProps.Mode,
2434 0 : static_cast<int>(IsConvergedFlag),
2435 0 : ControllerProps.NextActuatedValue);
2436 :
2437 : // X | Y | setpoint | DeltaSensed = Y - YRoot | Offset | Mode | IsConvergedFlag
2438 :
2439 : // No trace available for root finder yet
2440 : // Skip call to WriteRootFinderTrace()
2441 :
2442 : // Finally skip line
2443 0 : print(TraceFile, "\n");
2444 :
2445 : // Skip an additional line to indicate end of current HVAC step
2446 0 : print(TraceFile, "\n");
2447 :
2448 0 : } break;
2449 0 : default: {
2450 : // Should never happen
2451 0 : ShowFatalError(
2452 0 : state, format("TraceIndividualController: Invalid Operation passed={}, Controller name={}", Operation, ControllerProps.ControllerName));
2453 0 : } break;
2454 : }
2455 :
2456 0 : TraceFile.flush();
2457 : }
2458 :
2459 0 : Real64 GetCurrentHVACTime(const EnergyPlusData &state)
2460 : {
2461 : // SUBROUTINE INFORMATION:
2462 : // AUTHOR Dimitri Curtil
2463 : // DATE WRITTEN November 2004
2464 : // MODIFIED na
2465 : // RE-ENGINEERED na
2466 :
2467 : // PURPOSE OF THIS FUNCTION:
2468 : // This routine returns the time in seconds at the end of the current HVAC step.
2469 :
2470 : // This is the correct formula that does not use MinutesPerSystemTimeStep, which would
2471 : // erronously truncate all sub-minute system time steps down to the closest full minute.
2472 : // Maybe later TimeStepZone, TimeStepSys and SysTimeElapsed could also be specified
2473 : // as real.
2474 : Real64 const CurrentHVACTime =
2475 0 : (state.dataGlobal->CurrentTime - state.dataGlobal->TimeStepZone) + state.dataHVACGlobal->SysTimeElapsed + state.dataHVACGlobal->TimeStepSys;
2476 0 : return CurrentHVACTime * Constant::SecInHour;
2477 : }
2478 :
2479 2838329 : Real64 GetPreviousHVACTime(const EnergyPlusData &state)
2480 : {
2481 : // SUBROUTINE INFORMATION:
2482 : // AUTHOR Dimitri Curtil
2483 : // DATE WRITTEN November 2004
2484 :
2485 : // PURPOSE OF THIS FUNCTION:
2486 : // This routine returns the time in seconds at the beginning of the current HVAC step.
2487 :
2488 : // This is the correct formula that does not use MinutesPerSystemTimeStep, which would
2489 : // erronously truncate all sub-minute system time steps down to the closest full minute.
2490 2838329 : Real64 const PreviousHVACTime = (state.dataGlobal->CurrentTime - state.dataGlobal->TimeStepZone) + state.dataHVACGlobal->SysTimeElapsed;
2491 2838329 : return PreviousHVACTime * Constant::SecInHour;
2492 : }
2493 :
2494 0 : std::string CreateHVACTimeString(const EnergyPlusData &state)
2495 : {
2496 :
2497 : // FUNCTION INFORMATION:
2498 : // AUTHOR Dimitri Curtil
2499 : // DATE WRITTEN January 2006
2500 :
2501 : // PURPOSE OF THIS FUNCTION:
2502 : // This function creates a string describing the current time stamp of the system
2503 : // time step.
2504 :
2505 0 : std::string Buffer = General::CreateTimeString(GetCurrentHVACTime(state));
2506 0 : return state.dataEnvrn->CurMnDy + ' ' + stripped(Buffer);
2507 0 : }
2508 :
2509 0 : std::string CreateHVACStepFullString(const EnergyPlusData &state)
2510 : {
2511 :
2512 : // FUNCTION INFORMATION:
2513 : // AUTHOR Dimitri Curtil
2514 : // DATE WRITTEN April 2006
2515 :
2516 : // PURPOSE OF THIS FUNCTION:
2517 : // This function creates a string describing the current HVAC step.
2518 : // It includes the environment name, the current day/month and the current
2519 : // time stamp for the system time step.
2520 : // It is used in error messages only.
2521 :
2522 0 : return state.dataEnvrn->EnvironmentName + ", " + MakeHVACTimeIntervalString(state);
2523 : }
2524 :
2525 0 : std::string MakeHVACTimeIntervalString(const EnergyPlusData &state)
2526 : {
2527 :
2528 : // FUNCTION INFORMATION:
2529 : // AUTHOR Dimitri Curtil
2530 : // DATE WRITTEN January 2006
2531 :
2532 : // PURPOSE OF THIS FUNCTION:
2533 : // This function creates a string describing the current time interval of the system
2534 : // time step.
2535 :
2536 0 : return format("{} - {}", General::CreateTimeString(GetPreviousHVACTime(state)), General::CreateTimeString(GetCurrentHVACTime(state)));
2537 : }
2538 :
2539 296 : void CheckControllerListOrder(EnergyPlusData &state)
2540 : {
2541 :
2542 : // SUBROUTINE INFORMATION:
2543 : // AUTHOR B. Griffith
2544 : // DATE WRITTEN Oct 10.
2545 :
2546 : // PURPOSE OF THIS SUBROUTINE:
2547 : // check that if multiple controllers on an air loop, that they aren't listed in a bad order
2548 : // CR 8253
2549 :
2550 : // METHODOLOGY EMPLOYED:
2551 : // setup data for sensed nodes and compare positions if on the same branch
2552 :
2553 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
2554 296 : Array2D_int ContrlSensedNodeNums; // array for storing sense node info
2555 :
2556 325 : for (int AirSysNum = 1; AirSysNum <= state.dataHVACGlobal->NumPrimaryAirSys; ++AirSysNum) {
2557 :
2558 29 : if (state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).NumControllers > 1) {
2559 : // first see how many are water coil controllers
2560 7 : int WaterCoilContrlCount = 0; // init
2561 21 : for (int ContrlNum = 1; ContrlNum <= state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).NumControllers; ++ContrlNum) {
2562 14 : if (Util::SameString(state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).ControllerType(ContrlNum), "CONTROLLER:WATERCOIL")) {
2563 14 : ++WaterCoilContrlCount;
2564 : }
2565 : }
2566 :
2567 7 : if (WaterCoilContrlCount > 1) {
2568 7 : ContrlSensedNodeNums.allocate(3, WaterCoilContrlCount);
2569 7 : ContrlSensedNodeNums = 0;
2570 7 : int SensedNodeIndex = 0;
2571 21 : for (int ContrlNum = 1; ContrlNum <= state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).NumControllers; ++ContrlNum) {
2572 14 : if (Util::SameString(state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).ControllerType(ContrlNum), "CONTROLLER:WATERCOIL")) {
2573 14 : ++SensedNodeIndex;
2574 14 : int foundControl = Util::FindItemInList(state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).ControllerName(ContrlNum),
2575 14 : state.dataHVACControllers->ControllerProps,
2576 : &ControllerPropsType::ControllerName);
2577 14 : if (foundControl > 0) {
2578 14 : ContrlSensedNodeNums(1, SensedNodeIndex) = state.dataHVACControllers->ControllerProps(foundControl).SensedNode;
2579 : }
2580 : }
2581 : }
2582 : }
2583 :
2584 : // fill branch index for sensed nodes
2585 7 : if (allocated(ContrlSensedNodeNums)) {
2586 26 : for (int BranchNum = 1; BranchNum <= state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).NumBranches; ++BranchNum) {
2587 57 : for (int SensedNodeIndex = 1; SensedNodeIndex <= WaterCoilContrlCount; ++SensedNodeIndex) {
2588 128 : for (int BranchNodeIndex = 1;
2589 128 : BranchNodeIndex <= state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).Branch(BranchNum).TotalNodes;
2590 : ++BranchNodeIndex) {
2591 90 : if (ContrlSensedNodeNums(1, SensedNodeIndex) ==
2592 90 : state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).Branch(BranchNum).NodeNum(BranchNodeIndex)) {
2593 14 : ContrlSensedNodeNums(2, SensedNodeIndex) = BranchNodeIndex;
2594 14 : ContrlSensedNodeNums(3, SensedNodeIndex) = BranchNum;
2595 : }
2596 : }
2597 : }
2598 : }
2599 : }
2600 : // check if flow order doesn't agree with controller order
2601 7 : if (allocated(ContrlSensedNodeNums)) {
2602 21 : for (int SensedNodeIndex = 1; SensedNodeIndex <= WaterCoilContrlCount; ++SensedNodeIndex) {
2603 14 : if (SensedNodeIndex == 1) continue;
2604 7 : if (ContrlSensedNodeNums(2, SensedNodeIndex) < ContrlSensedNodeNums(2, SensedNodeIndex - 1)) {
2605 : // now see if on the same branch
2606 1 : if (ContrlSensedNodeNums(3, SensedNodeIndex) == ContrlSensedNodeNums(3, SensedNodeIndex - 1)) {
2607 : // we have a flow order problem with water coil controllers
2608 0 : ShowSevereError(state, "CheckControllerListOrder: A water coil controller list has the wrong order");
2609 0 : ShowContinueError(state,
2610 0 : format("Check the AirLoopHVAC:ControllerList for the air loop called \"{}\"",
2611 0 : state.dataAirSystemsData->PrimaryAirSystems(AirSysNum).Name));
2612 0 : ShowContinueError(state,
2613 : "When there are multiple Controller:WaterCoil objects for the same air loop, they need to be "
2614 : "listed in the proper order.");
2615 0 : ShowContinueError(state,
2616 : "The controllers should be listed in natural flow order with those for upstream coils listed "
2617 : "before those for downstream coils.");
2618 0 : ShowContinueError(state, "The sensed nodes specified for the respective controllers should also reflect this order.");
2619 : }
2620 : }
2621 : }
2622 : }
2623 :
2624 7 : if (allocated(ContrlSensedNodeNums)) ContrlSensedNodeNums.deallocate();
2625 :
2626 : } // controllers > 1
2627 : }
2628 296 : }
2629 :
2630 863 : void CheckCoilWaterInletNode(EnergyPlusData &state,
2631 : int const WaterInletNodeNum, // input actuator node number
2632 : bool &NodeNotFound // true if matching actuator node not found
2633 : )
2634 : {
2635 :
2636 : // SUBROUTINE INFORMATION:
2637 : // AUTHOR Heejin Cho
2638 : // DATE WRITTEN November 2010
2639 :
2640 : // PURPOSE OF THIS FUNCTION:
2641 : // This subroutine checks that the water inlet node number is matched by
2642 : // the actuator node number of some water coil
2643 :
2644 863 : if (state.dataHVACControllers->GetControllerInputFlag) {
2645 22 : GetControllerInput(state);
2646 22 : state.dataHVACControllers->GetControllerInputFlag = false;
2647 : }
2648 :
2649 863 : NodeNotFound = true;
2650 7405 : for (auto const &ControllerProps : state.dataHVACControllers->ControllerProps) {
2651 6542 : if (ControllerProps.ActuatedNode == WaterInletNodeNum) {
2652 861 : NodeNotFound = false;
2653 : }
2654 : }
2655 863 : }
2656 :
2657 3318 : void GetControllerNameAndIndex(EnergyPlusData &state,
2658 : int const WaterInletNodeNum, // input actuator node number
2659 : std::string &ControllerName, // controller name used by water coil
2660 : int &ControllerIndex, // controller index used by water coil
2661 : bool &ErrorsFound // true if matching actuator node not found
2662 : )
2663 : {
2664 :
2665 : // SUBROUTINE INFORMATION:
2666 : // AUTHOR Richard Raustad
2667 : // DATE WRITTEN June 2017
2668 :
2669 : // PURPOSE OF THIS FUNCTION:
2670 : // This subroutine checks that the water inlet node number is matched by
2671 : // the actuator node number of some water coil and passed back controller name and index
2672 :
2673 3318 : if (state.dataHVACControllers->GetControllerInputFlag) {
2674 301 : GetControllerInput(state);
2675 301 : state.dataHVACControllers->GetControllerInputFlag = false;
2676 : }
2677 :
2678 3318 : ControllerName = " ";
2679 3318 : ControllerIndex = 0;
2680 25248 : for (int ControlNum = 1; ControlNum <= state.dataHVACControllers->NumControllers; ++ControlNum) {
2681 22801 : if (state.dataHVACControllers->ControllerProps(ControlNum).ActuatedNode == WaterInletNodeNum) {
2682 871 : ControllerIndex = ControlNum;
2683 871 : ControllerName = state.dataHVACControllers->ControllerProps(ControlNum).ControllerName;
2684 871 : break;
2685 : }
2686 : }
2687 :
2688 3318 : if (ControllerIndex == 0) {
2689 2447 : ErrorsFound = true;
2690 : }
2691 3318 : }
2692 :
2693 58 : void GetControllerActuatorNodeNum(EnergyPlusData &state,
2694 : std::string const &ControllerName, // name of coil controller
2695 : int &WaterInletNodeNum, // input actuator node number
2696 : bool &NodeNotFound // true if matching actuator node not found
2697 : )
2698 : {
2699 :
2700 : // SUBROUTINE INFORMATION:
2701 : // AUTHOR Richard Raustad, FSEC
2702 : // DATE WRITTEN September 2013
2703 :
2704 : // PURPOSE OF THIS FUNCTION:
2705 : // This subroutine finds the controllers actuator node number
2706 :
2707 58 : if (state.dataHVACControllers->GetControllerInputFlag) {
2708 2 : GetControllerInput(state);
2709 2 : state.dataHVACControllers->GetControllerInputFlag = false;
2710 : }
2711 :
2712 58 : NodeNotFound = true;
2713 58 : int ControlNum = Util::FindItemInList(ControllerName, state.dataHVACControllers->ControllerProps, &ControllerPropsType::ControllerName);
2714 58 : if (ControlNum > 0 && ControlNum <= state.dataHVACControllers->NumControllers) {
2715 58 : WaterInletNodeNum = state.dataHVACControllers->ControllerProps(ControlNum).ActuatedNode;
2716 58 : NodeNotFound = false;
2717 : }
2718 58 : }
2719 :
2720 864 : int GetControllerIndex(EnergyPlusData &state, std::string const &ControllerName // name of coil controller
2721 : )
2722 : {
2723 :
2724 : // SUBROUTINE INFORMATION:
2725 : // AUTHOR Richard Raustad, FSEC
2726 : // DATE WRITTEN January 2018
2727 :
2728 : // This subroutine finds the controllers actuator node number
2729 :
2730 864 : if (state.dataHVACControllers->GetControllerInputFlag) {
2731 0 : GetControllerInput(state);
2732 0 : state.dataHVACControllers->GetControllerInputFlag = false;
2733 : }
2734 :
2735 864 : int ControllerIndex = Util::FindItemInList(ControllerName, state.dataHVACControllers->ControllerProps, &ControllerPropsType::ControllerName);
2736 864 : if (ControllerIndex == 0) {
2737 0 : ShowFatalError(state,
2738 0 : format("ManageControllers: Invalid controller={}. The only valid controller type for an AirLoopHVAC is Controller:WaterCoil.",
2739 : ControllerName));
2740 : }
2741 :
2742 864 : return ControllerIndex;
2743 : }
2744 :
2745 : } // namespace EnergyPlus::HVACControllers
|