Line data Source code
1 : // EnergyPlus, Copyright (c) 1996-2025, The Board of Trustees of the University of Illinois,
2 : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
3 : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
4 : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
5 : // contributors. All rights reserved.
6 : //
7 : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
8 : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
9 : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
10 : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
11 : // derivative works, and perform publicly and display publicly, and to permit others to do so.
12 : //
13 : // Redistribution and use in source and binary forms, with or without modification, are permitted
14 : // provided that the following conditions are met:
15 : //
16 : // (1) Redistributions of source code must retain the above copyright notice, this list of
17 : // conditions and the following disclaimer.
18 : //
19 : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
20 : // conditions and the following disclaimer in the documentation and/or other materials
21 : // provided with the distribution.
22 : //
23 : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
24 : // the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
25 : // used to endorse or promote products derived from this software without specific prior
26 : // written permission.
27 : //
28 : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
29 : // without changes from the version obtained under this License, or (ii) Licensee makes a
30 : // reference solely to the software portion of its product, Licensee must refer to the
31 : // software as "EnergyPlus version X" software, where "X" is the version number Licensee
32 : // obtained under this License and may not use a different name for the software. Except as
33 : // specifically required in this Section (4), Licensee shall not use in a company name, a
34 : // product name, in advertising, publicity, or other promotional activities any name, trade
35 : // name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
36 : // similar designation, without the U.S. Department of Energy's prior written consent.
37 : //
38 : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
39 : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
40 : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
41 : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
43 : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44 : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
45 : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46 : // POSSIBILITY OF SUCH DAMAGE.
47 :
48 : // C++ Headers
49 : #include <cmath>
50 : #include <string>
51 :
52 : // ObjexxFCL Headers
53 : #include <ObjexxFCL/Array.functions.hh>
54 : #include <ObjexxFCL/Fmath.hh>
55 :
56 : // EnergyPlus Headers
57 : #include <EnergyPlus/Autosizing/Base.hh>
58 : #include <EnergyPlus/BranchNodeConnections.hh>
59 : #include <EnergyPlus/CurveManager.hh>
60 : #include <EnergyPlus/Data/EnergyPlusData.hh>
61 : #include <EnergyPlus/DataContaminantBalance.hh>
62 : #include <EnergyPlus/DataDefineEquip.hh>
63 : #include <EnergyPlus/DataEnvironment.hh>
64 : #include <EnergyPlus/DataHVACGlobals.hh>
65 : #include <EnergyPlus/DataHeatBalance.hh>
66 : #include <EnergyPlus/DataIPShortCuts.hh>
67 : #include <EnergyPlus/DataLoopNode.hh>
68 : #include <EnergyPlus/DataSizing.hh>
69 : #include <EnergyPlus/DataZoneControls.hh>
70 : #include <EnergyPlus/DataZoneEnergyDemands.hh>
71 : #include <EnergyPlus/DataZoneEquipment.hh>
72 : #include <EnergyPlus/DesiccantDehumidifiers.hh>
73 : #include <EnergyPlus/EMSManager.hh>
74 : #include <EnergyPlus/EvaporativeCoolers.hh>
75 : #include <EnergyPlus/Fans.hh>
76 : #include <EnergyPlus/FaultsManager.hh>
77 : #include <EnergyPlus/General.hh>
78 : #include <EnergyPlus/GeneralRoutines.hh>
79 : #include <EnergyPlus/GlobalNames.hh>
80 : #include <EnergyPlus/HVACControllers.hh>
81 : #include <EnergyPlus/HVACDXHeatPumpSystem.hh>
82 : #include <EnergyPlus/HVACHXAssistedCoolingCoil.hh>
83 : #include <EnergyPlus/HVACVariableRefrigerantFlow.hh>
84 : #include <EnergyPlus/HeatRecovery.hh>
85 : #include <EnergyPlus/HeatingCoils.hh>
86 : #include <EnergyPlus/Humidifiers.hh>
87 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
88 : #include <EnergyPlus/MixedAir.hh>
89 : #include <EnergyPlus/NodeInputManager.hh>
90 : #include <EnergyPlus/OutAirNodeManager.hh>
91 : #include <EnergyPlus/OutputProcessor.hh>
92 : #include <EnergyPlus/OutputReportPredefined.hh>
93 : #include <EnergyPlus/PhotovoltaicThermalCollectors.hh>
94 : #include <EnergyPlus/Psychrometrics.hh>
95 : #include <EnergyPlus/ScheduleManager.hh>
96 : #include <EnergyPlus/SetPointManager.hh>
97 : #include <EnergyPlus/SteamCoils.hh>
98 : #include <EnergyPlus/TranspiredCollector.hh>
99 : #include <EnergyPlus/UnitarySystem.hh>
100 : #include <EnergyPlus/UserDefinedComponents.hh>
101 : #include <EnergyPlus/UtilityRoutines.hh>
102 : #include <EnergyPlus/WaterCoils.hh>
103 :
104 : namespace EnergyPlus::MixedAir {
105 :
106 : // Module containing the routines dealing with the mixed air portion
107 : // of the HVAC air loop.
108 :
109 : // MODULE INFORMATION:
110 : // AUTHOR Fred Buhl
111 : // DATE WRITTEN October 1998
112 : // MODIFIED Shirey/Raustad FSEC, June/Aug 2003, Jan 2004
113 : // Lawrie, March 2006 - Module order (per template)
114 : // Craig Wray 22Aug2010 - Added Fan ComponentModel
115 : // Chandan Sharma, FSEC, 25Aug 2011 - Added ProportionalControl
116 : // to enhance CO2 based DCV control
117 : // Feb 2013 Bereket Nigusse, FSEC
118 : // Added DX Coil Model For 100% OA systems
119 : // RE-ENGINEERED na
120 :
121 : // PURPOSE OF THIS MODULE:
122 : // To encapsulate the data and algorithms required to
123 : // simulate the mixed air portion of the EPlus air loop.
124 :
125 : // METHODOLOGY EMPLOYED:
126 : // An algorithmic controller will be employed - there is no attempt to
127 : // simulate real controllers for the economizer. The mixed air controller
128 : // will sense various node conditions and set some node flow rates. Mixed
129 : // air components will operate with predetermined flow rates.
130 :
131 : // Using/Aliasing
132 : using namespace DataLoopNode;
133 : using namespace DataAirLoop;
134 : using namespace DataEnvironment;
135 : using namespace DataSizing;
136 : using namespace FaultsManager;
137 :
138 : constexpr std::array<std::string_view, static_cast<int>(ControllerKind::Num)> ControllerKindNamesUC{"CONTROLLER:WATERCOIL", "CONTROLLER:OUTDOORAIR"};
139 :
140 : constexpr std::array<std::string_view, static_cast<int>(MixedAirControllerType::Num)> MixedAirControllerTypeNames{
141 : "Controller:OutdoorAir", "ZoneHVAC:EnergyRecoveryVentilator:Controller"};
142 :
143 : constexpr std::array<std::string_view, static_cast<int>(CMO::Num)> CurrentModuleObjects{"None",
144 : "AirLoopHVAC:OutdoorAirSystem",
145 : "AirLoopHVAC:OutdoorAirSystem:EquipmentList",
146 : "AirLoopHVAC:ControllerList",
147 : "AvailabilityManagerAssignmentList",
148 : "Controller:OutdoorAir",
149 : "ZoneHVAC:EnergyRecoveryVentilator:Controller",
150 : "Controller:MechanicalVentilation",
151 : "OutdoorAir:Mixer"};
152 :
153 : constexpr std::array<std::string_view, static_cast<int>(DataSizing::SysOAMethod::Num)> SOAMNamesUC{"ZONESUM",
154 : "STANDARD62.1VENTILATIONRATEPROCEDURE",
155 : "INDOORAIRQUALITYPROCEDURE",
156 : "PROPORTIONALCONTROLBASEDONOCCUPANCYSCHEDULE",
157 : "INDOORAIRQUALITYPROCEDUREGENERICCONTAMINANT",
158 : "INDOORAIRQUALITYPROCEDURECOMBINED",
159 : "PROPORTIONALCONTROLBASEDONDESIGNOCCUPANCY",
160 : "PROPORTIONALCONTROLBASEDONDESIGNOARATE",
161 : "STANDARD62.1SIMPLIFIEDPROCEDURE",
162 : "STANDARD62.1VENTILATIONRATEPROCEDUREWITHLIMIT"};
163 :
164 : constexpr std::array<std::string_view, static_cast<int>(SimAirServingZones::CompType::Num)> CompTypeNamesUC{
165 : "OUTDOORAIR:MIXER",
166 : "FAN:CONSTANTVOLUME",
167 : "FAN:VARIABLEVOLUME",
168 : "COIL:COOLING:WATER",
169 : "COIL:HEATING:WATER",
170 : "COIL:HEATING:STEAM",
171 : "COIL:COOLING:WATER:DETAILEDGEOMETRY",
172 : "COIL:HEATING:ELECTRIC",
173 : "COIL:HEATING:FUEL",
174 : "COILSYSTEM:COOLING:WATER:HEATEXCHANGERASSISTED",
175 : "COIL:HEATING:DESUPERHEATER",
176 : "COILSYSTEM:COOLING:DX",
177 : "HEATEXCHANGER:AIRTOAIR:FLATPLATE",
178 : "DEHUMIDIFIER:DESICCANT:NOFANS",
179 : "SOLARCOLLECTOR:UNGLAZEDTRANSPIRED",
180 : "EVAPORATIVECOOLER:DIRECT:CELDEKPAD",
181 : "AIRLOOPHVAC:UNITARY:FURNACE:HEATONLY",
182 : "AIRLOOPHVAC:UNITARY:FURNACE:HEATCOOL",
183 : "HUMIDIFIER:STEAM:ELECTRIC",
184 : "DUCT",
185 : "AIRLOOPHVAC:UNITARYHEATCOOL:VAVCHANGEOVERBYPASS",
186 : "AIRLOOPHVAC:UNITARYHEATPUMP:AIRTOAIR:MULTISPEED",
187 : "FAN:COMPONENTMODEL",
188 : "COILSYSTEM:HEATING:DX",
189 : "COIL:USERDEFINED",
190 : "FAN:SYSTEMMODEL",
191 : "AIRLOOPHVAC:UNITARYSYSTEM",
192 : "ZONEHVAC:TERMINALUNIT:VARIABLEREFRIGERANTFLOW",
193 : "SOLARCOLLECTOR:FLATPLATE:PHOTOVOLTAICTHERMAL",
194 : "COILSYSTEM:COOLING:WATER"};
195 :
196 : static constexpr std::array<std::string_view, static_cast<int>(DataSizing::SysOAMethod::Num)> printSysOAMethod{
197 : "ZoneSum,",
198 : "Standard62.1VentilationRateProcedure,",
199 : "IndoorAirQualityProcedure,",
200 : "ProportionalControlBasedOnOccupancySchedule,",
201 : "IndoorAirQualityGenericContaminant,",
202 : "IndoorAirQualityProcedureCombined,",
203 : "ProportionalControlBasedOnDesignOccupancy,",
204 : "ProportionalControlBasedOnDesignOARate,",
205 : "Standard62.1SimplifiedProcedure,",
206 : "Standard62.1VentilationRateProcedureWithLimit,"};
207 :
208 0 : Real64 OAGetFlowRate(EnergyPlusData &state, int OAPtr)
209 : {
210 0 : Real64 FlowRate(0);
211 0 : if ((OAPtr > 0) && (OAPtr <= state.dataMixedAir->NumOAControllers) && (state.dataEnvrn->StdRhoAir != 0)) {
212 0 : FlowRate = state.dataMixedAir->OAController(OAPtr).OAMassFlow / state.dataEnvrn->StdRhoAir;
213 : }
214 0 : return FlowRate;
215 : }
216 0 : Real64 OAGetMinFlowRate(EnergyPlusData &state, int OAPtr)
217 : {
218 0 : Real64 MinFlowRate(0);
219 0 : if ((OAPtr > 0) && (OAPtr <= state.dataMixedAir->NumOAControllers)) {
220 0 : MinFlowRate = state.dataMixedAir->OAController(OAPtr).MinOA;
221 : }
222 0 : return MinFlowRate;
223 : }
224 0 : void OASetDemandManagerVentilationState(EnergyPlusData &state, int OAPtr, bool aState)
225 : {
226 0 : if ((OAPtr > 0) && (OAPtr <= state.dataMixedAir->NumOAControllers)) {
227 0 : state.dataMixedAir->OAController(OAPtr).ManageDemand = aState;
228 : }
229 0 : }
230 0 : void OASetDemandManagerVentilationFlow(EnergyPlusData &state, int OAPtr, Real64 aFlow)
231 : {
232 0 : if ((OAPtr > 0) && (OAPtr <= state.dataMixedAir->NumOAControllers)) {
233 0 : state.dataMixedAir->OAController(OAPtr).DemandLimitFlowRate = aFlow * state.dataEnvrn->StdRhoAir;
234 : }
235 0 : }
236 32 : int GetOAController(EnergyPlusData &state, std::string const &OAName)
237 : {
238 32 : int CurrentOAController(0);
239 32 : for (int i = 1; i <= state.dataMixedAir->NumOAControllers; i++) {
240 32 : if (OAName == state.dataMixedAir->OAController(i).Name) {
241 32 : CurrentOAController = i;
242 32 : break;
243 : }
244 : }
245 32 : return CurrentOAController;
246 : }
247 :
248 92985 : void ManageOutsideAirSystem(EnergyPlusData &state, std::string const &OASysName, bool const FirstHVACIteration, int const AirLoopNum, int &OASysNum)
249 : {
250 :
251 : // SUBROUTINE INFORMATION:
252 : // AUTHOR Fred Buhl
253 : // DATE WRITTEN Oct 1998
254 :
255 : // PURPOSE OF THIS SUBROUTINE
256 : // Manage the outside air system
257 :
258 92985 : if (state.dataMixedAir->GetOASysInputFlag) {
259 1 : GetOutsideAirSysInputs(state);
260 1 : state.dataMixedAir->GetOASysInputFlag = false;
261 : }
262 :
263 92985 : if (OASysNum == 0) {
264 23 : OASysNum = Util::FindItemInList(OASysName, state.dataAirLoop->OutsideAirSys);
265 23 : if (OASysNum == 0) {
266 0 : ShowFatalError(state, format("ManageOutsideAirSystem: AirLoopHVAC:OutdoorAirSystem not found={}", OASysName));
267 : }
268 : }
269 :
270 92985 : InitOutsideAirSys(state, OASysNum, AirLoopNum);
271 :
272 92985 : SimOutsideAirSys(state, OASysNum, FirstHVACIteration, AirLoopNum);
273 92985 : }
274 :
275 93000 : void SimOASysComponents(EnergyPlusData &state, int const OASysNum, bool const FirstHVACIteration, int const AirLoopNum)
276 : {
277 93000 : auto &CompType = state.dataMixedAir->CompType;
278 93000 : auto &CompName = state.dataMixedAir->CompName;
279 93000 : bool ReSim(false);
280 93000 : bool Sim(true);
281 93000 : bool OAHeatCoil(false);
282 93000 : bool OACoolCoil(false);
283 93000 : bool OAHX(false);
284 :
285 221540 : for (int CompNum = 1; CompNum <= state.dataAirLoop->OutsideAirSys(OASysNum).NumComponents; ++CompNum) {
286 128540 : CompType = state.dataAirLoop->OutsideAirSys(OASysNum).ComponentType(CompNum);
287 128540 : CompName = state.dataAirLoop->OutsideAirSys(OASysNum).ComponentName(CompNum);
288 514160 : SimOAComponent(state,
289 : CompType,
290 : CompName,
291 128540 : state.dataAirLoop->OutsideAirSys(OASysNum).ComponentTypeEnum(CompNum),
292 : FirstHVACIteration,
293 128540 : state.dataAirLoop->OutsideAirSys(OASysNum).ComponentIndex(CompNum),
294 : AirLoopNum,
295 : Sim,
296 : OASysNum,
297 : OAHeatCoil,
298 : OACoolCoil,
299 : OAHX);
300 128540 : if (OAHX) ReSim = true;
301 : }
302 : // if there were heat exchangers and/or desiccant wheel in the OA path, need to simulate again in reverse
303 : // order to propagate the air flow and conditions out the relief air path to the relief air exit node
304 93000 : if (ReSim) {
305 7105 : for (int CompNum = state.dataAirLoop->OutsideAirSys(OASysNum).NumComponents - 1; CompNum >= 1; --CompNum) {
306 3557 : CompType = state.dataAirLoop->OutsideAirSys(OASysNum).ComponentType(CompNum);
307 3557 : CompName = state.dataAirLoop->OutsideAirSys(OASysNum).ComponentName(CompNum);
308 14228 : SimOAComponent(state,
309 : CompType,
310 : CompName,
311 3557 : state.dataAirLoop->OutsideAirSys(OASysNum).ComponentTypeEnum(CompNum),
312 : FirstHVACIteration,
313 3557 : state.dataAirLoop->OutsideAirSys(OASysNum).ComponentIndex(CompNum),
314 : AirLoopNum,
315 : Sim,
316 : OASysNum,
317 : OAHeatCoil,
318 : OACoolCoil,
319 : OAHX);
320 : }
321 : // now simulate again propagate current temps back through OA system
322 10653 : for (int CompNum = 1; CompNum <= state.dataAirLoop->OutsideAirSys(OASysNum).NumComponents; ++CompNum) {
323 7105 : CompType = state.dataAirLoop->OutsideAirSys(OASysNum).ComponentType(CompNum);
324 7105 : CompName = state.dataAirLoop->OutsideAirSys(OASysNum).ComponentName(CompNum);
325 28420 : SimOAComponent(state,
326 : CompType,
327 : CompName,
328 7105 : state.dataAirLoop->OutsideAirSys(OASysNum).ComponentTypeEnum(CompNum),
329 : FirstHVACIteration,
330 7105 : state.dataAirLoop->OutsideAirSys(OASysNum).ComponentIndex(CompNum),
331 : AirLoopNum,
332 : Sim,
333 : OASysNum,
334 : OAHeatCoil,
335 : OACoolCoil,
336 : OAHX);
337 : }
338 : }
339 93000 : }
340 :
341 92985 : void SimOutsideAirSys(EnergyPlusData &state, int const OASysNum, bool const FirstHVACIteration, int const AirLoopNum)
342 : {
343 :
344 : // SUBROUTINE INFORMATION:
345 : // AUTHOR Fred Buhl
346 : // DATE WRITTEN Oct 1998
347 :
348 : // PURPOSE OF THIS SUBROUTINE
349 : // Simulate the controllers and components in the outside air system.
350 :
351 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
352 92985 : state.dataSize->CurOASysNum = OASysNum;
353 92985 : auto &CurrentOASystem(state.dataAirLoop->OutsideAirSys(OASysNum));
354 92985 : if (state.dataAirLoop->OutsideAirSys(OASysNum).AirLoopDOASNum == -1) {
355 78417 : SimOAController(state, CurrentOASystem.OAControllerName, CurrentOASystem.OAControllerIndex, FirstHVACIteration, AirLoopNum);
356 : }
357 92985 : SimOASysComponents(state, OASysNum, FirstHVACIteration, AirLoopNum);
358 :
359 92985 : if (state.dataMixedAir->MyOneTimeErrorFlag(OASysNum)) {
360 29 : bool FatalErrorFlag(false);
361 29 : if (CurrentOASystem.NumControllers - CurrentOASystem.NumSimpleControllers > 1) {
362 0 : ShowWarningError(
363 : state,
364 0 : format("AirLoopHVAC:OutdoorAirSystem {} has more than 1 outside air controller; only the 1st will be used", CurrentOASystem.Name));
365 : }
366 70 : for (int CompNum = 1; CompNum <= CurrentOASystem.NumComponents; ++CompNum) {
367 41 : auto &CompType = CurrentOASystem.ComponentType(CompNum);
368 41 : auto &CompName = CurrentOASystem.ComponentName(CompNum);
369 41 : if (Util::SameString(CompType, "OutdoorAir:Mixer")) {
370 25 : int OAMixerNum = Util::FindItemInList(CompName, state.dataMixedAir->OAMixer);
371 25 : int OAControllerNum = CurrentOASystem.OAControllerIndex;
372 25 : if (state.dataMixedAir->OAController(OAControllerNum).MixNode != state.dataMixedAir->OAMixer(OAMixerNum).MixNode) {
373 0 : ShowSevereError(
374 0 : state, format("The mixed air node of Controller:OutdoorAir=\"{}\"", state.dataMixedAir->OAController(OAControllerNum).Name));
375 0 : ShowContinueError(state,
376 0 : format("should be the same node as the mixed air node of OutdoorAir:Mixer=\"{}\".",
377 0 : state.dataMixedAir->OAMixer(OAMixerNum).Name));
378 0 : ShowContinueError(state,
379 0 : format("Controller:OutdoorAir mixed air node=\"{}\".",
380 0 : state.dataLoopNodes->NodeID(state.dataMixedAir->OAController(OAControllerNum).MixNode)));
381 0 : ShowContinueError(state,
382 0 : format("OutdoorAir:Mixer mixed air node=\"{}\".",
383 0 : state.dataLoopNodes->NodeID(state.dataMixedAir->OAMixer(OAMixerNum).MixNode)));
384 0 : FatalErrorFlag = true;
385 : }
386 25 : if (state.dataMixedAir->OAController(OAControllerNum).RelNode != state.dataMixedAir->OAMixer(OAMixerNum).RelNode) {
387 0 : ShowSevereError(
388 0 : state, format("The relief air node of Controller:OutdoorAir=\"{}\"", state.dataMixedAir->OAController(OAControllerNum).Name));
389 0 : ShowContinueError(state,
390 0 : format("should be the same node as the relief air node of OutdoorAir:Mixer=\"{}\".",
391 0 : state.dataMixedAir->OAMixer(OAMixerNum).Name));
392 0 : ShowContinueError(state,
393 0 : format("Controller:OutdoorAir relief air node=\"{}\".",
394 0 : state.dataLoopNodes->NodeID(state.dataMixedAir->OAController(OAControllerNum).RelNode)));
395 0 : ShowContinueError(state,
396 0 : format("OutdoorAir:Mixer relief air node=\"{}\".",
397 0 : state.dataLoopNodes->NodeID(state.dataMixedAir->OAMixer(OAMixerNum).RelNode)));
398 0 : FatalErrorFlag = true;
399 : }
400 25 : if (state.dataMixedAir->OAController(OAControllerNum).RetNode != state.dataMixedAir->OAMixer(OAMixerNum).RetNode) {
401 0 : ShowSevereError(
402 0 : state, format("The return air node of Controller:OutdoorAir=\"{}\"", state.dataMixedAir->OAController(OAControllerNum).Name));
403 0 : ShowContinueError(state,
404 0 : format("should be the same node as the return air node of OutdoorAir:Mixer=\"{}\".",
405 0 : state.dataMixedAir->OAMixer(OAMixerNum).Name));
406 0 : ShowContinueError(state,
407 0 : format("Controller:OutdoorAir return air node=\"{}\".",
408 0 : state.dataLoopNodes->NodeID(state.dataMixedAir->OAController(OAControllerNum).RetNode)));
409 0 : ShowContinueError(state,
410 0 : format("OutdoorAir:Mixer return air node=\"{}\".",
411 0 : state.dataLoopNodes->NodeID(state.dataMixedAir->OAMixer(OAMixerNum).RetNode)));
412 0 : FatalErrorFlag = true;
413 : }
414 : }
415 : }
416 29 : state.dataMixedAir->MyOneTimeErrorFlag(OASysNum) = false;
417 29 : if (FatalErrorFlag) ShowFatalError(state, "Previous severe error(s) cause program termination");
418 : }
419 :
420 92985 : state.dataSize->CurOASysNum = 0;
421 92985 : if (state.dataAirLoop->OutsideAirSys(OASysNum).AirLoopDOASNum == -1) {
422 78417 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).OASysComponentsSimulated = true;
423 : }
424 92985 : }
425 :
426 139316 : void SimOAComponent(EnergyPlusData &state,
427 : std::string const &CompType, // the component type
428 : std::string const &CompName, // the component Name
429 : SimAirServingZones::CompType const CompTypeNum, // Component Type -- Integerized for this module
430 : bool const FirstHVACIteration,
431 : int &CompIndex,
432 : int const AirLoopNum, // air loop index for economizer lockout coordination
433 : bool const Sim, // if TRUE, simulate component; if FALSE, just set the coil existence flags
434 : int const OASysNum, // index to outside air system
435 : bool &OAHeatingCoil, // TRUE indicates a heating coil has been found
436 : bool &OACoolingCoil, // TRUE indicates a cooling coil has been found
437 : bool &OAHX) // TRUE indicates a heat exchanger has been found
438 : {
439 :
440 : // SUBROUTINE INFORMATION
441 : // AUTHOR: Russ Taylor, Dan Fisher, Fred Buhl
442 : // DATE WRITTEN: Oct 1997
443 : // MODIFIED: Dec 1997 Fred Buhl, D Shirey Feb/Sept 2003
444 : // Nov 2004 M. J. Witte, GARD Analytics, Inc.
445 : // Add DXSystem:AirLoop as valid OA system equipment
446 : // Work supported by ASHRAE research project 1254-RP
447 :
448 : // PURPOSE OF THIS SUBROUTINE:
449 : // Calls the individual air loop component simulation routines
450 :
451 : // SUBROUTINE LOCAL VARIABLE DEFINITIONS
452 139316 : OAHeatingCoil = false;
453 139316 : OACoolingCoil = false;
454 139316 : OAHX = false;
455 : HVAC::FanOp fanOp;
456 139316 : Real64 sensOut = 0.0;
457 139316 : int constexpr zoneOAUnitNum = -1;
458 139316 : Real64 constexpr OAUCoilOutTemp = 0.0;
459 139316 : bool constexpr ZoneEquipFlag = false;
460 139316 : bool HeatingActive = false; // why isn't this returning that a coil is active?
461 139316 : bool CoolingActive = false;
462 :
463 139316 : switch (CompTypeNum) {
464 82073 : case SimAirServingZones::CompType::OAMixer_Num: { // OutdoorAir:Mixer
465 82073 : if (Sim) {
466 81977 : SimOAMixer(state, CompName, CompIndex);
467 : }
468 82073 : break;
469 : }
470 14565 : case SimAirServingZones::CompType::Fan_Simple_CV: // Fan:ConstantVolume
471 : case SimAirServingZones::CompType::Fan_Simple_VAV: // Fan:VariableVolume
472 : case SimAirServingZones::CompType::Fan_System_Object: // Fan:SystemModel
473 : case SimAirServingZones::CompType::Fan_ComponentModel: { // Fan:ComponentModel
474 14565 : if (Sim) {
475 14565 : if (CompIndex == 0) { // TODO: get rid of this stuff
476 0 : CompIndex = Fans::GetFanIndex(state, CompName);
477 0 : assert(CompIndex > 0);
478 : }
479 :
480 14565 : state.dataFans->fans(CompIndex)->simulate(state, FirstHVACIteration);
481 : }
482 14565 : } break;
483 2 : case SimAirServingZones::CompType::WaterCoil_Cooling: { // Coil:Cooling:Water
484 2 : if (Sim) {
485 : // get water coil and controller data if not called previously
486 0 : if (CompIndex == 0) WaterCoils::SimulateWaterCoilComponents(state, CompName, FirstHVACIteration, CompIndex);
487 : // iterate on OA sys controller and water coil at the same time
488 0 : if (!state.dataWaterCoils->WaterCoil(CompIndex).heatRecoveryCoil) {
489 0 : SimAirServingZones::SolveWaterCoilController(state,
490 : FirstHVACIteration,
491 : AirLoopNum,
492 : CompName,
493 : CompIndex,
494 0 : state.dataWaterCoils->WaterCoil(CompIndex).ControllerName,
495 0 : state.dataWaterCoils->WaterCoil(CompIndex).ControllerIndex,
496 : false);
497 : // set flag to tell HVAC controller it will be simulated only in SolveWaterCoilController()
498 0 : state.dataHVACControllers->ControllerProps(state.dataWaterCoils->WaterCoil(CompIndex).ControllerIndex).BypassControllerCalc = true;
499 : } else {
500 0 : WaterCoils::SimulateWaterCoilComponents(state, CompName, FirstHVACIteration, CompIndex);
501 : }
502 : } else {
503 : // This is not working as intended ... don't want to include the HR coil in sizing.
504 : // But if the water coil is called to get this index, then the controller is called to set the
505 : // controller index and the simulation sizes the controller before the cooling coil.
506 : // Pushing this aspect forward to a follow up issue where the
507 : // controller index call is moved out of water coils getInput.
508 : // if (CompIndex == 0) {
509 : // bool errFound = false;
510 : // CompIndex = WaterCoils::GetWaterCoilIndex(state, CompType, CompName, errFound);
511 : // if (errFound) ShowFatalError(state, "SimOAComponent: Program terminates for preceding reason.");
512 : // }
513 : // if (!state.dataWaterCoils->WaterCoil(CompIndex).heatRecoveryCoil) OACoolingCoil = true;
514 : // should not include heat recovery coils in sizing since heat transfer at peak cooling is minimal.
515 2 : OACoolingCoil = true;
516 : }
517 2 : } break;
518 2848 : case SimAirServingZones::CompType::WaterCoil_SimpleHeat: { // Coil:Heating:Water
519 2848 : if (Sim) {
520 : // get water coil and controller data if not called previously
521 2842 : if (CompIndex == 0) WaterCoils::SimulateWaterCoilComponents(state, CompName, FirstHVACIteration, CompIndex);
522 : // iterate on OA sys controller and water coil at the same time
523 5684 : SimAirServingZones::SolveWaterCoilController(state,
524 : FirstHVACIteration,
525 : AirLoopNum,
526 : CompName,
527 : CompIndex,
528 2842 : state.dataWaterCoils->WaterCoil(CompIndex).ControllerName,
529 2842 : state.dataWaterCoils->WaterCoil(CompIndex).ControllerIndex,
530 : false);
531 : // set flag to tell HVAC controller it will be simulated only in SolveWaterCoilController()
532 2842 : state.dataHVACControllers->ControllerProps(state.dataWaterCoils->WaterCoil(CompIndex).ControllerIndex).BypassControllerCalc = true;
533 : }
534 2848 : OAHeatingCoil = true;
535 2848 : } break;
536 0 : case SimAirServingZones::CompType::SteamCoil_AirHeat: { // Coil:Heating:Steam
537 0 : if (Sim) {
538 0 : SteamCoils::SimulateSteamCoilComponents(state, CompName, FirstHVACIteration, CompIndex, 0.0);
539 : }
540 0 : OAHeatingCoil = true;
541 0 : } break;
542 0 : case SimAirServingZones::CompType::WaterCoil_DetailedCool: { // Coil:Cooling:Water:DetailedGeometry
543 0 : if (Sim) {
544 : // get water coil and controller data if not called previously
545 0 : if (CompIndex == 0) WaterCoils::SimulateWaterCoilComponents(state, CompName, FirstHVACIteration, CompIndex);
546 : // iterate on OA sys controller and water coil at the same time
547 0 : SimAirServingZones::SolveWaterCoilController(state,
548 : FirstHVACIteration,
549 : AirLoopNum,
550 : CompName,
551 : CompIndex,
552 0 : state.dataWaterCoils->WaterCoil(CompIndex).ControllerName,
553 0 : state.dataWaterCoils->WaterCoil(CompIndex).ControllerIndex,
554 : false);
555 : // set flag to tell HVAC controller it will be simulated only in SolveWaterCoilController()
556 0 : state.dataHVACControllers->ControllerProps(state.dataWaterCoils->WaterCoil(CompIndex).ControllerIndex).BypassControllerCalc = true;
557 : }
558 0 : OACoolingCoil = true;
559 0 : } break;
560 14595 : case SimAirServingZones::CompType::Coil_ElectricHeat: // Coil:Heating:Electric
561 : case SimAirServingZones::CompType::Coil_GasHeat: { // Coil:Heating:Fuel
562 14595 : if (Sim) {
563 : // stand-alone coils are temperature controlled (do not pass QCoilReq in argument list, QCoilReq overrides temp SP)
564 14595 : HeatingCoils::SimulateHeatingCoilComponents(state, CompName, FirstHVACIteration, _, CompIndex);
565 : }
566 14595 : OAHeatingCoil = true;
567 14595 : } break;
568 0 : case SimAirServingZones::CompType::WaterCoil_CoolingHXAsst: { // CoilSystem:Cooling:Water:HeatExchangerAssisted
569 0 : if (Sim) {
570 : // get water coil and controller data if not called previously
571 0 : if (CompIndex == 0)
572 0 : HVACHXAssistedCoolingCoil::SimHXAssistedCoolingCoil(
573 : state, CompName, FirstHVACIteration, HVAC::CompressorOp::On, 0.0, CompIndex, HVAC::FanOp::Continuous);
574 : // iterate on OA sys controller and water coil at the same time
575 0 : SimAirServingZones::SolveWaterCoilController(state,
576 : FirstHVACIteration,
577 : AirLoopNum,
578 : CompName,
579 : CompIndex,
580 0 : state.dataHVACAssistedCC->HXAssistedCoil(CompIndex).ControllerName,
581 0 : state.dataHVACAssistedCC->HXAssistedCoil(CompIndex).ControllerIndex,
582 : true);
583 : // set flag to tell HVAC controller it will be simulated only in SolveWaterCoilController()
584 0 : state.dataHVACControllers->ControllerProps(state.dataHVACAssistedCC->HXAssistedCoil(CompIndex).ControllerIndex).BypassControllerCalc =
585 : true;
586 : }
587 0 : OACoolingCoil = true;
588 0 : } break;
589 14571 : case SimAirServingZones::CompType::DXSystem: // CoilSystem:Cooling:DX
590 : case SimAirServingZones::CompType::CoilSystemWater: // CoilSystem:Cooling:Water
591 : case SimAirServingZones::CompType::UnitarySystemModel: { // AirloopHVAC:UnitarySystem
592 14571 : if (Sim) {
593 14567 : Real64 latOut = 0.0; // does the air loop not need to know what the latent capacity is?
594 14567 : int compNum = CompIndex; // use local so return value of compNum from simulate call does not overwrite CompIndex
595 14567 : state.dataAirLoop->OutsideAirSys(OASysNum).compPointer[compNum]->simulate(state,
596 : CompName,
597 : FirstHVACIteration,
598 : AirLoopNum,
599 : compNum,
600 : HeatingActive,
601 : CoolingActive,
602 : zoneOAUnitNum,
603 : OAUCoilOutTemp,
604 : ZoneEquipFlag,
605 : sensOut,
606 : latOut);
607 : }
608 14571 : if (state.dataMixedAir->MyOneTimeCheckUnitarySysFlag(OASysNum) && CompTypeNum == SimAirServingZones::CompType::UnitarySystemModel) {
609 0 : UnitarySystems::UnitarySys::getUnitarySysHeatCoolCoil(state, CompName, OACoolingCoil, OAHeatingCoil, 0);
610 0 : UnitarySystems::UnitarySys::checkUnitarySysCoilInOASysExists(state, CompName, 0);
611 0 : if (Sim) state.dataMixedAir->MyOneTimeCheckUnitarySysFlag(OASysNum) = false;
612 : } else {
613 14571 : OACoolingCoil = true;
614 : }
615 14571 : } break;
616 0 : case SimAirServingZones::CompType::DXHeatPumpSystem: { // CoilSystem:IntegratedHeatPump:AirSource
617 0 : if (Sim) {
618 0 : HVACDXHeatPumpSystem::SimDXHeatPumpSystem(state, CompName, FirstHVACIteration, AirLoopNum, CompIndex);
619 : }
620 0 : OAHeatingCoil = true;
621 0 : } break;
622 0 : case SimAirServingZones::CompType::CoilUserDefined: { // Coil:UserDefined
623 0 : if (Sim) {
624 0 : UserDefinedComponents::SimCoilUserDefined(state, CompName, CompIndex, AirLoopNum, OAHeatingCoil, OACoolingCoil);
625 : }
626 0 : } break;
627 10657 : case SimAirServingZones::CompType::HeatXchngr: {
628 : // HeatExchanger:AirToAir:FlatPlate, HeatExchanger:AirToAir:SensibleAndLatent, HeatExchanger:Desiccant:BalancedFlow
629 10657 : if (Sim) {
630 10653 : Real64 AirloopPLR = 1;
631 10653 : if (state.dataAirLoop->OutsideAirSys(OASysNum).AirLoopDOASNum > -1) {
632 9 : fanOp = HVAC::FanOp::Continuous;
633 : } else {
634 10644 : if (state.dataAirLoop->AirLoopControlInfo(AirLoopNum).fanOp == HVAC::FanOp::Cycling) {
635 0 : fanOp = HVAC::FanOp::Cycling;
636 : } else {
637 10644 : fanOp = HVAC::FanOp::Continuous;
638 : }
639 :
640 10644 : if (fanOp == HVAC::FanOp::Cycling) {
641 : // HX's in the OA system can be troublesome given that the OA flow rate is not necessarily proportional to air loop PLR
642 : // adding that user input for branch flow rate, HX nominal flow rate, OA system min/max flow rate will not necessarily be
643 : // perfectly input, a compromise is used for OA sys HX's as the ratio of flow to max. Issue #4298.
644 : // AirloopPLR = AirLoopFlow( AirLoopNum ).FanPLR;
645 0 : AirloopPLR = state.dataMixedAir->OAController(OASysNum).OAMassFlow / state.dataMixedAir->OAController(OASysNum).MaxOAMassFlowRate;
646 : }
647 : }
648 10653 : if (state.dataAirLoop->OutsideAirSys(OASysNum).AirLoopDOASNum > -1) {
649 9 : HeatRecovery::SimHeatRecovery(state, CompName, FirstHVACIteration, CompIndex, fanOp, AirloopPLR, _, _, _, _, _);
650 : } else {
651 21288 : HeatRecovery::SimHeatRecovery(state,
652 : CompName,
653 : FirstHVACIteration,
654 : CompIndex,
655 : fanOp,
656 : AirloopPLR,
657 : _,
658 : _,
659 : _,
660 10644 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).HeatRecoveryBypass,
661 10644 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).HighHumCtrlActive);
662 : }
663 : }
664 10657 : OAHX = true;
665 10657 : } break;
666 2 : case SimAirServingZones::CompType::Desiccant: { // Dehumidifier:Desiccant:NoFans, Dehumidifier:Desiccant:NoFans, Dehumidifier:Desiccant:System
667 2 : if (Sim) {
668 0 : DesiccantDehumidifiers::SimDesiccantDehumidifier(state, CompName, FirstHVACIteration, CompIndex);
669 : }
670 2 : OAHX = true;
671 2 : } break;
672 3 : case SimAirServingZones::CompType::Humidifier: { // Humidifier:Steam:Electric Humidifier:Steam:Gas
673 3 : if (Sim) {
674 3 : Humidifiers::SimHumidifier(state, CompName, FirstHVACIteration, CompIndex);
675 : }
676 3 : } break;
677 0 : case SimAirServingZones::CompType::Unglazed_SolarCollector: { // SolarCollector:UnglazedTranspired
678 0 : if (Sim) {
679 0 : TranspiredCollector::SimTranspiredCollector(state, CompName, CompIndex);
680 : }
681 0 : } break;
682 0 : case SimAirServingZones::CompType::PVT_AirBased: { // SolarCollector:FlatPlate:PhotovoltaicThermal
683 0 : if (Sim) {
684 0 : if (CompIndex == 0) {
685 0 : CompIndex = PhotovoltaicThermalCollectors::getPVTindexFromName(state, CompName);
686 : }
687 0 : PhotovoltaicThermalCollectors::simPVTfromOASys(state, CompIndex, FirstHVACIteration);
688 : }
689 0 : } break;
690 0 : case SimAirServingZones::CompType::EvapCooler: { // EvaporativeCooler:Direct:CelDekPad, EvaporativeCooler:Indirect:CelDekPad
691 : // EvaporativeCooler:Indirect:WetCoil, EvaporativeCooler:Indirect:ResearchSpecial
692 0 : if (Sim) {
693 0 : EvaporativeCoolers::SimEvapCooler(state, CompName, CompIndex);
694 : }
695 0 : } break;
696 0 : case SimAirServingZones::CompType::ZoneVRFasAirLoopEquip: { // ZoneHVAC:TerminalUnit:VariableRefrigerantFlow
697 0 : if (Sim) {
698 0 : int ControlledZoneNum = 0;
699 0 : int constexpr OAUnitNum = 0;
700 0 : Real64 constexpr OAUCoilOutTemp = 0.0;
701 0 : bool constexpr ZoneEquipment = false;
702 0 : Real64 sysOut = 0.0;
703 0 : Real64 latOut = 0.0;
704 0 : HVACVariableRefrigerantFlow::SimulateVRF(state,
705 : CompName,
706 : FirstHVACIteration,
707 : ControlledZoneNum,
708 : CompIndex,
709 : HeatingActive,
710 : CoolingActive,
711 : OAUnitNum,
712 : OAUCoilOutTemp,
713 : ZoneEquipment,
714 : sysOut,
715 : latOut);
716 : } else {
717 0 : HVACVariableRefrigerantFlow::isVRFCoilPresent(state, CompName, OACoolingCoil, OAHeatingCoil);
718 : }
719 0 : } break;
720 0 : default:
721 0 : ShowFatalError(state, format("Invalid Outside Air Component={}", CompType));
722 : }
723 139316 : }
724 :
725 383145 : void SimOAMixer(EnergyPlusData &state, std::string const &CompName, int &CompIndex)
726 : {
727 :
728 : // SUBROUTINE INFORMATION:
729 : // AUTHOR Fred Buhl
730 : // DATE WRITTEN Oct 1998
731 :
732 : // PURPOSE OF THIS SUBROUTINE
733 : // Simulate an Outside Air Mixer component
734 :
735 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
736 : int OAMixerNum;
737 :
738 383145 : if (state.dataMixedAir->GetOAMixerInputFlag) {
739 5 : GetOAMixerInputs(state);
740 5 : state.dataMixedAir->GetOAMixerInputFlag = false;
741 : }
742 :
743 383145 : if (CompIndex == 0) {
744 73 : OAMixerNum = Util::FindItemInList(CompName, state.dataMixedAir->OAMixer);
745 73 : CompIndex = OAMixerNum;
746 73 : if (OAMixerNum == 0) {
747 0 : ShowFatalError(state, format("SimOAMixer: OutdoorAir:Mixer not found={}", CompName));
748 : }
749 : } else {
750 383072 : OAMixerNum = CompIndex;
751 : }
752 :
753 383145 : auto &mixer = state.dataMixedAir->OAMixer(OAMixerNum);
754 :
755 383145 : mixer.InitOAMixer(state);
756 :
757 383145 : mixer.CalcOAMixer(state);
758 :
759 383145 : mixer.UpdateOAMixer(state);
760 383145 : }
761 :
762 78418 : void SimOAController(EnergyPlusData &state, std::string const &CtrlName, int &CtrlIndex, bool const FirstHVACIteration, int const AirLoopNum)
763 : {
764 :
765 : // SUBROUTINE INFORMATION:
766 : // AUTHOR Fred Buhl
767 : // DATE WRITTEN Oct 1998
768 :
769 : // PURPOSE OF THIS SUBROUTINE
770 : // Simulate an Outside Air Controller component
771 :
772 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
773 : int OAControllerNum;
774 :
775 78418 : if ((state.dataMixedAir->GetOAControllerInputFlag) &&
776 : (AirLoopNum > 0)) { // Gets input for object first time Sim routine is called from an airloop
777 25 : GetOAControllerInputs(state);
778 25 : state.dataMixedAir->GetOAControllerInputFlag = false;
779 : }
780 :
781 : // check that the economizer staging operation EconomizerFirst is only used with an sensible load-based controlled AirLoopHVAC:UnitarySystem
782 78418 : if (AirLoopNum > 0) {
783 78417 : auto &primaryAirSystems = state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum);
784 78417 : if (primaryAirSystems.EconomizerStagingCheckFlag == false) {
785 25 : OAControllerNum = Util::FindItemInList(CtrlName, state.dataMixedAir->OAController);
786 25 : if (state.dataMixedAir->OAController(OAControllerNum).EconomizerStagingType == HVAC::EconomizerStagingType::EconomizerFirst) {
787 0 : bool sensLoadCtrlUnitarySystemFound = false;
788 0 : for (int BranchNum = 1; BranchNum <= primaryAirSystems.NumBranches; ++BranchNum) {
789 0 : for (int CompNum = 1; CompNum <= primaryAirSystems.Branch(BranchNum).TotalComponents; ++CompNum) {
790 0 : if (primaryAirSystems.Branch(BranchNum).Comp(CompNum).CompType_Num == SimAirServingZones::CompType::UnitarySystemModel) {
791 0 : std::string_view unitarySystemName = primaryAirSystems.Branch(BranchNum).Comp(CompNum).Name;
792 0 : int unitarySystemNum = Util::FindItemInList(
793 0 : unitarySystemName, state.dataUnitarySystems->unitarySys, state.dataUnitarySystems->numUnitarySystems);
794 0 : if (state.dataUnitarySystems->unitarySys[unitarySystemNum - 1].m_ControlType ==
795 : UnitarySystems::UnitarySys::UnitarySysCtrlType::Load) {
796 0 : if (state.dataUnitarySystems->unitarySys[unitarySystemNum - 1].m_CoolingCoilType_Num ==
797 0 : HVAC::CoilDX_MultiSpeedCooling ||
798 0 : state.dataUnitarySystems->unitarySys[unitarySystemNum - 1].m_CoolingCoilType_Num ==
799 0 : HVAC::Coil_CoolingAirToAirVariableSpeed ||
800 0 : state.dataUnitarySystems->unitarySys[unitarySystemNum - 1].m_CoolingCoilType_Num == HVAC::CoilDX_Cooling) {
801 0 : sensLoadCtrlUnitarySystemFound = true;
802 0 : break;
803 : }
804 : }
805 : }
806 : }
807 : }
808 0 : if (!sensLoadCtrlUnitarySystemFound) {
809 0 : ShowWarningError(
810 : state,
811 0 : format(
812 : "SimOAController: EconomizerFirst was selected in the \"{}\" Controller:OutdoorAir object but the air loop it belongs to "
813 : "does not include an AirLoopHVAC:UnitarySystem with a \"Load\" Control Type input and cooling coil of one of the "
814 : "following types: Coil:Cooling:DX:MultiSpeed,"
815 : " Coil:Cooling:DX:VariableSpeed, or Coil:Cooling:DX. EconomizerFirst will not be enforced.",
816 0 : state.dataMixedAir->OAController(OAControllerNum).Name));
817 : }
818 : }
819 25 : primaryAirSystems.EconomizerStagingCheckFlag = true;
820 : }
821 : }
822 :
823 78418 : if (CtrlIndex == 0) {
824 26 : if (state.dataMixedAir->NumOAControllers > 0) {
825 26 : OAControllerNum = Util::FindItemInList(CtrlName, state.dataMixedAir->OAController);
826 : } else {
827 0 : OAControllerNum = 0;
828 : }
829 26 : CtrlIndex = OAControllerNum;
830 26 : if (OAControllerNum == 0) {
831 0 : ShowFatalError(state, format("SimOAController: Outside Air Controller not found={}", CtrlName));
832 : }
833 : } else {
834 78392 : OAControllerNum = CtrlIndex;
835 : }
836 :
837 78418 : InitOAController(state, OAControllerNum, FirstHVACIteration, AirLoopNum);
838 :
839 78418 : state.dataMixedAir->OAController(OAControllerNum).CalcOAController(state, AirLoopNum, FirstHVACIteration);
840 78418 : state.dataMixedAir->OAController(OAControllerNum).UpdateOAController(state);
841 78418 : }
842 :
843 : // Get Input Section of the Module
844 : //******************************************************************************
845 :
846 143 : void GetOutsideAirSysInputs(EnergyPlusData &state)
847 : {
848 :
849 : // SUBROUTINE INFORMATION:
850 : // AUTHOR Fred Buhl
851 : // DATE WRITTEN Oct 1998
852 :
853 : // PURPOSE OF THIS SUBROUTINE
854 : // Input the Outside Air System data and store it in the OutsideAirSys array.
855 :
856 : // METHODOLOGY EMPLOYED:
857 : // Use the Get routines from the InputProcessor module.
858 :
859 : // SUBROUTINE PARAMETER DEFINITIONS:
860 : static constexpr std::string_view RoutineName("GetOutsideAirSysInputs: "); // include trailing blank space
861 :
862 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
863 143 : bool ErrorsFound(false);
864 : int NumNums; // Number of real numbers returned by GetObjectItem
865 : int NumAlphas; // Number of alphanumerics returned by GetObjectItem
866 143 : int TotalArgs(0); // Total number of alpha and numeric arguments (max) for a
867 : int IOStat;
868 143 : Array1D<Real64> NumArray;
869 143 : Array1D_string AlphArray;
870 143 : Array1D_string cAlphaFields; // Alpha field names
871 143 : Array1D_string cNumericFields; // Numeric field names
872 143 : Array1D_bool lAlphaBlanks; // Logical array, alpha field input BLANK = .TRUE.
873 143 : Array1D_bool lNumericBlanks; // Logical array, numeric field input BLANK = .TRUE.
874 :
875 143 : if (!state.dataMixedAir->GetOASysInputFlag) return;
876 :
877 282 : state.dataInputProcessing->inputProcessor->getObjectDefMaxArgs(
878 141 : state, CurrentModuleObjects[static_cast<int>(CMO::OASystem)], TotalArgs, NumAlphas, NumNums);
879 141 : int MaxNums = NumNums;
880 141 : int MaxAlphas = NumAlphas;
881 282 : state.dataInputProcessing->inputProcessor->getObjectDefMaxArgs(
882 141 : state, CurrentModuleObjects[static_cast<int>(CMO::AirLoopEqList)], TotalArgs, NumAlphas, NumNums);
883 141 : MaxNums = max(MaxNums, NumNums);
884 141 : MaxAlphas = max(MaxAlphas, NumAlphas);
885 282 : state.dataInputProcessing->inputProcessor->getObjectDefMaxArgs(
886 141 : state, CurrentModuleObjects[static_cast<int>(CMO::ControllerList)], TotalArgs, NumAlphas, NumNums);
887 141 : MaxNums = max(MaxNums, NumNums);
888 141 : MaxAlphas = max(MaxAlphas, NumAlphas);
889 :
890 141 : AlphArray.allocate(MaxAlphas);
891 141 : cAlphaFields.allocate(MaxAlphas);
892 141 : NumArray.dimension(MaxNums, 0.0);
893 141 : cNumericFields.allocate(MaxNums);
894 141 : lAlphaBlanks.dimension(MaxAlphas, true);
895 141 : lNumericBlanks.dimension(MaxNums, true);
896 :
897 141 : std::string_view CurrentModuleObject = CurrentModuleObjects[static_cast<int>(CMO::ControllerList)];
898 141 : state.dataMixedAir->NumControllerLists = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, CurrentModuleObject);
899 :
900 141 : state.dataMixedAir->ControllerLists.allocate(state.dataMixedAir->NumControllerLists);
901 :
902 226 : for (int Item = 1; Item <= state.dataMixedAir->NumControllerLists; ++Item) {
903 :
904 : // create a reference for convenience
905 85 : auto &thisControllerList(state.dataMixedAir->ControllerLists(Item));
906 85 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
907 : CurrentModuleObject,
908 : Item,
909 : AlphArray,
910 : NumAlphas,
911 : NumArray,
912 : NumNums,
913 : IOStat,
914 : lNumericBlanks,
915 : lAlphaBlanks,
916 : cAlphaFields,
917 : cNumericFields);
918 85 : thisControllerList.Name = AlphArray(1); // no need to check if AlphaArray(1) is empty since Json will catch missing required fields
919 85 : thisControllerList.NumControllers = (NumAlphas - 1) / 2;
920 85 : thisControllerList.ControllerType.dimension(thisControllerList.NumControllers, ControllerKind::Invalid);
921 85 : thisControllerList.ControllerName.allocate(thisControllerList.NumControllers);
922 85 : int AlphaNum = 2;
923 185 : for (int CompNum = 1; CompNum <= thisControllerList.NumControllers; ++CompNum) {
924 : // Json will catch any object types that are not the correct key choice of Controller:OutdoorAir or Controller:WaterCoil
925 200 : thisControllerList.ControllerType(CompNum) =
926 100 : static_cast<ControllerKind>(getEnumValue(ControllerKindNamesUC, Util::makeUPPER(AlphArray(AlphaNum))));
927 100 : thisControllerList.ControllerName(CompNum) = AlphArray(AlphaNum + 1);
928 : // loop over all previous controller lists to check if this controllers is also present on previous controllers
929 139 : for (int previousListNum = 1; previousListNum < Item; ++previousListNum) {
930 : // loop over each of the controllers listed for this list
931 39 : auto &previousList(state.dataMixedAir->ControllerLists(previousListNum));
932 84 : for (int PreviousListControllerNum = 1; PreviousListControllerNum <= previousList.NumControllers; ++PreviousListControllerNum) {
933 76 : if ((previousList.ControllerType(PreviousListControllerNum) == thisControllerList.ControllerType(CompNum)) &&
934 31 : (previousList.ControllerName(PreviousListControllerNum) == thisControllerList.ControllerName(CompNum))) {
935 0 : ShowSevereError(state, format("Controller instance repeated in multiple {} objects", CurrentModuleObject));
936 0 : ShowContinueError(state, format("Found in {} = {}", CurrentModuleObject, thisControllerList.Name));
937 0 : ShowContinueError(state, format("Also found in {} = {}", CurrentModuleObject, previousList.Name));
938 0 : ErrorsFound = true;
939 : }
940 : }
941 : }
942 100 : AlphaNum += 2;
943 : }
944 : }
945 :
946 141 : CurrentModuleObject = CurrentModuleObjects[static_cast<int>(CMO::OASystem)];
947 :
948 141 : state.dataAirLoop->NumOASystems = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, CurrentModuleObject);
949 :
950 141 : state.dataAirLoop->OutsideAirSys.allocate(state.dataAirLoop->NumOASystems);
951 141 : state.dataSize->OASysEqSizing.allocate(state.dataAirLoop->NumOASystems);
952 141 : state.dataMixedAir->ControllerListUniqueNames.reserve(static_cast<unsigned>(state.dataAirLoop->NumOASystems));
953 141 : state.dataMixedAir->MyOneTimeErrorFlag.dimension(state.dataAirLoop->NumOASystems, true);
954 141 : state.dataMixedAir->MyOneTimeCheckUnitarySysFlag.dimension(state.dataAirLoop->NumOASystems, true);
955 141 : state.dataMixedAir->initOASysFlag.dimension(state.dataAirLoop->NumOASystems, true);
956 :
957 212 : for (int OASysNum = 1; OASysNum <= state.dataAirLoop->NumOASystems; ++OASysNum) {
958 71 : auto &OASys = state.dataAirLoop->OutsideAirSys(OASysNum);
959 71 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
960 : CurrentModuleObject,
961 : OASysNum,
962 : AlphArray,
963 : NumAlphas,
964 : NumArray,
965 : NumNums,
966 : IOStat,
967 : lNumericBlanks,
968 : lAlphaBlanks,
969 : cAlphaFields,
970 : cNumericFields);
971 71 : OASys.Name = AlphArray(1); // no need to check if AlphaArray(1) is empty since Json will catch missing required fields
972 71 : if (!AlphArray(2).empty()) {
973 134 : GlobalNames::IntraObjUniquenessCheck(
974 67 : state, AlphArray(2), CurrentModuleObject, cAlphaFields(2), state.dataMixedAir->ControllerListUniqueNames, ErrorsFound);
975 : }
976 71 : OASys.ControllerListName = AlphArray(2);
977 71 : OASys.ComponentListName = AlphArray(3);
978 :
979 355 : BranchNodeConnections::TestCompSet(state, CurrentModuleObject, OASys.Name, "UNDEFINED", "UNDEFINED", "Air Nodes");
980 :
981 71 : if (!lAlphaBlanks(3)) {
982 142 : int ListNum = state.dataInputProcessing->inputProcessor->getObjectItemNum(
983 71 : state, CurrentModuleObjects[static_cast<int>(CMO::AirLoopEqList)], OASys.ComponentListName);
984 71 : if (ListNum > 0) {
985 142 : state.dataInputProcessing->inputProcessor->getObjectItem(
986 71 : state, CurrentModuleObjects[static_cast<int>(CMO::AirLoopEqList)], ListNum, AlphArray, NumAlphas, NumArray, NumNums, IOStat);
987 71 : int NumInList = (NumAlphas - 1) / 2;
988 71 : OASys.NumComponents = NumInList;
989 71 : OASys.ComponentName.allocate(NumInList);
990 71 : OASys.ComponentType.allocate(NumInList);
991 71 : OASys.ComponentTypeEnum.dimension(NumInList, SimAirServingZones::CompType::Invalid);
992 71 : OASys.ComponentIndex.dimension(NumInList, 0);
993 71 : OASys.InletNodeNum.dimension(NumInList, 0);
994 71 : OASys.OutletNodeNum.dimension(NumInList, 0);
995 71 : OASys.compPointer.resize(NumInList + 1, nullptr);
996 176 : for (int InListNum = 1; InListNum <= NumInList; ++InListNum) {
997 105 : OASys.ComponentName(InListNum) = AlphArray(InListNum * 2 + 1);
998 105 : OASys.ComponentType(InListNum) = AlphArray(InListNum * 2);
999 :
1000 : // Add equipment to component sets array
1001 210 : BranchNodeConnections::SetUpCompSets(state,
1002 : CurrentModuleObject,
1003 : OASys.Name,
1004 105 : OASys.ComponentType(InListNum),
1005 105 : OASys.ComponentName(InListNum),
1006 : "UNDEFINED",
1007 : "UNDEFINED");
1008 : }
1009 : } else {
1010 0 : ShowSevereError(
1011 : state,
1012 0 : format("{} = \"{}\" invalid {}=\"{}\" not found.", CurrentModuleObject, OASys.Name, cAlphaFields(3), OASys.ComponentListName));
1013 0 : ErrorsFound = true;
1014 : }
1015 : } else {
1016 0 : ShowSevereError(state, format("{} = \"{}\" invalid {} is blank and must be entered.", CurrentModuleObject, OASys.Name, cAlphaFields(3)));
1017 0 : ErrorsFound = true;
1018 : }
1019 :
1020 71 : int ListNum = 0;
1021 71 : int NumSimpControllers = 0; // number of Controller:Simple objects in an OA System
1022 71 : if (!lAlphaBlanks(2)) {
1023 134 : ListNum = state.dataInputProcessing->inputProcessor->getObjectItemNum(
1024 67 : state, CurrentModuleObjects[static_cast<int>(CMO::ControllerList)], OASys.ControllerListName);
1025 67 : if (ListNum > 0) {
1026 134 : state.dataInputProcessing->inputProcessor->getObjectItem(
1027 67 : state, CurrentModuleObjects[static_cast<int>(CMO::ControllerList)], ListNum, AlphArray, NumAlphas, NumArray, NumNums, IOStat);
1028 67 : int NumInList = (NumAlphas - 1) / 2;
1029 67 : OASys.NumControllers = NumInList;
1030 67 : OASys.ControllerName.allocate(NumInList);
1031 67 : OASys.ControllerType.allocate(NumInList);
1032 67 : OASys.controllerTypeEnum.dimension(NumInList, DataAirLoop::ControllerKind::Invalid);
1033 67 : OASys.ControllerIndex.dimension(NumInList, 0);
1034 143 : for (int InListNum = 1; InListNum <= NumInList; ++InListNum) {
1035 76 : OASys.ControllerName(InListNum) = AlphArray(InListNum * 2 + 1);
1036 76 : OASys.ControllerType(InListNum) = AlphArray(InListNum * 2);
1037 152 : OASys.controllerTypeEnum(InListNum) =
1038 76 : static_cast<DataAirLoop::ControllerKind>(getEnumValue(ControllerKindNamesUC, OASys.ControllerType(InListNum)));
1039 : // only count Controller:OutdoorAir types as valid simple controllers
1040 76 : if (OASys.controllerTypeEnum(InListNum) != DataAirLoop::ControllerKind::OutdoorAir) {
1041 12 : ++NumSimpControllers;
1042 : }
1043 : }
1044 : } else {
1045 0 : ShowSevereError(state,
1046 0 : format("{} = \"{}\" invalid {}=\"{}\" not found.", CurrentModuleObject, AlphArray(1), cAlphaFields(2), AlphArray(2)));
1047 0 : ErrorsFound = true;
1048 : }
1049 : } else {
1050 4 : if (state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "AirLoopHVAC:DedicatedOutdoorAirSystem") == 0) {
1051 0 : ShowSevereError(state,
1052 0 : format("{} = \"{}\" invalid {} is blank and must be entered.", CurrentModuleObject, AlphArray(1), cAlphaFields(2)));
1053 0 : ErrorsFound = true;
1054 : } else {
1055 8 : ShowWarningError(state,
1056 8 : format("{} = \"{}\": blank {} must be used with AirLoopHVAC:DedicatedOutdoorAirSystem.",
1057 : CurrentModuleObject,
1058 : AlphArray(1),
1059 : cAlphaFields(2)));
1060 : }
1061 : }
1062 71 : OASys.ControllerListNum = ListNum;
1063 71 : OASys.NumSimpleControllers = NumSimpControllers;
1064 : }
1065 :
1066 212 : for (int OASysNum = 1; OASysNum <= state.dataAirLoop->NumOASystems; ++OASysNum) {
1067 71 : auto &OASys = state.dataAirLoop->OutsideAirSys(OASysNum);
1068 176 : for (int CompNum = 1; CompNum <= OASys.NumComponents; ++CompNum) {
1069 105 : OASys.ComponentTypeEnum(CompNum) = static_cast<SimAirServingZones::CompType>(getEnumValue(CompTypeNamesUC, OASys.ComponentType(CompNum)));
1070 105 : if (OASys.ComponentTypeEnum(CompNum) == SimAirServingZones::CompType::Fan_System_Object) {
1071 : // construct fan object
1072 5 : OASys.ComponentIndex(CompNum) = Fans::GetFanIndex(state, OASys.ComponentName(CompNum));
1073 100 : } else if (OASys.ComponentTypeEnum(CompNum) == SimAirServingZones::CompType::CoilSystemWater ||
1074 200 : OASys.ComponentTypeEnum(CompNum) == SimAirServingZones::CompType::UnitarySystemModel ||
1075 100 : OASys.ComponentTypeEnum(CompNum) == SimAirServingZones::CompType::DXSystem) {
1076 6 : OASys.ComponentIndex(CompNum) = CompNum;
1077 94 : } else if (OASys.ComponentTypeEnum(CompNum) == SimAirServingZones::CompType::Invalid) {
1078 8 : std::string const thisComp = OASys.ComponentType(CompNum);
1079 8 : if (thisComp == "HEATEXCHANGER:AIRTOAIR:SENSIBLEANDLATENT" || thisComp == "HEATEXCHANGER:DESICCANT:BALANCEDFLOW") {
1080 7 : OASys.ComponentTypeEnum(CompNum) = SimAirServingZones::CompType::HeatXchngr;
1081 1 : } else if (thisComp == "DEHUMIDIFIER:DESICCANT:SYSTEM") {
1082 1 : OASys.ComponentTypeEnum(CompNum) = SimAirServingZones::CompType::Desiccant;
1083 0 : } else if (thisComp == "EVAPORATIVECOOLER:INDIRECT:CELDEKPAD" || thisComp == "EVAPORATIVECOOLER:INDIRECT:WETCOIL" ||
1084 0 : thisComp == "EVAPORATIVECOOLER:INDIRECT:RESEARCHSPECIAL" || thisComp == "EVAPORATIVECOOLER:DIRECT:RESEARCHSPECIAL") {
1085 0 : OASys.ComponentTypeEnum(CompNum) = SimAirServingZones::CompType::EvapCooler;
1086 0 : } else if (thisComp == "HUMIDIFIER:STEAM:GAS") {
1087 0 : OASys.ComponentTypeEnum(CompNum) = SimAirServingZones::CompType::Humidifier;
1088 : } else {
1089 0 : ShowSevereError(
1090 : state,
1091 0 : format("{} = \"{}\" invalid Outside Air Component=\"{}\".", CurrentModuleObject, AlphArray(1), OASys.ComponentType(CompNum)));
1092 0 : ErrorsFound = true;
1093 : }
1094 8 : }
1095 : }
1096 :
1097 : // loop through the controllers in the controller list for OA system and save the pointer to the OA controller index
1098 78 : for (int OAControllerNum = 1; OAControllerNum <= state.dataAirLoop->OutsideAirSys(OASysNum).NumControllers; ++OAControllerNum) {
1099 71 : if (state.dataAirLoop->OutsideAirSys(OASysNum).controllerTypeEnum(OAControllerNum) == DataAirLoop::ControllerKind::OutdoorAir) {
1100 64 : state.dataAirLoop->OutsideAirSys(OASysNum).OAControllerName =
1101 128 : state.dataAirLoop->OutsideAirSys(OASysNum).ControllerName(OAControllerNum);
1102 64 : break;
1103 : }
1104 : }
1105 : }
1106 :
1107 141 : if (ErrorsFound) {
1108 0 : ShowFatalError(state, format("{}Errors found in getting {}.", RoutineName, CurrentModuleObject));
1109 : }
1110 :
1111 141 : AlphArray.deallocate();
1112 141 : cAlphaFields.deallocate();
1113 141 : NumArray.deallocate();
1114 141 : cNumericFields.deallocate();
1115 141 : lAlphaBlanks.deallocate();
1116 141 : lNumericBlanks.deallocate();
1117 :
1118 141 : state.dataMixedAir->GetOASysInputFlag = false;
1119 :
1120 : // once GetOASysInputFlag is set to false other calls to objects can occur without worry that GetOutsideAirSysInputs will be called again
1121 : // now get the pointer for UnitarySystem - doing this earlier can cause recursion which trips IntraObjUniquenessCheck warnings
1122 212 : for (int OASysNum = 1; OASysNum <= state.dataAirLoop->NumOASystems; ++OASysNum) {
1123 176 : for (int CompNum = 1; CompNum <= state.dataAirLoop->OutsideAirSys(OASysNum).NumComponents; ++CompNum) {
1124 105 : if (state.dataAirLoop->OutsideAirSys(OASysNum).ComponentTypeEnum(CompNum) == SimAirServingZones::CompType::CoilSystemWater ||
1125 210 : state.dataAirLoop->OutsideAirSys(OASysNum).ComponentTypeEnum(CompNum) == SimAirServingZones::CompType::UnitarySystemModel ||
1126 105 : state.dataAirLoop->OutsideAirSys(OASysNum).ComponentTypeEnum(CompNum) == SimAirServingZones::CompType::DXSystem) {
1127 6 : state.dataAirLoop->OutsideAirSys(OASysNum).compPointer[CompNum] = UnitarySystems::UnitarySys::factory(
1128 6 : state, HVAC::UnitarySysType::Unitary_AnyCoilType, state.dataAirLoop->OutsideAirSys(OASysNum).ComponentName(CompNum), false, 0);
1129 : }
1130 : }
1131 : }
1132 153 : }
1133 :
1134 53 : void GetOAControllerInputs(EnergyPlusData &state)
1135 : {
1136 :
1137 : // SUBROUTINE INFORMATION:
1138 : // AUTHOR Fred Buhl
1139 : // DATE WRITTEN Oct 1998
1140 : // MODIFIED Shirey/Raustad FSEC, June 2003, Jan 2004
1141 : // Mangesh Basarkar, 06/2011: Getting zone OA specifications from Design Specification Object
1142 : // Tianzhen Hong, 3/2012: getting zone air distribution effectiveness and secondary recirculation
1143 : // from DesignSpecification:ZoneAirDistribution objects
1144 :
1145 : // PURPOSE OF THIS SUBROUTINE
1146 : // Input the OAController data and store it in the OAController array.
1147 : // Input the Ventilation:Mechanical data and store it in the VentilationMechanical array.
1148 : // Condense Ventilation:Mechanical data array to include only unique zones specified for each instance of this object.
1149 :
1150 : // METHODOLOGY EMPLOYED:
1151 : // Use the Get routines from the InputProcessor module.
1152 :
1153 : // SUBROUTINE PARAMETER DEFINITIONS:
1154 : static constexpr std::string_view RoutineName("GetOAControllerInputs: "); // include trailing blank space
1155 : static constexpr std::string_view routineName = "GetOAControllerInputs"; // include trailing blank space
1156 :
1157 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1158 : int NumArg; // Number of arguments from GetObjectDefMaxArgs call
1159 : int NumNums; // Number of real numbers returned by GetObjectItem
1160 : int NumAlphas; // Number of alphanumerics returned by GetObjectItem
1161 : int IOStat; // Status of GetObjectItem call
1162 53 : Array1D<Real64> NumArray;
1163 53 : Array1D_string AlphArray;
1164 53 : std::string_view CurrentModuleObject; // Object type for getting and messages
1165 53 : Array1D_string cAlphaFields; // Alpha field names
1166 53 : Array1D_string cNumericFields; // Numeric field names
1167 53 : Array1D_bool lAlphaBlanks; // Logical array, alpha field input BLANK = .TRUE.
1168 53 : Array1D_bool lNumericBlanks; // Logical array, numeric field input BLANK = .TRUE.
1169 53 : bool ErrorsFound(false); // Flag identifying errors found during get input
1170 :
1171 : // First, call other get input routines in this module to make sure data is filled during this routine.
1172 53 : if (state.dataMixedAir->GetOASysInputFlag) { // Gets input for object first time Sim routine is called
1173 23 : GetOutsideAirSysInputs(state);
1174 23 : state.dataMixedAir->GetOASysInputFlag = false;
1175 : }
1176 53 : if (state.dataMixedAir->GetOAMixerInputFlag) { // Gets input for object first time Sim routine is called
1177 30 : GetOAMixerInputs(state);
1178 30 : state.dataMixedAir->GetOAMixerInputFlag = false;
1179 : }
1180 :
1181 53 : FaultsManager::CheckAndReadFaults(state);
1182 :
1183 106 : state.dataInputProcessing->inputProcessor->getObjectDefMaxArgs(
1184 53 : state, CurrentModuleObjects[static_cast<int>(CMO::OAController)], NumArg, NumAlphas, NumNums);
1185 53 : int MaxAlphas = NumAlphas;
1186 53 : int MaxNums = NumNums;
1187 106 : state.dataInputProcessing->inputProcessor->getObjectDefMaxArgs(
1188 53 : state, CurrentModuleObjects[static_cast<int>(CMO::ERVController)], NumArg, NumAlphas, NumNums);
1189 53 : MaxAlphas = max(MaxAlphas, NumAlphas);
1190 53 : MaxNums = max(MaxNums, NumNums);
1191 106 : state.dataInputProcessing->inputProcessor->getObjectDefMaxArgs(
1192 53 : state, CurrentModuleObjects[static_cast<int>(CMO::MechVentilation)], NumArg, NumAlphas, NumNums);
1193 53 : MaxAlphas = max(MaxAlphas, NumAlphas);
1194 53 : MaxNums = max(MaxNums, NumNums);
1195 :
1196 53 : AlphArray.allocate(MaxAlphas);
1197 53 : NumArray.dimension(MaxNums, 0.0);
1198 53 : lAlphaBlanks.dimension(MaxAlphas, true);
1199 53 : lNumericBlanks.dimension(MaxNums, true);
1200 53 : cAlphaFields.allocate(MaxAlphas);
1201 53 : cNumericFields.allocate(MaxNums);
1202 :
1203 : // Count OAcontrollers and ERVcontrollers and allocate arrays
1204 53 : AllocateOAControllers(state);
1205 :
1206 : // If there are ERV controllers, they have been filled before now NumOAControllers includes the count of NumERVControllers
1207 53 : if (state.dataMixedAir->NumOAControllers > state.dataMixedAir->NumERVControllers) {
1208 46 : CurrentModuleObject = CurrentModuleObjects[static_cast<int>(CMO::OAController)];
1209 46 : int currentOAControllerNum = 0;
1210 98 : for (int OutAirNum = state.dataMixedAir->NumERVControllers + 1; OutAirNum <= state.dataMixedAir->NumOAControllers; ++OutAirNum) {
1211 52 : ++currentOAControllerNum;
1212 52 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
1213 : CurrentModuleObject,
1214 : currentOAControllerNum,
1215 : AlphArray,
1216 : NumAlphas,
1217 : NumArray,
1218 : NumNums,
1219 : IOStat,
1220 : lNumericBlanks,
1221 : lAlphaBlanks,
1222 : cAlphaFields,
1223 : cNumericFields);
1224 104 : GlobalNames::VerifyUniqueInterObjectName(
1225 52 : state, state.dataMixedAir->OAControllerUniqueNames, AlphArray(1), CurrentModuleObject, cAlphaFields(1), ErrorsFound);
1226 :
1227 52 : ProcessOAControllerInputs(state,
1228 : CurrentModuleObject,
1229 : OutAirNum,
1230 : AlphArray,
1231 : NumAlphas,
1232 : NumArray,
1233 : NumNums,
1234 : lNumericBlanks,
1235 : lAlphaBlanks,
1236 : cAlphaFields,
1237 : cNumericFields,
1238 : ErrorsFound);
1239 :
1240 : // add applicable faults identifier to avoid string comparison at each time step
1241 : // loop through each fault for each OA controller and determine economizer faults
1242 62 : for (int i = 1; i <= state.dataFaultsMgr->NumFaultyEconomizer; ++i) {
1243 10 : if (state.dataFaultsMgr->FaultsEconomizer(i).ControllerTypeEnum != iController_AirEconomizer) continue;
1244 10 : if (Util::SameString(state.dataMixedAir->OAController(OutAirNum).Name, state.dataFaultsMgr->FaultsEconomizer(i).ControllerName)) {
1245 5 : state.dataFaultsMgr->FaultsEconomizer(i).ControllerID = OutAirNum;
1246 5 : ++state.dataMixedAir->OAController(OutAirNum).NumFaultyEconomizer;
1247 : }
1248 : }
1249 : // loop through each fault for each OA controller to determine faulty counts
1250 52 : state.dataMixedAir->OAController(OutAirNum).EconmizerFaultNum.allocate(state.dataMixedAir->OAController(OutAirNum).NumFaultyEconomizer);
1251 52 : if (state.dataMixedAir->OAController(OutAirNum).NumFaultyEconomizer > 0) {
1252 12 : for (int j = 0, i = 1; i <= state.dataFaultsMgr->NumFaultyEconomizer; ++i) {
1253 10 : if (state.dataFaultsMgr->FaultsEconomizer(i).ControllerTypeEnum != iController_AirEconomizer) continue;
1254 10 : if (Util::SameString(state.dataMixedAir->OAController(OutAirNum).Name, state.dataFaultsMgr->FaultsEconomizer(i).ControllerName)) {
1255 5 : state.dataMixedAir->OAController(OutAirNum).EconmizerFaultNum(++j) = i;
1256 : }
1257 : }
1258 : }
1259 : } // LOOP FOR OutAirNum
1260 :
1261 46 : if (ErrorsFound) {
1262 0 : AlphArray.deallocate();
1263 0 : NumArray.deallocate();
1264 0 : lNumericBlanks.deallocate();
1265 0 : lAlphaBlanks.deallocate();
1266 0 : cAlphaFields.deallocate();
1267 0 : cNumericFields.deallocate();
1268 0 : ShowFatalError(state, format("{}Errors found in getting {} inputs.", RoutineName, CurrentModuleObject));
1269 : }
1270 : }
1271 :
1272 53 : state.dataMixedAir->GetOAControllerInputFlag = false;
1273 :
1274 : // Process Controller:MechanicalVentilation objects
1275 53 : CurrentModuleObject = CurrentModuleObjects[static_cast<int>(CMO::MechVentilation)];
1276 53 : state.dataMixedAir->NumVentMechControllers = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, CurrentModuleObject);
1277 53 : if (state.dataMixedAir->NumVentMechControllers > 0) {
1278 17 : state.dataMixedAir->VentilationMechanical.allocate(state.dataMixedAir->NumVentMechControllers);
1279 34 : for (int VentMechNum = 1; VentMechNum <= state.dataMixedAir->NumVentMechControllers; ++VentMechNum) {
1280 17 : auto &thisVentilationMechanical(state.dataMixedAir->VentilationMechanical(VentMechNum));
1281 17 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
1282 : CurrentModuleObject,
1283 : VentMechNum,
1284 : AlphArray,
1285 : NumAlphas,
1286 : NumArray,
1287 : NumNums,
1288 : IOStat,
1289 : lNumericBlanks,
1290 : lAlphaBlanks,
1291 : cAlphaFields,
1292 : cNumericFields);
1293 :
1294 17 : ErrorObjectHeader eoh{routineName, CurrentModuleObject, AlphArray(1)};
1295 :
1296 17 : int MechVentZoneCount = 0;
1297 :
1298 17 : int NumGroups = (NumAlphas + NumNums - 5) / 3; // Number of extensible input groups of the VentilationMechanical object
1299 17 : if (mod((NumAlphas + NumNums - 5), 3) != 0) ++NumGroups;
1300 17 : thisVentilationMechanical.Name = AlphArray(1); // no need to check if AlphaArray(1) is empty since Json will catch missing required fields
1301 :
1302 17 : if (lAlphaBlanks(2)) {
1303 6 : thisVentilationMechanical.availSched = Sched::GetScheduleAlwaysOn(state);
1304 11 : } else if ((thisVentilationMechanical.availSched = Sched::GetSchedule(state, AlphArray(2))) == nullptr) {
1305 0 : ShowSevereItemNotFound(state, eoh, cAlphaFields(2), AlphArray(2));
1306 0 : ErrorsFound = true;
1307 : }
1308 :
1309 : // Adding new flag for DCV
1310 17 : if (Util::SameString(AlphArray(3), "Yes")) {
1311 12 : thisVentilationMechanical.DCVFlag = true;
1312 5 : } else if (Util::SameString(AlphArray(3), "No") || lAlphaBlanks(3)) {
1313 5 : thisVentilationMechanical.DCVFlag = false;
1314 : } else {
1315 0 : ShowSevereError(state,
1316 0 : format("{}=\"{}\" invalid value {}=\"{}\".", CurrentModuleObject, AlphArray(1), cAlphaFields(3), AlphArray(3)));
1317 0 : ShowContinueError(state, "...Valid values are \"Yes\" or \"No\".");
1318 0 : ErrorsFound = true;
1319 : }
1320 :
1321 : // System outdoor air method
1322 17 : thisVentilationMechanical.SystemOAMethod = static_cast<DataSizing::SysOAMethod>(getEnumValue(SOAMNamesUC, Util::makeUPPER(AlphArray(4))));
1323 :
1324 17 : if (thisVentilationMechanical.SystemOAMethod == DataSizing::SysOAMethod::IAQP ||
1325 16 : thisVentilationMechanical.SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlSchOcc ||
1326 16 : thisVentilationMechanical.SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlDesOcc ||
1327 14 : thisVentilationMechanical.SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlDesOARate ||
1328 13 : thisVentilationMechanical.SystemOAMethod == DataSizing::SysOAMethod::IAQPCOM) {
1329 4 : if (!state.dataContaminantBalance->Contaminant.CO2Simulation) {
1330 0 : ShowSevereError(
1331 : state,
1332 0 : format(
1333 : "{}=\"{}\" valid {}=\"{}\" requires CO2 simulation.", CurrentModuleObject, AlphArray(1), cAlphaFields(2), AlphArray(2)));
1334 0 : ShowContinueError(state, "The choice must be Yes for the field Carbon Dioxide Concentration in ZoneAirContaminantBalance");
1335 0 : ErrorsFound = true;
1336 : }
1337 : }
1338 :
1339 17 : if (thisVentilationMechanical.SystemOAMethod == DataSizing::SysOAMethod::IAQPGC ||
1340 17 : thisVentilationMechanical.SystemOAMethod == DataSizing::SysOAMethod::IAQPCOM) {
1341 0 : if (!state.dataContaminantBalance->Contaminant.GenericContamSimulation) {
1342 0 : ShowSevereError(state,
1343 0 : format("{}=\"{}\" valid {}=\"{}\" requires generic contaminant simulation.",
1344 : CurrentModuleObject,
1345 : AlphArray(1),
1346 : cAlphaFields(2),
1347 : AlphArray(2)));
1348 0 : ShowContinueError(state, "The choice must be Yes for the field Generic Contaminant Concentration in ZoneAirContaminantBalance");
1349 0 : ErrorsFound = true;
1350 : }
1351 : }
1352 :
1353 17 : if (thisVentilationMechanical.SystemOAMethod == DataSizing::SysOAMethod::Invalid) { // If specified incorrectly, show errors
1354 0 : thisVentilationMechanical.SystemOAMethod = DataSizing::SysOAMethod::ZoneSum;
1355 0 : ShowWarningError(state,
1356 0 : format("{}=\"{}\" incorrect specification for {}, the ZoneSum method will be used.",
1357 : CurrentModuleObject,
1358 : AlphArray(1),
1359 : cAlphaFields(4)));
1360 : // ErrorsFound=.TRUE.
1361 : }
1362 :
1363 : // Zone maximum outdoor air fraction
1364 17 : thisVentilationMechanical.ZoneMaxOAFraction = NumArray(1);
1365 :
1366 17 : EPVector<int> designSpecOAObjIndex; // index of the design specification outdoor air object
1367 17 : EPVector<std::string> ventMechZoneOrListName; // Zone or Zone List to apply mechanical ventilation rate
1368 17 : EPVector<int> designSpecZoneADObjIndex; // index of the design specification zone air distribution object
1369 17 : ventMechZoneOrListName.allocate(NumGroups);
1370 17 : designSpecOAObjIndex.dimension(NumGroups, 0);
1371 17 : designSpecZoneADObjIndex.dimension(NumGroups, 0);
1372 :
1373 : // First time through find the total number of zones requiring mechanical ventilation
1374 : // May include duplicate zones. Will check for duplicate zones further down in this subroutine.
1375 50 : for (int groupNum = 1; groupNum <= NumGroups; ++groupNum) {
1376 33 : ventMechZoneOrListName(groupNum) = AlphArray((groupNum - 1) * 3 + 5);
1377 :
1378 : // Getting OA details from design specification OA object
1379 33 : if (!lAlphaBlanks((groupNum - 1) * 3 + 6)) {
1380 30 : std::string_view designSpecOAObjName = AlphArray((groupNum - 1) * 3 + 6);
1381 30 : int ObjIndex = Util::FindItemInList(designSpecOAObjName, state.dataSize->OARequirements);
1382 30 : designSpecOAObjIndex(groupNum) = ObjIndex;
1383 30 : if (ObjIndex == 0) {
1384 0 : ShowSevereError(state, format("{}{}=\"{}\", invalid", RoutineName, CurrentModuleObject, thisVentilationMechanical.Name));
1385 0 : ShowContinueError(state, format("... not found {}=\"{}\".", cAlphaFields((groupNum - 1) * 3 + 6), designSpecOAObjName));
1386 0 : ErrorsFound = true;
1387 : }
1388 : } else {
1389 6 : ShowSevereError(
1390 : state,
1391 6 : format("{}=\"{}\", Design Specification Outdoor Air Object Name blank", CurrentModuleObject, thisVentilationMechanical.Name));
1392 3 : ShowContinueError(state, format("For Zone=\"{}\".", ventMechZoneOrListName(groupNum)));
1393 6 : ShowContinueError(state, "This field either needs to be filled in in this object or Sizing:Zone object.");
1394 9 : ShowContinueError(state, "For this run, default values for these fields will be used.");
1395 : }
1396 :
1397 : // Get zone air distribution details from design specification Zone Air Distribution object
1398 33 : if (!lAlphaBlanks((groupNum - 1) * 3 + 7)) {
1399 19 : std::string_view designSpecZoneADObjName = AlphArray((groupNum - 1) * 3 + 7);
1400 19 : int ObjIndex = Util::FindItemInList(designSpecZoneADObjName, state.dataSize->ZoneAirDistribution);
1401 19 : designSpecZoneADObjIndex(groupNum) = ObjIndex;
1402 19 : if (ObjIndex == 0) {
1403 : // Cannot find the design specification Zone Air Distribution object
1404 0 : ShowSevereError(state, format("{}{}=\"{}\", invalid", RoutineName, CurrentModuleObject, thisVentilationMechanical.Name));
1405 0 : ShowContinueError(state, format("... not found {}=\"{}\".", cAlphaFields((groupNum - 1) * 3 + 7), designSpecZoneADObjName));
1406 0 : ErrorsFound = true;
1407 : }
1408 : }
1409 :
1410 33 : int ZoneNum = Util::FindItemInList(ventMechZoneOrListName(groupNum), state.dataHeatBal->Zone);
1411 33 : if (ZoneNum > 0) {
1412 33 : ++MechVentZoneCount;
1413 : } else {
1414 0 : int ZoneListNum = Util::FindItemInList(ventMechZoneOrListName(groupNum), state.dataHeatBal->ZoneList);
1415 0 : if (ZoneListNum > 0) {
1416 0 : MechVentZoneCount += state.dataHeatBal->ZoneList(ZoneListNum).NumOfZones;
1417 : } else {
1418 0 : ShowWarningError(
1419 : state,
1420 0 : format("{}=\"{}\" invalid {} not found.", CurrentModuleObject, AlphArray(1), cAlphaFields((groupNum - 1) * 3 + 5)));
1421 0 : ShowContinueError(state, format("Missing {} = {}", cAlphaFields((groupNum - 1) * 3 + 5), ventMechZoneOrListName(groupNum)));
1422 0 : ErrorsFound = true;
1423 : }
1424 : }
1425 : }
1426 :
1427 17 : thisVentilationMechanical.NumofVentMechZones = MechVentZoneCount;
1428 :
1429 : // Now allocate and store unique zone and associated ventilation rate information
1430 17 : thisVentilationMechanical.VentMechZone.allocate(MechVentZoneCount);
1431 :
1432 17 : MechVentZoneCount = 0;
1433 :
1434 : // Loop through zone names and list of zone names, remove duplicate zones, and store designspec names and indexes
1435 50 : for (int groupNum = 1; groupNum <= NumGroups; ++groupNum) {
1436 33 : int ZoneNum = Util::FindItemInList(ventMechZoneOrListName(groupNum), state.dataHeatBal->Zone);
1437 33 : if (ZoneNum > 0) {
1438 33 : if (std::any_of(thisVentilationMechanical.VentMechZone.begin(),
1439 : thisVentilationMechanical.VentMechZone.end(),
1440 87 : [ZoneNum](auto const &vmZone) { return vmZone.zoneNum == ZoneNum; })) {
1441 : // Disregard duplicate zone names, show warning and do not store data for this zone
1442 0 : ShowWarningError(state,
1443 0 : format("Zone name = {} for {} object = {}",
1444 : ventMechZoneOrListName(groupNum),
1445 : CurrentModuleObject,
1446 0 : thisVentilationMechanical.Name));
1447 0 : ShowContinueError(state, "is specified more than once. The first ventilation values specified for this zone will be used");
1448 0 : ShowContinueError(state, "and the rest will be ignored. Simulation will continue..");
1449 : } else {
1450 : // Store unique zone names
1451 33 : ++MechVentZoneCount;
1452 33 : auto &thisMechVentZone = thisVentilationMechanical.VentMechZone(MechVentZoneCount);
1453 33 : thisMechVentZone.zoneNum = ZoneNum;
1454 33 : thisMechVentZone.name = state.dataHeatBal->Zone(ZoneNum).Name;
1455 :
1456 : // Populating new temp array to hold design spec OA object for each zone
1457 33 : if (designSpecOAObjIndex(groupNum) > 0) {
1458 30 : thisMechVentZone.ZoneDesignSpecOAObjIndex = designSpecOAObjIndex(groupNum);
1459 : } else {
1460 3 : if (state.dataGlobal->DoZoneSizing) {
1461 1 : int ObjIndex = Util::FindItemInList(
1462 1 : ventMechZoneOrListName(groupNum), state.dataSize->ZoneSizingInput, &ZoneSizingInputData::ZoneName);
1463 1 : if (ObjIndex > 0) {
1464 1 : thisMechVentZone.ZoneDesignSpecOAObjIndex = state.dataSize->ZoneSizingInput(ObjIndex).ZoneDesignSpecOAIndex;
1465 : }
1466 : }
1467 : }
1468 : // Zone Air Distribution inputs
1469 33 : if (designSpecZoneADObjIndex(groupNum) > 0) {
1470 : // new DCV inputs
1471 19 : thisMechVentZone.ZoneDesignSpecADObjIndex = designSpecZoneADObjIndex(groupNum);
1472 : } else {
1473 14 : if (state.dataGlobal->DoZoneSizing) {
1474 0 : int ObjIndex = Util::FindItemInList(
1475 0 : ventMechZoneOrListName(groupNum), state.dataSize->ZoneSizingInput, &ZoneSizingInputData::ZoneName);
1476 0 : if (ObjIndex > 0) {
1477 0 : thisMechVentZone.ZoneDesignSpecADObjIndex = state.dataSize->ZoneSizingInput(ObjIndex).ZoneAirDistributionIndex;
1478 : }
1479 : }
1480 : }
1481 : }
1482 : } else {
1483 : // Not a zone name, must be a zone list
1484 0 : int ZoneListNum = Util::FindItemInList(ventMechZoneOrListName(groupNum), state.dataHeatBal->ZoneList);
1485 0 : if (ZoneListNum > 0) {
1486 0 : for (int ScanZoneListNum = 1; ScanZoneListNum <= state.dataHeatBal->ZoneList(ZoneListNum).NumOfZones; ++ScanZoneListNum) {
1487 : // check to make sure zone name is unique (not listed more than once)...
1488 0 : int zoneNum2 = state.dataHeatBal->ZoneList(ZoneListNum).Zone(ScanZoneListNum);
1489 0 : if (std::any_of(thisVentilationMechanical.VentMechZone.begin(),
1490 : thisVentilationMechanical.VentMechZone.end(),
1491 0 : [zoneNum2](auto const &vmZone) { return vmZone.zoneNum == zoneNum2; })) {
1492 : // Disregard duplicate zone names, show warning and do not store data for this zone
1493 0 : ShowWarningError(state,
1494 0 : format("Zone name = {} in ZoneList = {} for {} object = {}",
1495 0 : state.dataHeatBal->Zone(zoneNum2).Name,
1496 : ventMechZoneOrListName(groupNum),
1497 : CurrentModuleObject,
1498 0 : thisVentilationMechanical.Name));
1499 0 : ShowContinueError(state, "is a duplicate. The first ventilation values specified for this zone will be used ");
1500 0 : ShowContinueError(state, "and the rest will be ignored. The simulation will continue...");
1501 : } else {
1502 : // Store data for each zone name from zone list (duplicate zone names accounted for in
1503 : // HeatBalanceManager)
1504 0 : ++MechVentZoneCount;
1505 0 : auto &thisMechVentZone = thisVentilationMechanical.VentMechZone(MechVentZoneCount);
1506 0 : thisMechVentZone.zoneNum = zoneNum2;
1507 0 : thisMechVentZone.name = state.dataHeatBal->Zone(zoneNum2).Name;
1508 : // Populating new temp array to hold design spec OA object for each zone
1509 0 : if (designSpecOAObjIndex(groupNum) > 0) {
1510 0 : thisMechVentZone.ZoneDesignSpecOAObjIndex = designSpecOAObjIndex(groupNum);
1511 : } else {
1512 0 : if (state.dataGlobal->DoZoneSizing) {
1513 0 : int ObjIndex = Util::FindItemInList(
1514 0 : state.dataHeatBal->Zone(zoneNum2).Name, state.dataSize->ZoneSizingInput, &ZoneSizingInputData::ZoneName);
1515 0 : if (ObjIndex > 0) {
1516 0 : thisMechVentZone.ZoneDesignSpecOAObjIndex =
1517 0 : state.dataSize->ZoneSizingInput(ObjIndex).ZoneDesignSpecOAIndex;
1518 : }
1519 : }
1520 : }
1521 :
1522 0 : if (designSpecZoneADObjIndex(groupNum) > 0) {
1523 : // new DCV inputs
1524 0 : thisMechVentZone.ZoneDesignSpecADObjIndex = designSpecZoneADObjIndex(groupNum);
1525 : } else {
1526 0 : if (state.dataGlobal->DoZoneSizing) {
1527 0 : int ObjIndex = Util::FindItemInList(
1528 0 : state.dataHeatBal->Zone(zoneNum2).Name, state.dataSize->ZoneSizingInput, &ZoneSizingInputData::ZoneName);
1529 0 : if (ObjIndex > 0) {
1530 0 : thisMechVentZone.ZoneDesignSpecADObjIndex =
1531 0 : state.dataSize->ZoneSizingInput(ObjIndex).ZoneAirDistributionIndex;
1532 : }
1533 : }
1534 : }
1535 : }
1536 : }
1537 : }
1538 : }
1539 : }
1540 :
1541 17 : if (MechVentZoneCount <= 0) {
1542 0 : ShowSevereError(state,
1543 0 : format("{}{}=\"{}\", invalid input. At least one Zone or ZoneList Name must be entered.",
1544 : RoutineName,
1545 : CurrentModuleObject,
1546 0 : thisVentilationMechanical.Name));
1547 0 : ErrorsFound = true;
1548 : }
1549 :
1550 : // Overwrite previous number of zones with number that does not include duplicates
1551 17 : thisVentilationMechanical.NumofVentMechZones = MechVentZoneCount;
1552 :
1553 : // Loop over zones and fill OA and AD specs, if none were found, use defaults
1554 50 : for (int ventMechZoneNum = 1; ventMechZoneNum <= MechVentZoneCount; ++ventMechZoneNum) {
1555 33 : auto &thisVentMechZone = thisVentilationMechanical.VentMechZone(ventMechZoneNum);
1556 33 : if (thisVentMechZone.ZoneDesignSpecOAObjIndex == 0) {
1557 : // use defaults
1558 3 : thisVentMechZone.ZoneDesignSpecOAObjIndex = DataSizing::getDefaultOAReq(state);
1559 3 : ShowWarningError(state, format("{}{}=\"{}", RoutineName, CurrentModuleObject, thisVentilationMechanical.Name));
1560 6 : ShowContinueError(
1561 6 : state, format("Cannot locate a matching DesignSpecification:OutdoorAir object for Zone=\"{}\".", thisVentMechZone.name));
1562 9 : ShowContinueError(state, "Using default OA of 0.00944 m3/s-person and 0.0 m3/s-m2.");
1563 : }
1564 33 : assert(thisVentMechZone.ZoneDesignSpecOAObjIndex > 0);
1565 33 : auto &curOARequirements = state.dataSize->OARequirements(thisVentMechZone.ZoneDesignSpecOAObjIndex);
1566 33 : Real64 zoneOAAreaRate = curOARequirements.desFlowPerZoneArea(state, thisVentMechZone.zoneNum);
1567 33 : Real64 zoneOAPeopleRate = curOARequirements.desFlowPerZonePerson(state, thisVentMechZone.zoneNum);
1568 : // Get OA schedules - only used for system methods
1569 33 : if (thisVentilationMechanical.SystemOAMethod == DataSizing::SysOAMethod::IAQP ||
1570 31 : thisVentilationMechanical.SystemOAMethod == DataSizing::SysOAMethod::IAQPGC ||
1571 31 : thisVentilationMechanical.SystemOAMethod == DataSizing::SysOAMethod::IAQPCOM) {
1572 2 : bool notAllSame = false;
1573 2 : thisVentMechZone.zoneOASched = curOARequirements.getZoneFlowFracSched(state, notAllSame);
1574 2 : if (notAllSame) {
1575 0 : ShowWarningError(state,
1576 0 : format("{}{}=\"{}\", mixed schedules found for Zone={}.",
1577 : RoutineName,
1578 : CurrentModuleObject,
1579 0 : thisVentilationMechanical.Name,
1580 0 : thisVentMechZone.name));
1581 : }
1582 : }
1583 33 : if (thisVentilationMechanical.SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlSchOcc ||
1584 33 : thisVentilationMechanical.SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlDesOcc ||
1585 29 : thisVentilationMechanical.SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlDesOARate) {
1586 5 : bool notAllSame = false;
1587 5 : thisVentMechZone.zonePropCtlMinRateSched = curOARequirements.getZonePropCtlMinRateSched(state, notAllSame);
1588 5 : if (notAllSame) {
1589 0 : ShowWarningError(state,
1590 0 : format("{}{}=\"{}\", mixed schedules found for Zone={}.",
1591 : RoutineName,
1592 : CurrentModuleObject,
1593 0 : thisVentilationMechanical.Name,
1594 0 : thisVentMechZone.name));
1595 : }
1596 : }
1597 33 : if (thisVentilationMechanical.SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlDesOARate) {
1598 1 : if (zoneOAPeopleRate == 0.0 && zoneOAAreaRate == 0.0) {
1599 0 : ShowSevereError(state,
1600 0 : format("{}{}=\"{}\", invalid input with System Outdoor Air Method = ProportionalControlBasedOnDesignOARate.",
1601 : RoutineName,
1602 : CurrentModuleObject,
1603 0 : thisVentilationMechanical.Name));
1604 0 : ShowContinueError(state,
1605 : " The values of Outdoor Air Flow per Person and Outdoor Air Flow per Zone Floor Area in the same "
1606 : "object can not be zero.");
1607 0 : ErrorsFound = true;
1608 : }
1609 : }
1610 33 : if (thisVentilationMechanical.SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlSchOcc) {
1611 0 : if (curOARequirements.desFlowPerACH(state, thisVentMechZone.zoneNum) > 0.0 ||
1612 0 : curOARequirements.desFlowPerZone(state, thisVentMechZone.zoneNum) > 0.0) {
1613 0 : ShowWarningError(state,
1614 0 : format("{}=\"{}\", inappropriate outdoor air method", CurrentModuleObject, thisVentilationMechanical.Name));
1615 0 : ShowContinueError(
1616 0 : state, format("Inappropriate method for Design Specification Outdoor Air Object Name=\"{}\".", curOARequirements.Name));
1617 0 : ShowContinueError(state, format("For Zone=\"{}\".", thisVentMechZone.name));
1618 0 : ShowContinueError(state,
1619 : "Since System Outdoor Air Method= ProportionalControlBasedOnOccupancySchedule\", AirChanges/Hour or "
1620 : "Flow/Zone outdoor air methods are not valid. Simulation continues.... ");
1621 : }
1622 : }
1623 33 : if (thisVentilationMechanical.SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlDesOcc) {
1624 8 : if (curOARequirements.desFlowPerACH(state, thisVentMechZone.zoneNum) > 0.0 ||
1625 4 : curOARequirements.desFlowPerZone(state, thisVentMechZone.zoneNum) > 0.0) {
1626 0 : ShowWarningError(state,
1627 0 : format("{}=\"{}\", inappropriate outdoor air method", CurrentModuleObject, thisVentilationMechanical.Name));
1628 0 : ShowContinueError(
1629 0 : state, format("Inappropriate method for Design Specification Outdoor Air Object Name=\"{}\".", curOARequirements.Name));
1630 0 : ShowContinueError(state, format("For Zone=\"{}\".", thisVentMechZone.name));
1631 0 : ShowContinueError(state,
1632 : "Since System Outdoor Air Method= ProportionalControlBasedOnDesignOccupancy\", AirChanges/Hour or "
1633 : "Flow/Zone outdoor air methods are not valid. Simulation continues.... ");
1634 : }
1635 : }
1636 33 : int zoneAirDistObjIndex = thisVentMechZone.ZoneDesignSpecADObjIndex;
1637 33 : if (zoneAirDistObjIndex > 0) {
1638 19 : auto const &curZoneAirDistribution(state.dataSize->ZoneAirDistribution(zoneAirDistObjIndex));
1639 19 : thisVentMechZone.ZoneADEffCooling = curZoneAirDistribution.ZoneADEffCooling;
1640 19 : thisVentMechZone.ZoneADEffHeating = curZoneAirDistribution.ZoneADEffHeating;
1641 19 : thisVentMechZone.zoneADEffSched = curZoneAirDistribution.zoneADEffSched;
1642 19 : thisVentMechZone.ZoneSecondaryRecirculation = curZoneAirDistribution.ZoneSecondaryRecirculation;
1643 : } else { // use defaults
1644 14 : thisVentMechZone.ZoneADEffCooling = 1.0;
1645 14 : thisVentMechZone.ZoneADEffHeating = 1.0;
1646 14 : thisVentMechZone.ZoneSecondaryRecirculation = 0.0;
1647 14 : ShowWarningError(state, format("{}{}=\"{}\"", RoutineName, CurrentModuleObject, thisVentilationMechanical.Name));
1648 28 : ShowContinueError(
1649 : state,
1650 28 : format("Cannot locate a matching DesignSpecification:ZoneAirDistribution object for Zone=\"{}\".", thisVentMechZone.name));
1651 42 : ShowContinueError(state, "Using default zone air distribution effectiveness of 1.0 for heating and cooling.");
1652 : }
1653 :
1654 : // Error check to see if a single duct air terminal is assigned to a zone that has zone secondary recirculation
1655 33 : if (thisVentMechZone.ZoneSecondaryRecirculation > 0.0) {
1656 0 : int ZoneNum = thisVentMechZone.zoneNum;
1657 0 : if (ZoneNum > 0) {
1658 0 : int EquipListIndex = state.dataZoneEquip->ZoneEquipConfig(ZoneNum).EquipListIndex;
1659 0 : if (EquipListIndex > 0) {
1660 0 : for (int EquipListNum = 1; EquipListNum <= state.dataZoneEquip->NumOfZoneEquipLists; ++EquipListNum) {
1661 0 : if (EquipListNum == EquipListIndex) {
1662 0 : for (int EquipNum = 1; EquipNum <= state.dataZoneEquip->ZoneEquipList(EquipListNum).NumOfEquipTypes; ++EquipNum) {
1663 0 : if (Util::SameString(state.dataZoneEquip->ZoneEquipList(EquipListNum).EquipTypeName(EquipNum),
1664 : "ZONEHVAC:AIRDISTRIBUTIONUNIT")) {
1665 0 : for (int ADUNum = 1; ADUNum <= (int)state.dataDefineEquipment->AirDistUnit.size(); ++ADUNum) {
1666 0 : if (Util::SameString(state.dataZoneEquip->ZoneEquipList(EquipListNum).EquipName(EquipNum),
1667 0 : state.dataDefineEquipment->AirDistUnit(ADUNum).Name)) {
1668 0 : if ((state.dataDefineEquipment->AirDistUnit(ADUNum).EquipTypeEnum(EquipNum) ==
1669 0 : DataDefineEquip::ZnAirLoopEquipType::SingleDuctVAVReheat) ||
1670 0 : (state.dataDefineEquipment->AirDistUnit(ADUNum).EquipTypeEnum(EquipNum) ==
1671 0 : DataDefineEquip::ZnAirLoopEquipType::SingleDuctConstVolNoReheat) ||
1672 0 : (state.dataDefineEquipment->AirDistUnit(ADUNum).EquipTypeEnum(EquipNum) ==
1673 0 : DataDefineEquip::ZnAirLoopEquipType::SingleDuctConstVolReheat) ||
1674 0 : (state.dataDefineEquipment->AirDistUnit(ADUNum).EquipTypeEnum(EquipNum) ==
1675 0 : DataDefineEquip::ZnAirLoopEquipType::SingleDuctVAVNoReheat) ||
1676 0 : (state.dataDefineEquipment->AirDistUnit(ADUNum).EquipTypeEnum(EquipNum) ==
1677 0 : DataDefineEquip::ZnAirLoopEquipType::SingleDuctVAVReheatVSFan) ||
1678 0 : (state.dataDefineEquipment->AirDistUnit(ADUNum).EquipTypeEnum(EquipNum) ==
1679 0 : DataDefineEquip::ZnAirLoopEquipType::SingleDuctCBVAVReheat) ||
1680 0 : (state.dataDefineEquipment->AirDistUnit(ADUNum).EquipTypeEnum(EquipNum) ==
1681 0 : DataDefineEquip::ZnAirLoopEquipType::SingleDuctCBVAVNoReheat) ||
1682 0 : (state.dataDefineEquipment->AirDistUnit(ADUNum).EquipTypeEnum(EquipNum) ==
1683 0 : DataDefineEquip::ZnAirLoopEquipType::SingleDuctConstVolCooledBeam) ||
1684 0 : (state.dataDefineEquipment->AirDistUnit(ADUNum).EquipTypeEnum(EquipNum) ==
1685 0 : DataDefineEquip::ZnAirLoopEquipType::SingleDuctConstVolFourPipeBeam) ||
1686 0 : (state.dataDefineEquipment->AirDistUnit(ADUNum).EquipTypeEnum(EquipNum) ==
1687 : DataDefineEquip::ZnAirLoopEquipType::DualDuctVAVOutdoorAir)) {
1688 0 : ShowWarningError(state,
1689 0 : format("{}=\"{}\", inappropriate use of Zone secondary recirculation",
1690 : CurrentModuleObject,
1691 0 : thisVentilationMechanical.Name));
1692 0 : ShowContinueError(state,
1693 : "A zone secondary recirculation fraction is specified for zone served by ");
1694 0 : ShowContinueError(state,
1695 0 : format("...terminal unit \"{}\" , that indicates a single path system",
1696 0 : state.dataDefineEquipment->AirDistUnit(ADUNum).Name));
1697 0 : ShowContinueError(state, format("For Zone=\"{}\".", thisVentMechZone.name));
1698 0 : ShowContinueError(state, "...The zone secondary recirculation for that zone was set to 0.0");
1699 0 : thisVentMechZone.ZoneSecondaryRecirculation = 0.0;
1700 : }
1701 0 : goto EquipLoop_exit;
1702 : }
1703 : }
1704 : }
1705 : }
1706 : }
1707 : }
1708 0 : EquipLoop_exit:;
1709 : }
1710 : }
1711 : }
1712 33 : if (zoneOAPeopleRate <= 0.0 && thisVentilationMechanical.DCVFlag) {
1713 10 : ShowWarningError(state, format("{}=\"{}\", Zone OA/person rate", CurrentModuleObject, thisVentilationMechanical.Name));
1714 10 : ShowContinueError(state, format("For Zone=\"{}\".", thisVentMechZone.name));
1715 20 : ShowContinueError(state,
1716 20 : format("Zone outside air per person rate not set in Design Specification Outdoor Air Object=\"{}\".",
1717 10 : state.dataSize->OARequirements(thisVentMechZone.ZoneDesignSpecOAObjIndex).Name));
1718 : }
1719 :
1720 33 : if (zoneOAAreaRate < 0.0) {
1721 0 : ShowSevereError(state,
1722 0 : format("{}=\"{}\", invalid Outdoor Air flow per area", CurrentModuleObject, thisVentilationMechanical.Name));
1723 0 : ShowContinueError(state, format("For Zone=\"{}\".", thisVentMechZone.name));
1724 0 : ShowContinueError(state,
1725 0 : format("invalid Outdoor Air flow per area specified in object=\"{}\". Value must be >= 0.0.",
1726 0 : state.dataSize->OARequirements(thisVentMechZone.ZoneDesignSpecOAObjIndex).Name));
1727 0 : ErrorsFound = true;
1728 : }
1729 33 : if (zoneOAPeopleRate < 0.0) {
1730 0 : ShowSevereError(state,
1731 0 : format("{}=\"{}\", invalid Outdoor Air flow per person", CurrentModuleObject, thisVentilationMechanical.Name));
1732 0 : ShowContinueError(state, format("For Zone=\"{}\".", thisVentMechZone.name));
1733 0 : ShowContinueError(state,
1734 0 : format("invalid Outdoor Air flow per person specified in object \"{}\". Value must be >= 0.0.",
1735 0 : state.dataSize->OARequirements(thisVentMechZone.ZoneDesignSpecOAObjIndex).Name));
1736 0 : ErrorsFound = true;
1737 : }
1738 : }
1739 17 : }
1740 :
1741 : // Link OA controller object with mechanical ventilation object
1742 28 : for (int OAControllerNum = 1; OAControllerNum <= state.dataMixedAir->NumOAControllers; ++OAControllerNum) {
1743 11 : state.dataMixedAir->OAController(OAControllerNum).VentMechObjectNum = Util::FindItemInList(
1744 11 : state.dataMixedAir->OAController(OAControllerNum).VentilationMechanicalName, state.dataMixedAir->VentilationMechanical);
1745 11 : if (state.dataMixedAir->OAController(OAControllerNum).VentMechObjectNum == 0 &&
1746 0 : !state.dataMixedAir->OAController(OAControllerNum).VentilationMechanicalName.empty()) {
1747 0 : ShowSevereError(state,
1748 0 : format("{}=\"{}\", non-match to Controller:OutdoorAir",
1749 : CurrentModuleObject,
1750 0 : state.dataMixedAir->OAController(OAControllerNum).VentilationMechanicalName));
1751 0 : ShowContinueError(
1752 0 : state, format("Invalid specified in Controller:OutdoorAir object = {}", state.dataMixedAir->OAController(OAControllerNum).Name));
1753 0 : ShowContinueError(state,
1754 0 : format("{} object name must match the {} object name specified in Controller:OutdoorAir.",
1755 : CurrentModuleObject,
1756 : CurrentModuleObject));
1757 0 : ErrorsFound = true;
1758 : }
1759 : }
1760 :
1761 : // write to .eio file
1762 : static constexpr std::string_view Format_700(
1763 : "!<Controller:MechanicalVentilation>,Name,Availability Schedule Name,Demand Controlled Ventilation "
1764 : "{Yes/No},System Outdoor Air Method,Zone Maximum Outdoor Air Fraction,Number of Zones,Zone Name,DSOA "
1765 : "Name,DSZAD Name");
1766 17 : print(state.files.eio, "{}\n", Format_700);
1767 34 : for (auto const &ventMech : state.dataMixedAir->VentilationMechanical) {
1768 17 : print(state.files.eio, " Controller:MechanicalVentilation,{},{},", ventMech.Name, ventMech.availSched ? ventMech.availSched->Name : "");
1769 :
1770 17 : print(state.files.eio, format("{},", yesNoNames[(int)ventMech.DCVFlag]));
1771 :
1772 17 : if (ventMech.SystemOAMethod != DataSizing::SysOAMethod::Invalid) {
1773 17 : print(state.files.eio, printSysOAMethod[(int)ventMech.SystemOAMethod]);
1774 : } else {
1775 0 : print(state.files.eio, "Invalid/Unknown,");
1776 : }
1777 :
1778 17 : print(state.files.eio, "{:.2R},", ventMech.ZoneMaxOAFraction);
1779 17 : print(state.files.eio, "{}", ventMech.NumofVentMechZones);
1780 :
1781 50 : for (int jZone = 1; jZone <= ventMech.NumofVentMechZones; ++jZone) {
1782 33 : auto &thisVentMechZone = ventMech.VentMechZone(jZone);
1783 33 : std::string const dsoaName = thisVentMechZone.ZoneDesignSpecOAObjIndex > 0
1784 33 : ? state.dataSize->OARequirements(thisVentMechZone.ZoneDesignSpecOAObjIndex).Name
1785 66 : : "";
1786 33 : std::string const adName = thisVentMechZone.ZoneDesignSpecADObjIndex > 0
1787 19 : ? state.dataSize->ZoneAirDistribution(thisVentMechZone.ZoneDesignSpecADObjIndex).Name
1788 66 : : "";
1789 33 : print(state.files.eio, ",{},{},{}", state.dataHeatBal->Zone(thisVentMechZone.zoneNum).Name, dsoaName, adName);
1790 33 : }
1791 17 : print(state.files.eio, "\n");
1792 : }
1793 :
1794 : } // Number of Mechanical Ventilation Objects > 0
1795 :
1796 53 : AlphArray.deallocate();
1797 53 : NumArray.deallocate();
1798 53 : lNumericBlanks.deallocate();
1799 53 : lAlphaBlanks.deallocate();
1800 53 : cAlphaFields.deallocate();
1801 53 : cNumericFields.deallocate();
1802 :
1803 53 : if (ErrorsFound) {
1804 0 : ShowFatalError(state, format("{}Errors found when getting {} inputs.", RoutineName, CurrentModuleObject));
1805 : }
1806 53 : }
1807 :
1808 53 : void AllocateOAControllers(EnergyPlusData &state)
1809 : {
1810 : // PURPOSE OF THIS SUBROUTINE:
1811 : // Allocate the OA controller arrays which are shared by Controller:OutdoorAir and ZoneHVAC:EnergyRecoveryVentilator:Controller
1812 :
1813 53 : if (state.dataMixedAir->AllocateOAControllersFlag) {
1814 106 : state.dataMixedAir->NumOAControllers =
1815 53 : state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, CurrentModuleObjects[static_cast<int>(CMO::OAController)]);
1816 106 : state.dataMixedAir->NumERVControllers =
1817 53 : state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, CurrentModuleObjects[static_cast<int>(CMO::ERVController)]);
1818 53 : state.dataMixedAir->NumOAControllers += state.dataMixedAir->NumERVControllers;
1819 53 : state.dataMixedAir->OAController.allocate(state.dataMixedAir->NumOAControllers);
1820 53 : state.dataMixedAir->OAControllerUniqueNames.reserve(static_cast<unsigned>(state.dataMixedAir->NumOAControllers));
1821 53 : state.dataMixedAir->AllocateOAControllersFlag = false;
1822 : }
1823 53 : }
1824 :
1825 125 : void GetOAMixerInputs(EnergyPlusData &state)
1826 : {
1827 :
1828 : // SUBROUTINE INFORMATION:
1829 : // AUTHOR Fred Buhl
1830 : // DATE WRITTEN Oct 1998
1831 :
1832 : // PURPOSE OF THIS SUBROUTINE
1833 : // Input the OAMixer data and store it in the OAMixer array.
1834 :
1835 : // METHODOLOGY EMPLOYED:
1836 : // Use the Get routines from the InputProcessor module.
1837 :
1838 : // SUBROUTINE PARAMETER DEFINITIONS:
1839 : static constexpr std::string_view RoutineName("GetOAMixerInputs: "); // include trailing blank space
1840 :
1841 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1842 :
1843 : int NumNums; // Number of REAL(r64) numbers returned by GetObjectItem
1844 : int NumAlphas; // Number of alphanumerics returned by GetObjectItem
1845 : int NumArg; // Number of arguments from GetObjectDefMaxArgs call
1846 125 : Array1D<Real64> NumArray; // array that holds numeric input values
1847 125 : Array1D_string AlphArray; // array that holds alpha input values
1848 125 : Array1D_string cAlphaFields; // Alpha field names
1849 125 : Array1D_string cNumericFields; // Numeric field names
1850 125 : Array1D_bool lAlphaBlanks; // Logical array, alpha field input BLANK = .TRUE.
1851 125 : Array1D_bool lNumericBlanks; // Logical array, numeric field input BLANK = .TRUE.
1852 125 : bool ErrorsFound(false);
1853 :
1854 125 : if (!state.dataMixedAir->GetOAMixerInputFlag) return;
1855 :
1856 250 : state.dataInputProcessing->inputProcessor->getObjectDefMaxArgs(
1857 125 : state, CurrentModuleObjects[static_cast<int>(CMO::OAMixer)], NumArg, NumAlphas, NumNums);
1858 :
1859 125 : AlphArray.allocate(NumAlphas);
1860 125 : NumArray.dimension(NumNums, 0.0);
1861 125 : lNumericBlanks.dimension(NumNums, true);
1862 125 : lAlphaBlanks.dimension(NumAlphas, true);
1863 125 : cAlphaFields.allocate(NumAlphas);
1864 125 : cNumericFields.allocate(NumNums);
1865 :
1866 125 : std::string_view const CurrentModuleObject = CurrentModuleObjects[static_cast<int>(CMO::OAMixer)];
1867 :
1868 125 : state.dataMixedAir->NumOAMixers = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, CurrentModuleObject);
1869 :
1870 125 : if (state.dataMixedAir->NumOAMixers > 0) {
1871 :
1872 99 : state.dataMixedAir->OAMixer.allocate(state.dataMixedAir->NumOAMixers);
1873 : int IOStat;
1874 :
1875 244 : for (int OutAirNum = 1; OutAirNum <= state.dataMixedAir->NumOAMixers; ++OutAirNum) {
1876 145 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
1877 : CurrentModuleObject,
1878 : OutAirNum,
1879 : AlphArray,
1880 : NumAlphas,
1881 : NumArray,
1882 : NumNums,
1883 : IOStat,
1884 : lNumericBlanks,
1885 : lAlphaBlanks,
1886 : cAlphaFields,
1887 : cNumericFields);
1888 : // no need to check if AlphaArray(1) is empty since Json will catch missing required fields
1889 145 : state.dataMixedAir->OAMixer(OutAirNum).Name = AlphArray(1);
1890 290 : state.dataMixedAir->OAMixer(OutAirNum).MixNode = NodeInputManager::GetOnlySingleNode(state,
1891 145 : AlphArray(2),
1892 : ErrorsFound,
1893 : DataLoopNode::ConnectionObjectType::OutdoorAirMixer,
1894 145 : AlphArray(1),
1895 : DataLoopNode::NodeFluidType::Air,
1896 : DataLoopNode::ConnectionType::Outlet,
1897 : NodeInputManager::CompFluidStream::Primary,
1898 : ObjectIsNotParent);
1899 : // Set connection type to 'Inlet', because this is not necessarily directly from
1900 : // outside air. Outside Air Inlet Node List will set the connection to outside air
1901 145 : state.dataMixedAir->OAMixer(OutAirNum).InletNode =
1902 290 : NodeInputManager::GetOnlySingleNode(state,
1903 145 : AlphArray(3),
1904 : ErrorsFound,
1905 : DataLoopNode::ConnectionObjectType::OutdoorAirMixer,
1906 145 : AlphArray(1),
1907 : DataLoopNode::NodeFluidType::Air,
1908 : DataLoopNode::ConnectionType::Inlet,
1909 : NodeInputManager::CompFluidStream::Primary,
1910 : ObjectIsNotParent);
1911 290 : state.dataMixedAir->OAMixer(OutAirNum).RelNode = NodeInputManager::GetOnlySingleNode(state,
1912 145 : AlphArray(4),
1913 : ErrorsFound,
1914 : DataLoopNode::ConnectionObjectType::OutdoorAirMixer,
1915 145 : AlphArray(1),
1916 : DataLoopNode::NodeFluidType::Air,
1917 : DataLoopNode::ConnectionType::ReliefAir,
1918 : NodeInputManager::CompFluidStream::Primary,
1919 : ObjectIsNotParent);
1920 290 : state.dataMixedAir->OAMixer(OutAirNum).RetNode = NodeInputManager::GetOnlySingleNode(state,
1921 145 : AlphArray(5),
1922 : ErrorsFound,
1923 : DataLoopNode::ConnectionObjectType::OutdoorAirMixer,
1924 145 : AlphArray(1),
1925 : DataLoopNode::NodeFluidType::Air,
1926 : DataLoopNode::ConnectionType::Inlet,
1927 : NodeInputManager::CompFluidStream::Primary,
1928 : ObjectIsNotParent);
1929 : // Check for dupes in the four nodes.
1930 145 : if (state.dataMixedAir->OAMixer(OutAirNum).MixNode == state.dataMixedAir->OAMixer(OutAirNum).InletNode) {
1931 0 : ShowSevereError(state,
1932 0 : format("{} = {} {} = {} duplicates the {}.",
1933 : CurrentModuleObject,
1934 0 : state.dataMixedAir->OAMixer(OutAirNum).Name,
1935 : cAlphaFields(3),
1936 0 : state.dataLoopNodes->NodeID(state.dataMixedAir->OAMixer(OutAirNum).InletNode),
1937 : cAlphaFields(2)));
1938 0 : ErrorsFound = true;
1939 145 : } else if (state.dataMixedAir->OAMixer(OutAirNum).MixNode == state.dataMixedAir->OAMixer(OutAirNum).RelNode) {
1940 0 : ShowSevereError(state,
1941 0 : format("{} = {} {} = {} duplicates the {}.",
1942 : CurrentModuleObject,
1943 0 : state.dataMixedAir->OAMixer(OutAirNum).Name,
1944 : cAlphaFields(4),
1945 0 : state.dataLoopNodes->NodeID(state.dataMixedAir->OAMixer(OutAirNum).RelNode),
1946 : cAlphaFields(2)));
1947 0 : ErrorsFound = true;
1948 145 : } else if (state.dataMixedAir->OAMixer(OutAirNum).MixNode == state.dataMixedAir->OAMixer(OutAirNum).RetNode) {
1949 0 : ShowSevereError(state,
1950 0 : format("{} = {} {} = {} duplicates the {}.",
1951 : CurrentModuleObject,
1952 0 : state.dataMixedAir->OAMixer(OutAirNum).Name,
1953 : cAlphaFields(5),
1954 0 : state.dataLoopNodes->NodeID(state.dataMixedAir->OAMixer(OutAirNum).RetNode),
1955 : cAlphaFields(2)));
1956 0 : ErrorsFound = true;
1957 : }
1958 :
1959 145 : if (state.dataMixedAir->OAMixer(OutAirNum).InletNode == state.dataMixedAir->OAMixer(OutAirNum).RelNode) {
1960 0 : ShowSevereError(state,
1961 0 : format("{} = {} {} = {} duplicates the {}.",
1962 : CurrentModuleObject,
1963 0 : state.dataMixedAir->OAMixer(OutAirNum).Name,
1964 : cAlphaFields(4),
1965 0 : state.dataLoopNodes->NodeID(state.dataMixedAir->OAMixer(OutAirNum).RelNode),
1966 : cAlphaFields(3)));
1967 0 : ErrorsFound = true;
1968 145 : } else if (state.dataMixedAir->OAMixer(OutAirNum).InletNode == state.dataMixedAir->OAMixer(OutAirNum).RetNode) {
1969 0 : ShowSevereError(state,
1970 0 : format("{} = {} {} = {} duplicates the {}.",
1971 : CurrentModuleObject,
1972 0 : state.dataMixedAir->OAMixer(OutAirNum).Name,
1973 : cAlphaFields(5),
1974 0 : state.dataLoopNodes->NodeID(state.dataMixedAir->OAMixer(OutAirNum).RetNode),
1975 : cAlphaFields(3)));
1976 0 : ErrorsFound = true;
1977 : }
1978 :
1979 145 : if (state.dataMixedAir->OAMixer(OutAirNum).RelNode == state.dataMixedAir->OAMixer(OutAirNum).RetNode) {
1980 0 : ShowSevereError(state,
1981 0 : format("{} = {} {} = {} duplicates the {}.",
1982 : CurrentModuleObject,
1983 0 : state.dataMixedAir->OAMixer(OutAirNum).Name,
1984 : cAlphaFields(5),
1985 0 : state.dataLoopNodes->NodeID(state.dataMixedAir->OAMixer(OutAirNum).RetNode),
1986 : cAlphaFields(4)));
1987 0 : ErrorsFound = true;
1988 : }
1989 435 : BranchNodeConnections::TestCompSet(
1990 145 : state, CurrentModuleObject, state.dataMixedAir->OAMixer(OutAirNum).Name, AlphArray(3), AlphArray(2), "Air Nodes");
1991 : }
1992 : }
1993 :
1994 125 : if (ErrorsFound) {
1995 0 : ShowFatalError(state, format("{}Errors found in getting {}", RoutineName, CurrentModuleObject));
1996 : }
1997 :
1998 125 : state.dataMixedAir->GetOAMixerInputFlag = false;
1999 125 : }
2000 :
2001 56 : void ProcessOAControllerInputs(EnergyPlusData &state,
2002 : std::string_view const CurrentModuleObject,
2003 : int const OutAirNum,
2004 : Array1D_string const &AlphArray,
2005 : int const NumAlphas,
2006 : Array1D<Real64> const &NumArray,
2007 : int const NumNums,
2008 : Array1D_bool const &lNumericBlanks, // Unused
2009 : Array1D_bool const &lAlphaBlanks,
2010 : Array1D_string const &cAlphaFields,
2011 : Array1D_string const &cNumericFields, // Unused
2012 : bool &ErrorsFound // If errors found in input
2013 : )
2014 : {
2015 :
2016 : // SUBROUTINE INFORMATION:
2017 : // AUTHOR Fred Buhl
2018 : // DATE WRITTEN Oct 1998
2019 : // MODIFIED Shirey/Raustad FSEC, June 2003, Jan 2004
2020 : // Mangesh Basarkar, 06/2011: Getting zone OA specifications from Design Specification Object
2021 : // Tianzhen Hong, 3/2012: getting zone air distribution effectiveness and secondary recirculation
2022 : // from DesignSpecification:ZoneAirDistribution objects
2023 : // RE-ENGINEERED MJW: Split out processing controller:outdoorair input to facilitate unit testing, Feb 2015
2024 :
2025 : // PURPOSE OF THIS SUBROUTINE
2026 : // Input the OAController data and store it in the OAController array.
2027 :
2028 : // SUBROUTINE PARAMETER DEFINITIONS:
2029 : static constexpr std::string_view RoutineName("GetOAControllerInputs: "); // include trailing blank space
2030 : static constexpr std::string_view routineName = "GetOAControllerInputs";
2031 :
2032 56 : ErrorObjectHeader eoh{routineName, CurrentModuleObject, AlphArray(1)};
2033 :
2034 56 : state.dataMixedAir->OAController(OutAirNum).Name = AlphArray(1);
2035 56 : state.dataMixedAir->OAController(OutAirNum).ControllerType = MixedAirControllerType::ControllerOutsideAir;
2036 56 : state.dataMixedAir->OAController(OutAirNum).MaxOA = NumArray(2);
2037 56 : state.dataMixedAir->OAController(OutAirNum).MinOA = NumArray(1);
2038 56 : state.dataMixedAir->OAController(OutAirNum).MixNode =
2039 112 : NodeInputManager::GetOnlySingleNode(state,
2040 56 : AlphArray(4),
2041 : ErrorsFound,
2042 : DataLoopNode::ConnectionObjectType::ControllerOutdoorAir,
2043 56 : AlphArray(1),
2044 : DataLoopNode::NodeFluidType::Air,
2045 : DataLoopNode::ConnectionType::Sensor,
2046 : NodeInputManager::CompFluidStream::Primary,
2047 : ObjectIsNotParent);
2048 112 : state.dataMixedAir->OAController(OutAirNum).OANode = NodeInputManager::GetOnlySingleNode(state,
2049 56 : AlphArray(5),
2050 : ErrorsFound,
2051 : DataLoopNode::ConnectionObjectType::ControllerOutdoorAir,
2052 56 : AlphArray(1),
2053 : DataLoopNode::NodeFluidType::Air,
2054 : DataLoopNode::ConnectionType::Actuator,
2055 : NodeInputManager::CompFluidStream::Primary,
2056 : ObjectIsNotParent);
2057 56 : if (!OutAirNodeManager::CheckOutAirNodeNumber(state, state.dataMixedAir->OAController(OutAirNum).OANode)) {
2058 18 : ShowWarningError(state,
2059 18 : format("{}=\"{}\": {}=\"{}\" is not an OutdoorAir:Node.", CurrentModuleObject, AlphArray(1), cAlphaFields(5), AlphArray(5)));
2060 27 : ShowContinueError(state, "Confirm that this is the intended source for the outdoor air stream.");
2061 : }
2062 56 : if (Util::SameString(AlphArray(6), "NoEconomizer")) {
2063 33 : state.dataMixedAir->OAController(OutAirNum).Econo = EconoOp::NoEconomizer;
2064 23 : } else if (Util::SameString(AlphArray(6), "FixedDryBulb")) {
2065 3 : state.dataMixedAir->OAController(OutAirNum).Econo = EconoOp::FixedDryBulb;
2066 20 : } else if (Util::SameString(AlphArray(6), "FixedEnthalpy")) {
2067 0 : state.dataMixedAir->OAController(OutAirNum).Econo = EconoOp::FixedEnthalpy;
2068 20 : } else if (Util::SameString(AlphArray(6), "FixedDewPointAndDryBulb")) {
2069 0 : state.dataMixedAir->OAController(OutAirNum).Econo = EconoOp::FixedDewPointAndDryBulb;
2070 20 : } else if (Util::SameString(AlphArray(6), "DifferentialDryBulb")) {
2071 18 : state.dataMixedAir->OAController(OutAirNum).Econo = EconoOp::DifferentialDryBulb;
2072 2 : } else if (Util::SameString(AlphArray(6), "DifferentialEnthalpy")) {
2073 2 : state.dataMixedAir->OAController(OutAirNum).Econo = EconoOp::DifferentialEnthalpy;
2074 0 : } else if (Util::SameString(AlphArray(6), "DifferentialDryBulbAndEnthalpy")) {
2075 0 : state.dataMixedAir->OAController(OutAirNum).Econo = EconoOp::DifferentialDryBulbAndEnthalpy;
2076 0 : } else if (Util::SameString(AlphArray(6), "ElectronicEnthalpy")) {
2077 0 : state.dataMixedAir->OAController(OutAirNum).Econo = EconoOp::ElectronicEnthalpy;
2078 : } else {
2079 0 : ShowSevereError(state, format("{}=\"{}\" invalid {}=\"{}\" value.", CurrentModuleObject, AlphArray(1), cAlphaFields(6), AlphArray(6)));
2080 0 : ErrorsFound = true;
2081 : }
2082 : // Bypass choice - Added by Amit for new feature implementation
2083 56 : if (Util::SameString(AlphArray(7), "ModulateFlow")) {
2084 48 : state.dataMixedAir->OAController(OutAirNum).EconBypass = false;
2085 8 : } else if (Util::SameString(AlphArray(7), "MinimumFlowWithBypass")) {
2086 8 : state.dataMixedAir->OAController(OutAirNum).EconBypass = true;
2087 : } else {
2088 0 : ShowSevereError(state, format("{}=\"{}\" invalid {}=\"{}\" value.", CurrentModuleObject, AlphArray(1), cAlphaFields(7), AlphArray(7)));
2089 0 : ErrorsFound = true;
2090 : }
2091 :
2092 56 : if (Util::SameString(AlphArray(9), "NoLockout")) {
2093 55 : state.dataMixedAir->OAController(OutAirNum).Lockout = LockoutType::NoLockoutPossible;
2094 1 : } else if (Util::SameString(AlphArray(9), "LockoutWithHeating")) {
2095 1 : state.dataMixedAir->OAController(OutAirNum).Lockout = LockoutType::LockoutWithHeatingPossible;
2096 0 : } else if (Util::SameString(AlphArray(9), "LockoutWithCompressor")) {
2097 0 : state.dataMixedAir->OAController(OutAirNum).Lockout = LockoutType::LockoutWithCompressorPossible;
2098 : } else {
2099 0 : ShowSevereError(state, format("{}=\"{}\" invalid {}=\"{}\" value.", CurrentModuleObject, AlphArray(1), cAlphaFields(9), AlphArray(9)));
2100 0 : ErrorsFound = true;
2101 : }
2102 56 : if (Util::SameString(AlphArray(10), "FixedMinimum")) {
2103 34 : state.dataMixedAir->OAController(OutAirNum).FixedMin = true;
2104 : } else {
2105 22 : state.dataMixedAir->OAController(OutAirNum).FixedMin = false;
2106 : }
2107 56 : if (lNumericBlanks(3)) {
2108 37 : state.dataMixedAir->OAController(OutAirNum).TempLim = HVAC::BlankNumeric;
2109 : } else {
2110 19 : state.dataMixedAir->OAController(OutAirNum).TempLim = NumArray(3);
2111 : }
2112 :
2113 56 : if (lNumericBlanks(4)) {
2114 45 : state.dataMixedAir->OAController(OutAirNum).EnthLim = HVAC::BlankNumeric;
2115 : } else {
2116 11 : state.dataMixedAir->OAController(OutAirNum).EnthLim = NumArray(4);
2117 : }
2118 56 : if (lNumericBlanks(5)) {
2119 54 : state.dataMixedAir->OAController(OutAirNum).DPTempLim = HVAC::BlankNumeric;
2120 : } else {
2121 2 : state.dataMixedAir->OAController(OutAirNum).DPTempLim = NumArray(5);
2122 : }
2123 :
2124 56 : if (lNumericBlanks(6)) {
2125 28 : state.dataMixedAir->OAController(OutAirNum).TempLowLim = HVAC::BlankNumeric;
2126 : } else {
2127 28 : state.dataMixedAir->OAController(OutAirNum).TempLowLim = NumArray(6);
2128 : }
2129 :
2130 56 : if (!lAlphaBlanks(8)) {
2131 0 : state.dataMixedAir->OAController(OutAirNum).EnthalpyCurvePtr = Curve::GetCurveIndex(state, AlphArray(8)); // convert curve name to number
2132 0 : if (state.dataMixedAir->OAController(OutAirNum).EnthalpyCurvePtr == 0) {
2133 0 : ShowSevereError(state,
2134 0 : format("{}=\"{}\" invalid {}=\"{}\" not found.", CurrentModuleObject, AlphArray(1), cAlphaFields(8), AlphArray(8)));
2135 0 : ErrorsFound = true;
2136 : } else {
2137 : // Verify Curve Object, only legal types are Quadratic and Cubic
2138 0 : ErrorsFound |= Curve::CheckCurveDims(state,
2139 0 : state.dataMixedAir->OAController(OutAirNum).EnthalpyCurvePtr, // Curve index
2140 : {1}, // Valid dimensions
2141 : RoutineName, // Routine name
2142 : CurrentModuleObject, // Object Type
2143 0 : state.dataMixedAir->OAController(OutAirNum).Name, // Object Name
2144 0 : cAlphaFields(8)); // Field Name
2145 : }
2146 : }
2147 :
2148 56 : state.dataMixedAir->OAController(OutAirNum).RelNode =
2149 112 : NodeInputManager::GetOnlySingleNode(state,
2150 56 : AlphArray(2),
2151 : ErrorsFound,
2152 : DataLoopNode::ConnectionObjectType::ControllerOutdoorAir,
2153 56 : AlphArray(1),
2154 : DataLoopNode::NodeFluidType::Air,
2155 : DataLoopNode::ConnectionType::Actuator,
2156 : NodeInputManager::CompFluidStream::Primary,
2157 : ObjectIsNotParent);
2158 56 : state.dataMixedAir->OAController(OutAirNum).RetNode =
2159 112 : NodeInputManager::GetOnlySingleNode(state,
2160 56 : AlphArray(3),
2161 : ErrorsFound,
2162 : DataLoopNode::ConnectionObjectType::ControllerOutdoorAir,
2163 56 : AlphArray(1),
2164 : DataLoopNode::NodeFluidType::Air,
2165 : DataLoopNode::ConnectionType::Sensor,
2166 : NodeInputManager::CompFluidStream::Primary,
2167 : ObjectIsNotParent);
2168 :
2169 56 : if (lAlphaBlanks(11)) {
2170 21 : } else if ((state.dataMixedAir->OAController(OutAirNum).minOASched = Sched::GetSchedule(state, AlphArray(11))) == nullptr) {
2171 0 : ShowSevereItemNotFound(state, eoh, cAlphaFields(11), AlphArray(11));
2172 0 : ErrorsFound = true;
2173 : }
2174 :
2175 : // Changed by Amit for new feature implementation
2176 56 : if (lAlphaBlanks(12)) {
2177 0 : } else if ((state.dataMixedAir->OAController(OutAirNum).minOAflowSched = Sched::GetSchedule(state, AlphArray(12))) == nullptr) {
2178 0 : ShowSevereItemNotFound(state, eoh, cAlphaFields(12), AlphArray(12));
2179 0 : ErrorsFound = true;
2180 : }
2181 :
2182 56 : if (lAlphaBlanks(13)) {
2183 0 : } else if ((state.dataMixedAir->OAController(OutAirNum).maxOAflowSched = Sched::GetSchedule(state, AlphArray(13))) == nullptr) {
2184 0 : ShowSevereItemNotFound(state, eoh, cAlphaFields(13), AlphArray(13));
2185 0 : ErrorsFound = true;
2186 : }
2187 56 : state.dataMixedAir->OAController(OutAirNum).VentilationMechanicalName = AlphArray(14);
2188 :
2189 : // Check for a time of day economizer control schedule
2190 56 : state.dataMixedAir->OAController(OutAirNum).economizerOASched = Sched::GetSchedule(state, AlphArray(15));
2191 :
2192 : // High humidity control option can be used with any economizer flag
2193 56 : if (Util::SameString(AlphArray(16), "Yes")) {
2194 :
2195 2 : state.dataMixedAir->OAController(OutAirNum).HumidistatZoneNum = Util::FindItemInList(AlphArray(17), state.dataHeatBal->Zone);
2196 :
2197 : // Get the node number for the zone with the humidistat
2198 2 : if (state.dataMixedAir->OAController(OutAirNum).HumidistatZoneNum > 0) {
2199 2 : bool AirNodeFound = false;
2200 2 : bool AirLoopFound = false;
2201 2 : bool OASysFound = false;
2202 4 : for (int ControlledZoneNum = 1; ControlledZoneNum <= state.dataGlobal->NumOfZones; ++ControlledZoneNum) {
2203 2 : if (ControlledZoneNum != state.dataMixedAir->OAController(OutAirNum).HumidistatZoneNum) continue;
2204 : // Find the controlled zone number for the specified humidistat location
2205 2 : state.dataMixedAir->OAController(OutAirNum).NodeNumofHumidistatZone =
2206 2 : state.dataZoneEquip->ZoneEquipConfig(ControlledZoneNum).ZoneNode;
2207 : // Determine which OA System uses this OA Controller
2208 2 : int OASysIndex = 0;
2209 2 : for (int OASysNum = 1; OASysNum <= state.dataAirLoop->NumOASystems; ++OASysNum) {
2210 2 : for (int OAControllerNum = 1; OAControllerNum <= state.dataAirLoop->OutsideAirSys(OASysNum).NumControllers; ++OAControllerNum) {
2211 4 : if (!Util::SameString(state.dataAirLoop->OutsideAirSys(OASysNum).ControllerType(OAControllerNum), CurrentModuleObject) ||
2212 2 : !Util::SameString(state.dataAirLoop->OutsideAirSys(OASysNum).ControllerName(OAControllerNum),
2213 2 : state.dataMixedAir->OAController(OutAirNum).Name))
2214 0 : continue;
2215 2 : OASysIndex = OASysNum;
2216 2 : OASysFound = true;
2217 2 : break;
2218 : }
2219 2 : if (OASysFound) break;
2220 : }
2221 : // Determine if controller is on air loop served by the humidistat location specified
2222 4 : for (int zoneInNode = 1; zoneInNode <= state.dataZoneEquip->ZoneEquipConfig(ControlledZoneNum).NumInletNodes; ++zoneInNode) {
2223 2 : int AirLoopNumber = state.dataZoneEquip->ZoneEquipConfig(ControlledZoneNum).InletNodeAirLoopNum(zoneInNode);
2224 2 : if (AirLoopNumber > 0 && OASysIndex > 0) {
2225 2 : for (int BranchNum = 1; BranchNum <= state.dataAirSystemsData->PrimaryAirSystems(AirLoopNumber).NumBranches; ++BranchNum) {
2226 2 : for (int CompNum = 1;
2227 2 : CompNum <= state.dataAirSystemsData->PrimaryAirSystems(AirLoopNumber).Branch(BranchNum).TotalComponents;
2228 : ++CompNum) {
2229 2 : if (!Util::SameString(state.dataAirSystemsData->PrimaryAirSystems(AirLoopNumber).Branch(BranchNum).Comp(CompNum).Name,
2230 6 : state.dataAirLoop->OutsideAirSys(OASysIndex).Name) ||
2231 6 : !Util::SameString(
2232 2 : state.dataAirSystemsData->PrimaryAirSystems(AirLoopNumber).Branch(BranchNum).Comp(CompNum).TypeOf,
2233 : "AirLoopHVAC:OutdoorAirSystem"))
2234 0 : continue;
2235 2 : AirLoopFound = true;
2236 2 : break;
2237 : }
2238 2 : if (AirLoopFound) break;
2239 : }
2240 2 : for (int HStatZoneNum = 1; HStatZoneNum <= state.dataZoneCtrls->NumHumidityControlZones; ++HStatZoneNum) {
2241 2 : if (state.dataZoneCtrls->HumidityControlZone(HStatZoneNum).ActualZoneNum !=
2242 2 : state.dataMixedAir->OAController(OutAirNum).HumidistatZoneNum)
2243 0 : continue;
2244 2 : AirNodeFound = true;
2245 2 : break;
2246 : }
2247 2 : } else {
2248 0 : if (OASysIndex == 0) {
2249 0 : ShowSevereError(
2250 : state,
2251 0 : format("Did not find an AirLoopHVAC:OutdoorAirSystem for {} = \"{}\"",
2252 0 : MixedAirControllerTypeNames[static_cast<int>(state.dataMixedAir->OAController(OutAirNum).ControllerType)],
2253 0 : state.dataMixedAir->OAController(OutAirNum).Name));
2254 0 : ErrorsFound = true;
2255 : }
2256 : }
2257 : }
2258 : }
2259 2 : if (!AirNodeFound) {
2260 0 : ShowSevereError(state,
2261 0 : format("Did not find Air Node (Zone with Humidistat), {} = \"{}\"",
2262 0 : MixedAirControllerTypeNames[static_cast<int>(state.dataMixedAir->OAController(OutAirNum).ControllerType)],
2263 0 : state.dataMixedAir->OAController(OutAirNum).Name));
2264 0 : ShowContinueError(state, format("Specified {} = {}", cAlphaFields(17), AlphArray(17)));
2265 0 : ShowContinueError(state,
2266 : "Both a ZoneHVAC:EquipmentConnections object and a ZoneControl:Humidistat object must be specified for this zone.");
2267 0 : ErrorsFound = true;
2268 : }
2269 2 : if (!AirLoopFound) {
2270 0 : ShowSevereError(state,
2271 0 : format("Did not find correct Primary Air Loop for {} = \"{}\"",
2272 0 : MixedAirControllerTypeNames[static_cast<int>(state.dataMixedAir->OAController(OutAirNum).ControllerType)],
2273 0 : state.dataMixedAir->OAController(OutAirNum).Name));
2274 0 : ShowContinueError(state, format("{} = {} is not served by this Primary Air Loop equipment.", cAlphaFields(17), AlphArray(17)));
2275 0 : ErrorsFound = true;
2276 : }
2277 : } else {
2278 0 : ShowSevereError(state,
2279 0 : format("Did not find Air Node (Zone with Humidistat), {} = \"{}\"",
2280 0 : MixedAirControllerTypeNames[static_cast<int>(state.dataMixedAir->OAController(OutAirNum).ControllerType)],
2281 0 : state.dataMixedAir->OAController(OutAirNum).Name));
2282 0 : ShowContinueError(state, format("Specified {} = {}", cAlphaFields(17), AlphArray(17)));
2283 0 : ShowContinueError(state,
2284 : "Both a ZoneHVAC:EquipmentConnections object and a ZoneControl:Humidistat object must be specified for this zone.");
2285 0 : ErrorsFound = true;
2286 : }
2287 :
2288 2 : state.dataMixedAir->OAController(OutAirNum).HighRHOAFlowRatio = NumArray(7);
2289 2 : if (state.dataMixedAir->OAController(OutAirNum).HighRHOAFlowRatio <= 0.0 && NumNums > 6) {
2290 0 : ShowWarningError(state, format("{} \"{}\"", CurrentModuleObject, state.dataMixedAir->OAController(OutAirNum).Name));
2291 0 : ShowContinueError(state, format(" {} must be greater than 0.", cNumericFields(7)));
2292 0 : ShowContinueError(state, format(" {} is reset to 1 and the simulation continues.", cNumericFields(7)));
2293 0 : state.dataMixedAir->OAController(OutAirNum).HighRHOAFlowRatio = 1.0;
2294 : }
2295 :
2296 2 : if (Util::SameString(AlphArray(16), "Yes") && state.dataMixedAir->OAController(OutAirNum).FixedMin) {
2297 0 : if (state.dataMixedAir->OAController(OutAirNum).MaxOA > 0.0 && state.dataMixedAir->OAController(OutAirNum).MinOA != AutoSize) {
2298 0 : Real64 OAFlowRatio = state.dataMixedAir->OAController(OutAirNum).MinOA / state.dataMixedAir->OAController(OutAirNum).MaxOA;
2299 0 : if (state.dataMixedAir->OAController(OutAirNum).HighRHOAFlowRatio < OAFlowRatio) {
2300 0 : ShowWarningError(state, format("{} \"{}\"", CurrentModuleObject, state.dataMixedAir->OAController(OutAirNum).Name));
2301 0 : ShowContinueError(state, "... A fixed minimum outside air flow rate and high humidity control have been specified.");
2302 0 : ShowContinueError(
2303 : state,
2304 0 : format("... The {} is less than the ratio of the outside air controllers minimum to maximum outside air flow rate.",
2305 : cNumericFields(7)));
2306 0 : ShowContinueError(
2307 0 : state, format("... Controller {} = {:.4T} m3/s.", cNumericFields(1), state.dataMixedAir->OAController(OutAirNum).MinOA));
2308 0 : ShowContinueError(
2309 0 : state, format("... Controller {} = {:.4T} m3/s.", cNumericFields(2), state.dataMixedAir->OAController(OutAirNum).MaxOA));
2310 0 : ShowContinueError(state, format("... Controller minimum to maximum flow ratio = {:.4T}.", OAFlowRatio));
2311 0 : ShowContinueError(state,
2312 0 : format("... {} = {:.4T}.", cNumericFields(7), state.dataMixedAir->OAController(OutAirNum).HighRHOAFlowRatio));
2313 : }
2314 : }
2315 : }
2316 :
2317 2 : if (NumAlphas >= 18) {
2318 1 : if (Util::SameString(AlphArray(18), "Yes")) {
2319 1 : state.dataMixedAir->OAController(OutAirNum).ModifyDuringHighOAMoisture = false;
2320 0 : } else if (Util::SameString(AlphArray(18), "No")) {
2321 0 : state.dataMixedAir->OAController(OutAirNum).ModifyDuringHighOAMoisture = true;
2322 : } else {
2323 0 : ShowSevereError(state,
2324 0 : format("{} \"{}\", invalid field value", CurrentModuleObject, state.dataMixedAir->OAController(OutAirNum).Name));
2325 0 : ShowContinueError(state, format("...{}=\"{}\" - valid values are \"Yes\" or \"No\".", cAlphaFields(18), AlphArray(18)));
2326 0 : ErrorsFound = true;
2327 : }
2328 : } else {
2329 1 : if (state.dataMixedAir->OAController(OutAirNum).Econo == EconoOp::NoEconomizer) {
2330 0 : state.dataMixedAir->OAController(OutAirNum).ModifyDuringHighOAMoisture = true;
2331 : } else {
2332 1 : state.dataMixedAir->OAController(OutAirNum).ModifyDuringHighOAMoisture = false;
2333 2 : ShowWarningError(state,
2334 2 : format("{} \"{}\", missing field value", CurrentModuleObject, state.dataMixedAir->OAController(OutAirNum).Name));
2335 1 : ShowContinueError(state, format("...{} will default to Yes when {}= \"Yes\"", cAlphaFields(18), cAlphaFields(16)));
2336 : }
2337 : }
2338 :
2339 54 : } else if (Util::SameString(AlphArray(16), "No") || lAlphaBlanks(16)) {
2340 54 : if (NumAlphas >= 18) {
2341 18 : if (!Util::SameString(AlphArray(18), "Yes") && !Util::SameString(AlphArray(18), "No")) {
2342 0 : ShowSevereError(state,
2343 0 : format("{} \"{}\", invalid field value", CurrentModuleObject, state.dataMixedAir->OAController(OutAirNum).Name));
2344 0 : ShowContinueError(state, format("...{}=\"{}\" - valid values are \"Yes\" or \"No\".", cAlphaFields(18), AlphArray(18)));
2345 0 : ErrorsFound = true;
2346 : }
2347 : }
2348 : } else { // Invalid field 16
2349 0 : ShowSevereError(state, format("{} \"{}\", invalid field value", CurrentModuleObject, state.dataMixedAir->OAController(OutAirNum).Name));
2350 0 : ShowContinueError(state, format("...{}=\"{}\" - valid values are \"Yes\" or \"No\".", cAlphaFields(16), AlphArray(16)));
2351 0 : ErrorsFound = true;
2352 0 : if (NumAlphas >= 18) {
2353 0 : if (!Util::SameString(AlphArray(18), "Yes") && !Util::SameString(AlphArray(18), "No")) {
2354 0 : ShowSevereError(state,
2355 0 : format("{} \"{}\", invalid field value", CurrentModuleObject, state.dataMixedAir->OAController(OutAirNum).Name));
2356 0 : ShowContinueError(state, format("...{}=\"{}\" - valid values are \"Yes\" or \"No\".", cAlphaFields(18), AlphArray(18)));
2357 0 : ErrorsFound = true;
2358 : }
2359 : }
2360 : }
2361 :
2362 56 : if (NumAlphas > 18) {
2363 14 : if (!lAlphaBlanks(19)) {
2364 14 : if (Util::SameString(AlphArray(19), "BypassWhenWithinEconomizerLimits")) {
2365 6 : state.dataMixedAir->OAController(OutAirNum).HeatRecoveryBypassControlType = HVAC::BypassWhenWithinEconomizerLimits;
2366 8 : } else if (Util::SameString(AlphArray(19), "BypassWhenOAFlowGreaterThanMinimum")) {
2367 8 : state.dataMixedAir->OAController(OutAirNum).HeatRecoveryBypassControlType = HVAC::BypassWhenOAFlowGreaterThanMinimum;
2368 : } else {
2369 0 : ShowWarningError(state, format("{}=\"{}\" invalid {}=\"{}\".", CurrentModuleObject, AlphArray(1), cAlphaFields(19), AlphArray(19)));
2370 0 : ShowContinueError(state, "...assuming \"BypassWhenWithinEconomizerLimits\" and the simulation continues.");
2371 0 : state.dataMixedAir->OAController(OutAirNum).HeatRecoveryBypassControlType = HVAC::BypassWhenWithinEconomizerLimits;
2372 : }
2373 : }
2374 : }
2375 :
2376 56 : if (NumAlphas > 19) {
2377 0 : if (!lAlphaBlanks(20)) {
2378 0 : if (Util::SameString(AlphArray(20), "EconomizerFirst")) {
2379 0 : state.dataMixedAir->OAController(OutAirNum).EconomizerStagingType = HVAC::EconomizerStagingType::EconomizerFirst;
2380 : } else {
2381 0 : state.dataMixedAir->OAController(OutAirNum).EconomizerStagingType = HVAC::EconomizerStagingType::InterlockedWithMechanicalCooling;
2382 : }
2383 : }
2384 : }
2385 :
2386 56 : if (Util::SameString(AlphArray(16), "Yes") && state.dataMixedAir->OAController(OutAirNum).Econo == EconoOp::NoEconomizer) {
2387 0 : ShowWarningError(state,
2388 0 : format("{} \"{}\"",
2389 0 : MixedAirControllerTypeNames[static_cast<int>(state.dataMixedAir->OAController(OutAirNum).ControllerType)],
2390 0 : state.dataMixedAir->OAController(OutAirNum).Name));
2391 0 : ShowContinueError(state, format("...Economizer operation must be enabled when {} is set to YES.", cAlphaFields(16)));
2392 0 : ShowContinueError(state, "...The high humidity control option will be disabled and the simulation continues.");
2393 : }
2394 :
2395 56 : state.dataMixedAir->OAController(OutAirNum).MixedAirSPMNum =
2396 56 : SetPointManager::GetMixedAirNumWithCoilFreezingCheck(state, state.dataMixedAir->OAController(OutAirNum).MixNode);
2397 56 : }
2398 :
2399 : // End of Get Input subroutines for the Module
2400 : //******************************************************************************
2401 :
2402 : // Beginning Initialization Section of the Module
2403 : //******************************************************************************
2404 :
2405 92985 : void InitOutsideAirSys(EnergyPlusData &state, int const(OASysNum), int const AirLoopNum)
2406 : {
2407 :
2408 : // SUBROUTINE INFORMATION:
2409 : // AUTHOR Fred Buhl
2410 : // DATE WRITTEN Oct 1998
2411 :
2412 : // PURPOSE OF THIS SUBROUTINE
2413 : // Initialize the OutsideAirSys data structure
2414 :
2415 92985 : if (state.dataAirLoop->OutsideAirSys(OASysNum).AirLoopDOASNum > -1) return;
2416 :
2417 78417 : if (state.dataMixedAir->initOASysFlag(OASysNum)) {
2418 25 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).OASysNum = OASysNum;
2419 25 : state.dataMixedAir->initOASysFlag(OASysNum) = false;
2420 : }
2421 : }
2422 :
2423 78423 : void InitOAController(EnergyPlusData &state, int const OAControllerNum, bool const FirstHVACIteration, int const AirLoopNum)
2424 : {
2425 :
2426 : // SUBROUTINE INFORMATION:
2427 : // AUTHOR Fred Buhl
2428 : // DATE WRITTEN Oct 1998
2429 : // MODIFIED Shirey/Raustad FSEC, June/Aug 2003, Feb 2004
2430 : // Tianzhen Hong, Feb 2009 for DCV
2431 : // Tianzhen Hong, Aug 2013 for economizer faults
2432 :
2433 : // PURPOSE OF THIS SUBROUTINE
2434 : // Initialize the OAController data structure with input node data
2435 :
2436 78423 : bool ErrorsFound = false;
2437 :
2438 78423 : auto &thisOAController(state.dataMixedAir->OAController(OAControllerNum));
2439 :
2440 78423 : if (state.dataMixedAir->InitOAControllerOneTimeFlag) {
2441 29 : state.dataMixedAir->OAControllerMyOneTimeFlag.dimension(state.dataMixedAir->NumOAControllers, true);
2442 29 : state.dataMixedAir->OAControllerMyEnvrnFlag.dimension(state.dataMixedAir->NumOAControllers, true);
2443 29 : state.dataMixedAir->OAControllerMySizeFlag.dimension(state.dataMixedAir->NumOAControllers, true);
2444 29 : state.dataMixedAir->MechVentCheckFlag.dimension(state.dataMixedAir->NumOAControllers, true);
2445 29 : state.dataMixedAir->InitOAControllerSetPointCheckFlag.dimension(state.dataMixedAir->NumOAControllers, true);
2446 29 : state.dataMixedAir->InitOAControllerOneTimeFlag = false;
2447 : }
2448 78423 : if (state.dataMixedAir->OAControllerMyOneTimeFlag(OAControllerNum)) {
2449 : // Determine Inlet node index for OAController, not a user input for controller, but is obtained from OutsideAirSys and OAMixer
2450 29 : switch (thisOAController.ControllerType) {
2451 29 : case MixedAirControllerType::ControllerOutsideAir: {
2452 29 : int thisOASys = 0;
2453 29 : for (int OASysNum = 1; OASysNum <= state.dataAirLoop->NumOASystems; ++OASysNum) {
2454 : // find which OAsys has this controller
2455 29 : int found = Util::FindItemInList(thisOAController.Name,
2456 29 : state.dataAirLoop->OutsideAirSys(OASysNum).ControllerName,
2457 29 : isize(state.dataAirLoop->OutsideAirSys(OASysNum).ControllerName));
2458 29 : if (found != 0) {
2459 29 : thisOASys = OASysNum;
2460 29 : state.dataAirLoop->OutsideAirSys(thisOASys).OAControllerIndex = GetOAController(state, thisOAController.Name);
2461 29 : break; // we found it
2462 : }
2463 : }
2464 29 : if (thisOASys == 0) {
2465 0 : ShowSevereError(state, format("InitOAController: Did not find OAController=\"{}\".", thisOAController.Name));
2466 0 : ShowContinueError(state, "in list of valid OA Controllers.");
2467 0 : ErrorsFound = true;
2468 : }
2469 29 : int thisNumForMixer = Util::FindItem(CurrentModuleObjects[static_cast<int>(CMO::OAMixer)],
2470 29 : state.dataAirLoop->OutsideAirSys(thisOASys).ComponentType,
2471 29 : isize(state.dataAirLoop->OutsideAirSys(thisOASys).ComponentType));
2472 29 : if (thisNumForMixer != 0) {
2473 29 : std::string_view const equipName = state.dataAirLoop->OutsideAirSys(thisOASys).ComponentName(thisNumForMixer);
2474 29 : int thisMixerIndex = Util::FindItemInList(equipName, state.dataMixedAir->OAMixer);
2475 29 : if (thisMixerIndex != 0) {
2476 29 : thisOAController.InletNode = state.dataMixedAir->OAMixer(thisMixerIndex).InletNode;
2477 : } else {
2478 0 : ShowSevereError(state, format("InitOAController: Did not find OAMixer=\"{}\".", equipName));
2479 0 : ShowContinueError(state, "in list of valid OA Mixers.");
2480 0 : ErrorsFound = true;
2481 : }
2482 : } else {
2483 0 : ShowSevereError(state, "InitOAController: Did not find OutdoorAir:Mixer Component=\"OutdoorAir:Mixer\".");
2484 0 : ShowContinueError(state, "in list of valid OA Components.");
2485 0 : ErrorsFound = true;
2486 : }
2487 :
2488 29 : if (thisOAController.InletNode == 0) { // throw an error
2489 0 : ShowSevereError(
2490 : state,
2491 0 : format("InitOAController: Failed to find proper inlet node for OutdoorAir:Mixer and Controller = {}", thisOAController.Name));
2492 0 : ErrorsFound = true;
2493 : }
2494 29 : } break;
2495 0 : case MixedAirControllerType::ControllerStandAloneERV: {
2496 : // set the inlet node to also equal the OA node because this is a special controller for economizing stand alone ERV
2497 : // with the assumption that equipment is bypassed....
2498 0 : thisOAController.InletNode = thisOAController.OANode;
2499 0 : } break;
2500 0 : default: {
2501 0 : ShowSevereError(state,
2502 0 : format("InitOAController: Failed to find ControllerType: {}",
2503 0 : MixedAirControllerTypeNames[static_cast<int>(thisOAController.ControllerType)]));
2504 0 : ErrorsFound = true;
2505 0 : } break;
2506 : }
2507 :
2508 29 : state.dataMixedAir->OAControllerMyOneTimeFlag(OAControllerNum) = false;
2509 : }
2510 :
2511 156846 : if (!state.dataGlobal->SysSizingCalc && state.dataMixedAir->InitOAControllerSetPointCheckFlag(OAControllerNum) &&
2512 156846 : state.dataHVACGlobal->DoSetPointTest && !FirstHVACIteration) {
2513 22 : int MixedAirNode = thisOAController.MixNode;
2514 22 : if (MixedAirNode > 0) {
2515 : // IF (OAController(OAControllerNum)%Econo == 1 .AND. .NOT. AirLoopControlInfo(AirLoopNum)%CyclingFan) THEN
2516 22 : if (thisOAController.Econo > EconoOp::NoEconomizer && state.dataAirLoop->AirLoopControlInfo(AirLoopNum).AnyContFan) {
2517 8 : if (state.dataLoopNodes->Node(MixedAirNode).TempSetPoint == SensedNodeFlagValue) {
2518 0 : if (!state.dataGlobal->AnyEnergyManagementSystemInModel) {
2519 0 : ShowSevereError(state, format("MixedAir: Missing temperature setpoint for economizer controller {}", thisOAController.Name));
2520 0 : ShowContinueError(state, format("Node Referenced (by Controller)={}", state.dataLoopNodes->NodeID(MixedAirNode)));
2521 0 : ShowContinueError(
2522 : state, " use a Setpoint Manager with Control Variable = \"Temperature\" to establish a setpoint at the mixed air node.");
2523 0 : state.dataHVACGlobal->SetPointErrorFlag = true;
2524 : } else {
2525 : // add call to check node in EMS
2526 0 : EMSManager::CheckIfNodeSetPointManagedByEMS(
2527 0 : state, MixedAirNode, HVAC::CtrlVarType::Temp, state.dataHVACGlobal->SetPointErrorFlag);
2528 0 : if (state.dataHVACGlobal->SetPointErrorFlag) {
2529 0 : ShowSevereError(state,
2530 0 : format("MixedAir: Missing temperature setpoint for economizer controller {}", thisOAController.Name));
2531 0 : ShowContinueError(state, format("Node Referenced (by Controller)={}", state.dataLoopNodes->NodeID(MixedAirNode)));
2532 0 : ShowContinueError(state,
2533 : " use a Setpoint Manager with Control Variable = \"Temperature\" to establish a setpoint at the "
2534 : "mixed air node.");
2535 0 : ShowContinueError(state, "Or add EMS Actuator to provide temperature setpoint at this node");
2536 : }
2537 : }
2538 : }
2539 : }
2540 : }
2541 :
2542 22 : state.dataMixedAir->InitOAControllerSetPointCheckFlag(OAControllerNum) = false;
2543 : }
2544 :
2545 78423 : if (!state.dataGlobal->SysSizingCalc && state.dataMixedAir->OAControllerMySizeFlag(OAControllerNum)) {
2546 29 : thisOAController.SizeOAController(state);
2547 29 : if (AirLoopNum > 0) {
2548 28 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).OACtrlNum = OAControllerNum;
2549 28 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).OACtrlName = thisOAController.Name;
2550 28 : if (thisOAController.Lockout == LockoutType::LockoutWithHeatingPossible) {
2551 0 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).CanLockoutEconoWithHeating = true;
2552 0 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).CanLockoutEconoWithCompressor = false;
2553 0 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).CanNotLockoutEcono = false;
2554 28 : } else if (thisOAController.Lockout == LockoutType::LockoutWithCompressorPossible) {
2555 0 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).CanLockoutEconoWithHeating = false;
2556 0 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).CanLockoutEconoWithCompressor = true;
2557 0 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).CanNotLockoutEcono = false;
2558 : } else {
2559 28 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).CanLockoutEconoWithHeating = false;
2560 28 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).CanLockoutEconoWithCompressor = false;
2561 28 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).CanNotLockoutEcono = true;
2562 : }
2563 : }
2564 29 : if ((thisOAController.MaxOA - thisOAController.MinOA) < -HVAC::SmallAirVolFlow) {
2565 0 : ShowSevereError(state, format("For Controller:OutdoorAir: {}", thisOAController.Name));
2566 0 : ShowContinueError(state,
2567 0 : format(" maximum outdoor air flow rate ({:.4R}) < minimum outdoor air flow rate ({:.4R})",
2568 0 : thisOAController.MaxOA,
2569 0 : thisOAController.MinOA));
2570 0 : ShowContinueError(state,
2571 : " To set the minimum outside air flow rate use the \"Design (minimum) outdoor air flow rate\" field in the "
2572 : "Sizing:System object");
2573 0 : ErrorsFound = true;
2574 : }
2575 :
2576 29 : if (AirLoopNum > 0) {
2577 28 : Real64 DesSupplyVolFlowRate = state.dataAirLoop->AirLoopFlow(AirLoopNum).DesSupply / state.dataEnvrn->StdRhoAir;
2578 28 : if ((thisOAController.MinOA - DesSupplyVolFlowRate) > 0.0001) {
2579 0 : ShowWarningError(state,
2580 0 : format("InitOAController: Minimum Outdoor Air Flow Rate for Controller:OutdoorAir={} is greater than Design Supply "
2581 : "Air Flow Rate for AirLoopHVAC={}.",
2582 0 : thisOAController.Name,
2583 0 : state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).Name));
2584 0 : ShowContinueError(state,
2585 0 : format("...Minimum Outdoor Air Flow Rate={:.6R} will be reset to loop Design Supply Air Flow Rate={:.6R}",
2586 0 : thisOAController.MinOA,
2587 : DesSupplyVolFlowRate));
2588 0 : thisOAController.MinOA = DesSupplyVolFlowRate;
2589 28 : } else if ((thisOAController.MinOA - DesSupplyVolFlowRate) > 0.0) {
2590 : // If difference is tiny, reset silently
2591 0 : thisOAController.MinOA = DesSupplyVolFlowRate;
2592 : }
2593 28 : if ((thisOAController.MaxOA - DesSupplyVolFlowRate) > 0.0001) {
2594 4 : ShowWarningError(state,
2595 4 : format("InitOAController: Maximum Outdoor Air Flow Rate for Controller:OutdoorAir={} is greater than Design Supply "
2596 : "Air Flow Rate for AirLoopHVAC={}.",
2597 2 : thisOAController.Name,
2598 2 : state.dataAirSystemsData->PrimaryAirSystems(AirLoopNum).Name));
2599 4 : ShowContinueError(state,
2600 4 : format("...Maximum Outdoor Air Flow Rate={:.6R} will be reset to loop Design Supply Air Flow Rate={:.6R}",
2601 2 : thisOAController.MaxOA,
2602 : DesSupplyVolFlowRate));
2603 2 : thisOAController.MaxOA = DesSupplyVolFlowRate;
2604 26 : } else if ((thisOAController.MaxOA - DesSupplyVolFlowRate) > 0.0) {
2605 : // If difference is tiny, reset silently
2606 0 : thisOAController.MaxOA = DesSupplyVolFlowRate;
2607 : }
2608 :
2609 : // Check if system has a Sizing:System object and a sizing run has been done
2610 28 : bool SizingDesRunThisAirSys = false;
2611 28 : CheckThisAirSystemForSizing(state, AirLoopNum, SizingDesRunThisAirSys);
2612 :
2613 : // Get design outdoor air flow rate
2614 28 : if (SizingDesRunThisAirSys && thisOAController.VentMechObjectNum > 0) {
2615 7 : state.dataMixedAir->VentilationMechanical(thisOAController.VentMechObjectNum).SysDesOA =
2616 7 : state.dataSize->FinalSysSizing(AirLoopNum).DesOutAirVolFlow;
2617 : }
2618 : }
2619 :
2620 29 : state.dataMixedAir->OAControllerMySizeFlag(OAControllerNum) = false;
2621 : }
2622 :
2623 78423 : if (state.dataGlobal->BeginEnvrnFlag && state.dataMixedAir->OAControllerMyEnvrnFlag(OAControllerNum)) {
2624 77 : Real64 RhoAirStdInit = state.dataEnvrn->StdRhoAir;
2625 77 : thisOAController.MinOAMassFlowRate = thisOAController.MinOA * RhoAirStdInit;
2626 77 : thisOAController.MaxOAMassFlowRate = thisOAController.MaxOA * RhoAirStdInit;
2627 77 : state.dataMixedAir->OAControllerMyEnvrnFlag(OAControllerNum) = false;
2628 77 : state.dataLoopNodes->Node(thisOAController.OANode).MassFlowRateMax = thisOAController.MaxOAMassFlowRate;
2629 :
2630 : // predefined reporting
2631 77 : if (thisOAController.Econo > EconoOp::NoEconomizer) {
2632 22 : std::string_view const equipName = thisOAController.Name;
2633 : // 90.1 descriptor for economizer controls. Changed by Amit for New Feature implementation
2634 22 : if (thisOAController.Econo == EconoOp::DifferentialEnthalpy) {
2635 4 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchEcoKind, equipName, "DifferentialEnthalpy");
2636 18 : } else if (thisOAController.Econo == EconoOp::DifferentialDryBulb) {
2637 10 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchEcoKind, equipName, "DifferentialDryBulb");
2638 8 : } else if (thisOAController.Econo == EconoOp::FixedEnthalpy) {
2639 0 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchEcoKind, equipName, "FixedEnthalpy");
2640 8 : } else if (thisOAController.Econo == EconoOp::FixedDryBulb) {
2641 8 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchEcoKind, equipName, "FixedDryBulb");
2642 : } else {
2643 0 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchEcoKind, equipName, "Other");
2644 : }
2645 :
2646 22 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchEcoMinOA, equipName, thisOAController.MinOA);
2647 22 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchEcoMaxOA, equipName, thisOAController.MaxOA);
2648 : // EnergyPlus input echos for economizer controls. Changed by Amit for new feature implementation
2649 22 : if (thisOAController.Econo == EconoOp::DifferentialDryBulb) {
2650 10 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchEcoRetTemp, equipName, "Yes");
2651 : } else {
2652 12 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchEcoRetTemp, equipName, "No");
2653 : }
2654 22 : if (thisOAController.Econo == EconoOp::DifferentialEnthalpy) {
2655 4 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchEcoRetTemp, equipName, "Yes");
2656 : } else {
2657 18 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchEcoRetTemp, equipName, "No");
2658 : }
2659 22 : if (thisOAController.Econo == EconoOp::FixedDryBulb) {
2660 8 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchEcoRetTemp, equipName, thisOAController.TempLim);
2661 : } else {
2662 14 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchEcoRetTemp, equipName, "-");
2663 : }
2664 22 : if (thisOAController.Econo == EconoOp::FixedEnthalpy) {
2665 0 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchEcoRetTemp, equipName, thisOAController.EnthLim);
2666 : } else {
2667 22 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchEcoRetTemp, equipName, "-");
2668 : }
2669 : }
2670 : }
2671 :
2672 78423 : if (!state.dataGlobal->BeginEnvrnFlag) {
2673 77640 : state.dataMixedAir->OAControllerMyEnvrnFlag(OAControllerNum) = true;
2674 : }
2675 :
2676 78423 : if (state.dataMixedAir->MechVentCheckFlag(OAControllerNum)) {
2677 : // Make these checks only once at the beginning of the simulation
2678 :
2679 : // Make sure all air loop zones and air loop zones with people objects are covered by mechanical ventilation
2680 : // Issue a warning only if the zone is not accounted for in the associated mechanical ventilation object
2681 29 : if (thisOAController.VentMechObjectNum > 0) {
2682 9 : auto &vent_mech(state.dataMixedAir->VentilationMechanical(thisOAController.VentMechObjectNum));
2683 :
2684 : // Make sure all zones with mechanical ventilation are on the correct air loop
2685 9 : int TempMechVentArrayCounter = 0;
2686 26 : for (int NumMechVentZone = 1; NumMechVentZone <= vent_mech.NumofVentMechZones; ++NumMechVentZone) {
2687 17 : auto &thisMechVentZone = vent_mech.VentMechZone(NumMechVentZone);
2688 17 : int ZoneNum = thisMechVentZone.zoneNum;
2689 17 : auto const &zone(state.dataHeatBal->Zone(ZoneNum));
2690 17 : bool FoundZone = false;
2691 :
2692 26 : for (int AirLoopZoneInfoZoneNum = 1; AirLoopZoneInfoZoneNum <= state.dataAirLoop->AirLoopZoneInfo(AirLoopNum).NumZones;
2693 : ++AirLoopZoneInfoZoneNum) {
2694 26 : int NumZone = state.dataAirLoop->AirLoopZoneInfo(AirLoopNum).ActualZoneNumber(AirLoopZoneInfoZoneNum);
2695 26 : if (ZoneNum == NumZone) {
2696 17 : FoundZone = true;
2697 17 : ++TempMechVentArrayCounter;
2698 17 : if (TempMechVentArrayCounter < NumMechVentZone) { // Copy to lower index
2699 0 : auto &tempMechVentZone = vent_mech.VentMechZone(TempMechVentArrayCounter);
2700 0 : tempMechVentZone.zoneNum = thisMechVentZone.zoneNum;
2701 0 : tempMechVentZone.ZoneDesignSpecOAObjIndex = thisMechVentZone.ZoneDesignSpecOAObjIndex;
2702 0 : tempMechVentZone.zoneOASched = thisMechVentZone.zoneOASched;
2703 0 : tempMechVentZone.zonePropCtlMinRateSched = thisMechVentZone.zonePropCtlMinRateSched;
2704 :
2705 : // new DCV
2706 0 : tempMechVentZone.ZoneDesignSpecADObjIndex = thisMechVentZone.ZoneDesignSpecADObjIndex;
2707 0 : tempMechVentZone.ZoneADEffCooling = thisMechVentZone.ZoneADEffCooling;
2708 0 : tempMechVentZone.ZoneADEffHeating = thisMechVentZone.ZoneADEffHeating;
2709 0 : tempMechVentZone.zoneADEffSched = thisMechVentZone.zoneADEffSched;
2710 0 : tempMechVentZone.ZoneSecondaryRecirculation = thisMechVentZone.ZoneSecondaryRecirculation;
2711 : }
2712 17 : break;
2713 : }
2714 : }
2715 17 : if (!FoundZone) {
2716 0 : ShowWarningError(state,
2717 0 : format("Zone name = {} in {} object name = {} is not on the same air loop as Controller:OutdoorAir = {}",
2718 0 : zone.Name,
2719 0 : CurrentModuleObjects[static_cast<int>(CMO::MechVentilation)],
2720 0 : thisOAController.VentilationMechanicalName,
2721 0 : thisOAController.Name));
2722 0 : ShowContinueError(state, "This zone will not be used and the simulation will continue...");
2723 : }
2724 : }
2725 :
2726 : // Shrink final arrays to conserve environment space
2727 9 : if (TempMechVentArrayCounter < vent_mech.NumofVentMechZones) {
2728 0 : vent_mech.VentMechZone.resize(TempMechVentArrayCounter);
2729 0 : vent_mech.NumofVentMechZones = TempMechVentArrayCounter;
2730 : }
2731 :
2732 : // predefined report
2733 26 : for (int jZone = 1; jZone <= vent_mech.NumofVentMechZones; ++jZone) {
2734 17 : auto &thisMechVentZone = vent_mech.VentMechZone(jZone);
2735 17 : std::string const zoneName = state.dataHeatBal->Zone(thisMechVentZone.zoneNum).Name;
2736 17 : auto &dsoa = state.dataSize->OARequirements(thisMechVentZone.ZoneDesignSpecOAObjIndex);
2737 :
2738 : // Loop through spaces if DesignSpecification:OutdoorAir:Spacelist, or just once for simple DSOA
2739 18 : auto writeDSOAToPredefined = [&state, &vent_mech, &thisMechVentZone](const OARequirementsData &dsoa,
2740 : const std::string &zoneOrSpaceName) {
2741 18 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchDCVventMechName, zoneOrSpaceName, vent_mech.Name);
2742 36 : OutputReportPredefined::PreDefTableEntry(state,
2743 18 : state.dataOutRptPredefined->pdchDCVType,
2744 : zoneOrSpaceName,
2745 18 : SysOAMethodNames[static_cast<int>(vent_mech.SystemOAMethod)]);
2746 18 : OutputReportPredefined::PreDefTableEntry(
2747 18 : state, state.dataOutRptPredefined->pdchDCVperPerson, zoneOrSpaceName, dsoa.OAFlowPerPerson, 6);
2748 18 : OutputReportPredefined::PreDefTableEntry(
2749 18 : state, state.dataOutRptPredefined->pdchDCVperArea, zoneOrSpaceName, dsoa.OAFlowPerArea, 6);
2750 18 : OutputReportPredefined::PreDefTableEntry(
2751 18 : state, state.dataOutRptPredefined->pdchDCVperZone, zoneOrSpaceName, dsoa.OAFlowPerZone, 6);
2752 18 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchDCVperACH, zoneOrSpaceName, dsoa.OAFlowACH, 6);
2753 36 : OutputReportPredefined::PreDefTableEntry(state,
2754 18 : state.dataOutRptPredefined->pdchDCVMethod,
2755 : zoneOrSpaceName,
2756 18 : OAFlowCalcMethodNames[static_cast<int>(dsoa.OAFlowMethod)]);
2757 18 : if (dsoa.oaFlowFracSched != nullptr) {
2758 36 : OutputReportPredefined::PreDefTableEntry(
2759 36 : state, state.dataOutRptPredefined->pdchDCVOASchName, zoneOrSpaceName, dsoa.oaFlowFracSched->Name);
2760 : }
2761 18 : if (thisMechVentZone.zoneADEffSched != nullptr) {
2762 8 : OutputReportPredefined::PreDefTableEntry(
2763 8 : state, state.dataOutRptPredefined->pdchDCVZoneADEffSchName, zoneOrSpaceName, thisMechVentZone.zoneADEffSched->Name);
2764 : } else {
2765 14 : OutputReportPredefined::PreDefTableEntry(
2766 14 : state, state.dataOutRptPredefined->pdchDCVZoneADEffCooling, zoneOrSpaceName, thisMechVentZone.ZoneADEffCooling, 2);
2767 14 : OutputReportPredefined::PreDefTableEntry(
2768 28 : state, state.dataOutRptPredefined->pdchDCVZoneADEffHeating, zoneOrSpaceName, thisMechVentZone.ZoneADEffHeating, 2);
2769 : }
2770 18 : };
2771 17 : if (dsoa.numDSOA == 0) {
2772 16 : writeDSOAToPredefined(dsoa, zoneName);
2773 : } else {
2774 3 : for (int n = 0; n < dsoa.numDSOA; ++n) {
2775 2 : writeDSOAToPredefined(state.dataSize->OARequirements(dsoa.dsoaIndexes[n]), format("{}:{}", zoneName, dsoa.dsoaSpaceNames[n]));
2776 : }
2777 : }
2778 17 : }
2779 :
2780 : // Fill People index lists if needed
2781 9 : if (vent_mech.SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlDesOcc) {
2782 6 : for (int peopleNum = 1; peopleNum <= state.dataHeatBal->TotPeople; ++peopleNum) {
2783 7 : for (auto &thisMechVentZone : vent_mech.VentMechZone) {
2784 7 : if (state.dataHeatBal->People(peopleNum).ZonePtr == thisMechVentZone.zoneNum) {
2785 4 : thisMechVentZone.peopleIndexes.push_back(peopleNum);
2786 4 : break;
2787 : }
2788 : }
2789 : }
2790 : }
2791 :
2792 : // Check to see if any zones on an air loop are not accounted for by a mechanical ventilation object
2793 26 : for (int AirLoopZoneInfoZoneNum = 1; AirLoopZoneInfoZoneNum <= state.dataAirLoop->AirLoopZoneInfo(AirLoopNum).NumZones;
2794 : ++AirLoopZoneInfoZoneNum) {
2795 17 : int NumZone = state.dataAirLoop->AirLoopZoneInfo(AirLoopNum).ActualZoneNumber(AirLoopZoneInfoZoneNum);
2796 17 : bool FoundAreaZone = false;
2797 17 : bool FoundPeopleZone = false;
2798 26 : for (int NumMechVentZone = 1; NumMechVentZone <= vent_mech.NumofVentMechZones; ++NumMechVentZone) {
2799 26 : auto const &thisMechVentZone = vent_mech.VentMechZone(NumMechVentZone);
2800 26 : int ZoneNum = thisMechVentZone.zoneNum;
2801 26 : if (ZoneNum == NumZone) {
2802 17 : FoundAreaZone = true;
2803 17 : if (state.dataSize->OARequirements(thisMechVentZone.ZoneDesignSpecOAObjIndex).desFlowPerZonePerson(state, ZoneNum) > 0.0) {
2804 11 : FoundPeopleZone = true;
2805 : }
2806 17 : break;
2807 : }
2808 : }
2809 17 : if (!FoundAreaZone) {
2810 0 : ShowWarningError(state,
2811 0 : format("Zone name = {} is not accounted for by {} object name = {}",
2812 0 : state.dataHeatBal->Zone(NumZone).Name,
2813 0 : CurrentModuleObjects[static_cast<int>(CMO::MechVentilation)],
2814 0 : thisOAController.VentilationMechanicalName));
2815 0 : ShowContinueError(state, "Ventilation per unit floor area has not been specified for this zone, which is connected to");
2816 0 : ShowContinueError(
2817 0 : state, format("the air loop served by Controller:OutdoorAir = {}. Simulation will continue...", thisOAController.Name));
2818 : }
2819 17 : if (!FoundPeopleZone) {
2820 : // Loop through people objects to see if this zone has a people object and only then show a warning
2821 19 : for (int PeopleNum = 1; PeopleNum <= state.dataHeatBal->TotPeople; ++PeopleNum) {
2822 13 : if (state.dataHeatBal->People(PeopleNum).ZonePtr == NumZone) {
2823 6 : if (!FoundAreaZone) {
2824 0 : ShowWarningError(state,
2825 0 : format("PEOPLE object for zone = {} is not accounted for by {} object name = {}",
2826 0 : state.dataHeatBal->Zone(NumZone).Name,
2827 0 : CurrentModuleObjects[static_cast<int>(CMO::MechVentilation)],
2828 0 : thisOAController.VentilationMechanicalName));
2829 0 : ShowContinueError(
2830 : state,
2831 0 : format(
2832 : "A \"PEOPLE\" object has been specified in the idf for this zone, but it is not included in this {} Object.",
2833 0 : CurrentModuleObjects[static_cast<int>(CMO::MechVentilation)]));
2834 0 : ShowContinueError(state,
2835 0 : format("Check {} object. Simulation will continue.",
2836 0 : CurrentModuleObjects[static_cast<int>(CMO::MechVentilation)]));
2837 : }
2838 : }
2839 : }
2840 : } else { // People > 0, check to make sure there is a people statement in the zone
2841 11 : FoundAreaZone = false;
2842 20 : for (int PeopleNum = 1; PeopleNum <= state.dataHeatBal->TotPeople; ++PeopleNum) {
2843 20 : if (state.dataHeatBal->People(PeopleNum).ZonePtr != NumZone) continue;
2844 11 : FoundAreaZone = true;
2845 11 : break;
2846 : }
2847 11 : if (!FoundAreaZone) {
2848 0 : ShowWarningError(state,
2849 0 : format("{} = \"{}\", Zone=\"{}\".",
2850 0 : CurrentModuleObjects[static_cast<int>(CMO::MechVentilation)],
2851 0 : thisOAController.VentilationMechanicalName,
2852 0 : state.dataHeatBal->Zone(NumZone).Name));
2853 0 : ShowContinueError(state,
2854 : "No \"PEOPLE\" object has been specified in the idf for this zone, but the ventilation rate is > 0 in "
2855 : "this Controller:MechanicalVentilation Object.");
2856 0 : ShowContinueError(state, "Check ventilation rate in Controller:MechanicalVentilation object. Simulation will continue.");
2857 : }
2858 : }
2859 : }
2860 : }
2861 :
2862 29 : state.dataMixedAir->MechVentCheckFlag(OAControllerNum) = false;
2863 : }
2864 : //****
2865 :
2866 : // Perform a one time initialization of AirloopHVAC OA System report variables
2867 : // If AirloopHVAC objects are used, NumPrimaryAirSys > 0 and the initialization proceeds and then sets
2868 : // SetUpAirLoopHVACVariables to .FALSE. so this is never done again and only the first IF is checked
2869 : // each time through Init. If for some reason the primary air system have not yet been read in, this
2870 : // code waits for the air loop data to be available before performing the report variable initialization.
2871 : // If AirloopHVAC objects are not used, NumPrimaryAirSys is always equal to 0 and only these
2872 : // two IF statements are checked each time through Init (e.g., if StandAloneERV controllers are used
2873 : // without AirloopHVAC objects).
2874 78423 : if (state.dataMixedAir->InitOAControllerSetUpAirLoopHVACVariables) {
2875 29 : if (AirLoopNum > 0) {
2876 : // Added code to report (TH, 10/20/2008):
2877 : // air economizer status (1 = on, 0 = off or does not exist), and actual and minimum outside air fraction (0 to 1)
2878 28 : std::string airloopName; // Temporary equipment name
2879 56 : for (int OAControllerLoop = 1; OAControllerLoop <= state.dataMixedAir->NumOAControllers; ++OAControllerLoop) {
2880 28 : auto &loopOAController(state.dataMixedAir->OAController(OAControllerLoop));
2881 :
2882 : // Find the outside air system that has the OA controller
2883 28 : if (loopOAController.ControllerType == MixedAirControllerType::ControllerStandAloneERV) continue; // ERV controller not on airloop
2884 28 : bool OASysFound = false;
2885 28 : int thisOASys = 0;
2886 28 : for (int OASysNum = 1; OASysNum <= state.dataAirLoop->NumOASystems; ++OASysNum) {
2887 28 : for (int OAControllerLoop2 = 1; OAControllerLoop2 <= state.dataAirLoop->OutsideAirSys(OASysNum).NumControllers;
2888 : ++OAControllerLoop2) {
2889 28 : if (Util::SameString(state.dataAirLoop->OutsideAirSys(OASysNum).ControllerName(OAControllerLoop2), loopOAController.Name)) {
2890 28 : thisOASys = OASysNum;
2891 28 : OASysFound = true;
2892 28 : break;
2893 : }
2894 : }
2895 28 : if (OASysFound) break;
2896 : }
2897 :
2898 28 : int airLoopNum = 0;
2899 28 : bool AirLoopFound = false;
2900 28 : if (thisOASys <= 0) {
2901 : // Check outside air system name
2902 0 : ShowWarningError(state, format("Cannot find the AirLoopHVAC:OutdoorAirSystem for the OA Controller: {}", loopOAController.Name));
2903 : } else {
2904 : // Find the primary air loop that has the outside air system
2905 28 : for (int thisAirLoop = 1; thisAirLoop <= state.dataHVACGlobal->NumPrimaryAirSys; ++thisAirLoop) {
2906 26 : for (int BranchNum = 1; BranchNum <= state.dataAirSystemsData->PrimaryAirSystems(thisAirLoop).NumBranches; ++BranchNum) {
2907 27 : for (int CompNum = 1;
2908 27 : CompNum <= state.dataAirSystemsData->PrimaryAirSystems(thisAirLoop).Branch(BranchNum).TotalComponents;
2909 : ++CompNum) {
2910 27 : if (!Util::SameString(state.dataAirSystemsData->PrimaryAirSystems(thisAirLoop).Branch(BranchNum).Comp(CompNum).Name,
2911 80 : state.dataAirLoop->OutsideAirSys(thisOASys).Name) ||
2912 53 : !Util::SameString(state.dataAirSystemsData->PrimaryAirSystems(thisAirLoop).Branch(BranchNum).Comp(CompNum).TypeOf,
2913 : "AirLoopHVAC:OutdoorAirSystem"))
2914 1 : continue;
2915 26 : AirLoopFound = true;
2916 26 : airLoopNum = thisAirLoop;
2917 26 : break;
2918 : }
2919 26 : if (AirLoopFound) break;
2920 : }
2921 26 : if (AirLoopFound) break;
2922 : }
2923 : }
2924 : // Check primary air loop name
2925 28 : if (AirLoopFound && airLoopNum > 0) {
2926 26 : airloopName = state.dataAirSystemsData->PrimaryAirSystems(airLoopNum).Name; // OutsideAirSys(OASysIndex)%Name
2927 : } else {
2928 2 : ShowWarningError(state, format("Cannot find the primary air loop for the OA Controller: {}", loopOAController.Name));
2929 2 : airloopName = "AirLoop not found";
2930 : }
2931 :
2932 : // Note use of OAControllerLoop here to keep DO Loop index separate from InitOAController local variable
2933 : // CurrentModuleObject='AirLoopHVAC'
2934 28 : SetupOutputVariable(state,
2935 : "Air System Outdoor Air Economizer Status",
2936 : Constant::Units::None,
2937 28 : loopOAController.EconomizerStatus,
2938 : OutputProcessor::TimeStepType::System,
2939 : OutputProcessor::StoreType::Average,
2940 : airloopName);
2941 :
2942 28 : SetupOutputVariable(state,
2943 : "Air System Outdoor Air Heat Recovery Bypass Status",
2944 : Constant::Units::None,
2945 28 : loopOAController.HeatRecoveryBypassStatus,
2946 : OutputProcessor::TimeStepType::System,
2947 : OutputProcessor::StoreType::Average,
2948 : airloopName);
2949 :
2950 28 : SetupOutputVariable(state,
2951 : "Air System Outdoor Air Heat Recovery Bypass Heating Coil Activity Status",
2952 : Constant::Units::None,
2953 28 : loopOAController.HRHeatingCoilActive,
2954 : OutputProcessor::TimeStepType::System,
2955 : OutputProcessor::StoreType::Average,
2956 : airloopName);
2957 56 : SetupOutputVariable(state,
2958 : "Air System Outdoor Air Heat Recovery Bypass Minimum Outdoor Air Mixed Air Temperature",
2959 : Constant::Units::C,
2960 28 : loopOAController.MixedAirTempAtMinOAFlow,
2961 : OutputProcessor::TimeStepType::System,
2962 : OutputProcessor::StoreType::Average,
2963 : airloopName);
2964 :
2965 28 : SetupOutputVariable(state,
2966 : "Air System Outdoor Air High Humidity Control Status",
2967 : Constant::Units::None,
2968 28 : loopOAController.HighHumCtrlStatus,
2969 : OutputProcessor::TimeStepType::System,
2970 : OutputProcessor::StoreType::Average,
2971 : airloopName);
2972 :
2973 28 : SetupOutputVariable(state,
2974 : "Air System Outdoor Air Limiting Factor",
2975 : Constant::Units::None,
2976 28 : loopOAController.OALimitingFactorReport,
2977 : OutputProcessor::TimeStepType::System,
2978 : OutputProcessor::StoreType::Average,
2979 : airloopName);
2980 :
2981 56 : SetupOutputVariable(state,
2982 : "Air System Outdoor Air Flow Fraction",
2983 : Constant::Units::None,
2984 28 : loopOAController.OAFractionRpt,
2985 : OutputProcessor::TimeStepType::System,
2986 : OutputProcessor::StoreType::Average,
2987 : airloopName);
2988 :
2989 56 : SetupOutputVariable(state,
2990 : "Air System Outdoor Air Minimum Flow Fraction",
2991 : Constant::Units::None,
2992 28 : loopOAController.MinOAFracLimit,
2993 : OutputProcessor::TimeStepType::System,
2994 : OutputProcessor::StoreType::Average,
2995 : airloopName);
2996 :
2997 56 : SetupOutputVariable(state,
2998 : "Air System Outdoor Air Mass Flow Rate",
2999 : Constant::Units::kg_s,
3000 28 : loopOAController.OAMassFlow,
3001 : OutputProcessor::TimeStepType::System,
3002 : OutputProcessor::StoreType::Average,
3003 : airloopName);
3004 :
3005 56 : SetupOutputVariable(state,
3006 : "Air System Mixed Air Mass Flow Rate",
3007 : Constant::Units::kg_s,
3008 28 : loopOAController.MixMassFlow,
3009 : OutputProcessor::TimeStepType::System,
3010 : OutputProcessor::StoreType::Average,
3011 : airloopName);
3012 :
3013 56 : SetupOutputVariable(state,
3014 : "Air System Relief Air Heat Transfer Rate",
3015 : Constant::Units::W,
3016 28 : loopOAController.RelTotalLossRate,
3017 : OutputProcessor::TimeStepType::System,
3018 : OutputProcessor::StoreType::Average,
3019 : airloopName);
3020 :
3021 56 : SetupOutputVariable(state,
3022 : "Air System Relief Air Sensible Heat Transfer Rate",
3023 : Constant::Units::W,
3024 28 : loopOAController.RelSensiLossRate,
3025 : OutputProcessor::TimeStepType::System,
3026 : OutputProcessor::StoreType::Average,
3027 : airloopName);
3028 :
3029 56 : SetupOutputVariable(state,
3030 : "Air System Relief Air Latent Heat Transfer Rate",
3031 : Constant::Units::W,
3032 28 : loopOAController.RelLatentLossRate,
3033 : OutputProcessor::TimeStepType::System,
3034 : OutputProcessor::StoreType::Average,
3035 : airloopName);
3036 :
3037 28 : if (loopOAController.MixedAirSPMNum > 0) {
3038 0 : SetupOutputVariable(state,
3039 : "Air System Outdoor Air Maximum Flow Fraction",
3040 : Constant::Units::None,
3041 0 : loopOAController.MaxOAFracBySetPoint,
3042 : OutputProcessor::TimeStepType::System,
3043 : OutputProcessor::StoreType::Average,
3044 : airloopName);
3045 : }
3046 :
3047 28 : if (state.dataGlobal->AnyEnergyManagementSystemInModel) {
3048 0 : SetupEMSInternalVariable(
3049 0 : state, "Outdoor Air Controller Maximum Mass Flow Rate", loopOAController.Name, "[kg/s]", loopOAController.MaxOAMassFlowRate);
3050 0 : SetupEMSInternalVariable(
3051 0 : state, "Outdoor Air Controller Minimum Mass Flow Rate", loopOAController.Name, "[kg/s]", loopOAController.MinOAMassFlowRate);
3052 0 : SetupEMSActuator(state,
3053 : "Outdoor Air Controller",
3054 : loopOAController.Name,
3055 : "Air Mass Flow Rate",
3056 : "[kg/s]",
3057 0 : loopOAController.EMSOverrideOARate,
3058 0 : loopOAController.EMSOARateValue);
3059 : }
3060 :
3061 28 : if (loopOAController.VentMechObjectNum > 0 && airLoopNum > 0) {
3062 18 : SetupOutputVariable(state,
3063 : "Air System Outdoor Air Mechanical Ventilation Requested Mass Flow Rate",
3064 : Constant::Units::kg_s,
3065 9 : loopOAController.MechVentOAMassFlowRequest,
3066 : OutputProcessor::TimeStepType::System,
3067 : OutputProcessor::StoreType::Average,
3068 : airloopName);
3069 9 : if (!state.dataMixedAir->VentilationMechanical(loopOAController.VentMechObjectNum).DCVFlag) {
3070 1 : state.dataAirLoop->AirLoopControlInfo(airLoopNum).AirLoopDCVFlag = false;
3071 : }
3072 : }
3073 : }
3074 :
3075 28 : state.dataMixedAir->InitOAControllerSetUpAirLoopHVACVariables = false;
3076 28 : }
3077 : }
3078 :
3079 : // Each time step
3080 78423 : if (FirstHVACIteration) {
3081 : // Mixed air setpoint. Set by a setpoint manager.
3082 38979 : if (thisOAController.ControllerType == MixedAirControllerType::ControllerOutsideAir) {
3083 38979 : if (state.dataLoopNodes->Node(thisOAController.MixNode).TempSetPoint > SensedNodeFlagValue) {
3084 28682 : thisOAController.MixSetTemp = state.dataLoopNodes->Node(thisOAController.MixNode).TempSetPoint;
3085 : } else {
3086 10297 : thisOAController.MixSetTemp = thisOAController.TempLowLim;
3087 : }
3088 : } else {
3089 : // Stand Alone ERV does not require a temperature setpoint schedule, make setpoint equal to lower economizer limit
3090 0 : thisOAController.MixSetTemp = thisOAController.TempLowLim;
3091 : }
3092 : }
3093 :
3094 : // Each iteration
3095 :
3096 78423 : if (thisOAController.ControllerType == MixedAirControllerType::ControllerOutsideAir) {
3097 : // zone exhaust mass flow is saved in AirLoopFlow%ZoneExhaust
3098 : // the zone exhaust mass flow that is said to be balanced by simple air flows is saved in AirLoopFlow%ZoneExhaustBalanced
3099 78423 : if (AirLoopNum > 0) {
3100 78422 : thisOAController.ExhMassFlow =
3101 78422 : max(0.0, state.dataAirLoop->AirLoopFlow(AirLoopNum).SupFlow - state.dataAirLoop->AirLoopFlow(AirLoopNum).SysRetFlow);
3102 78422 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).ZoneExhMassFlow = thisOAController.ExhMassFlow;
3103 78422 : if (state.dataAirLoop->AirLoopControlInfo(AirLoopNum).LoopFlowRateSet && !FirstHVACIteration) {
3104 : // if flow rate has been specified by a manager, set it to the specified value
3105 0 : thisOAController.MixMassFlow =
3106 0 : state.dataAirLoop->AirLoopFlow(AirLoopNum).ReqSupplyFrac * state.dataAirLoop->AirLoopFlow(AirLoopNum).DesSupply;
3107 : // state.dataLoopNodes->Node(thisOAController.RetNode).MassFlowRate = thisOAController.MixMassFlow - thisOAController.ExhMassFlow;
3108 : } else {
3109 78422 : thisOAController.MixMassFlow = state.dataLoopNodes->Node(thisOAController.RetNode).MassFlowRate + thisOAController.ExhMassFlow;
3110 :
3111 : // The following was commented out after discussion on PR 7382, it can be reopened for discussion anytime
3112 : // found this equation results in flow that exceeds the design flow rate when there is exhaust flow rate is greater than
3113 : // the design supply air flow rate. Capped the mixed air flow rate at design supply air flow rate, issue #77379
3114 : // thisOAController.MixMassFlow = Node(thisOAController.RetNode).MassFlowRate + thisOAController.ExhMassFlow;
3115 : // thisOAController.MixMassFlow =
3116 : // min(Node(thisOAController.RetNode).MassFlowRate + thisOAController.ExhMassFlow, AirLoopFlow(AirLoopNum).DesSupply);
3117 : }
3118 : } else {
3119 1 : thisOAController.ExhMassFlow = 0.0;
3120 1 : thisOAController.MixMassFlow = state.dataLoopNodes->Node(thisOAController.RetNode).MassFlowRate;
3121 : }
3122 78423 : if (state.dataLoopNodes->Node(thisOAController.MixNode).MassFlowRateMaxAvail <= 0.0) {
3123 4445 : thisOAController.MixMassFlow = 0.0;
3124 : }
3125 : } else {
3126 : // Mixed and exhaust flow rates are passed through to model CONTROLLER:STAND ALONE ERV in SimOAController
3127 0 : thisOAController.OAMassFlow = thisOAController.MaxOAMassFlowRate;
3128 0 : thisOAController.MixMassFlow = thisOAController.MaxOAMassFlowRate;
3129 0 : thisOAController.ExhMassFlow = state.dataLoopNodes->Node(thisOAController.RetNode).MassFlowRate;
3130 : }
3131 78423 : thisOAController.ExhMassFlow = max(thisOAController.ExhMassFlow, 0.0);
3132 :
3133 : // Outside air values
3134 78423 : thisOAController.OATemp = state.dataLoopNodes->Node(thisOAController.OANode).Temp;
3135 78423 : thisOAController.OAEnth = state.dataLoopNodes->Node(thisOAController.OANode).Enthalpy;
3136 78423 : thisOAController.OAPress = state.dataLoopNodes->Node(thisOAController.OANode).Press;
3137 78423 : thisOAController.OAHumRat = state.dataLoopNodes->Node(thisOAController.OANode).HumRat;
3138 :
3139 : // Inlet air values (on OA input side)
3140 78423 : thisOAController.InletTemp = state.dataLoopNodes->Node(thisOAController.InletNode).Temp;
3141 78423 : thisOAController.InletEnth = state.dataLoopNodes->Node(thisOAController.InletNode).Enthalpy;
3142 78423 : thisOAController.InletPress = state.dataLoopNodes->Node(thisOAController.InletNode).Press;
3143 78423 : thisOAController.InletHumRat = state.dataLoopNodes->Node(thisOAController.InletNode).HumRat;
3144 :
3145 : // Return air values
3146 78423 : thisOAController.RetTemp = state.dataLoopNodes->Node(thisOAController.RetNode).Temp;
3147 78423 : thisOAController.RetEnth = state.dataLoopNodes->Node(thisOAController.RetNode).Enthalpy;
3148 :
3149 : // Check sensors faults for the air economizer
3150 78423 : EconoOp iEco = thisOAController.Econo;
3151 78423 : if (state.dataFaultsMgr->AnyFaultsInModel && (iEco != EconoOp::NoEconomizer)) {
3152 0 : for (int i = 1; i <= thisOAController.NumFaultyEconomizer; ++i) {
3153 0 : int j = thisOAController.EconmizerFaultNum(i);
3154 0 : Real64 rSchVal = 0.0;
3155 0 : if (state.dataFaultsMgr->FaultsEconomizer(j).availSched->getCurrentVal() > 0.0) {
3156 0 : rSchVal = 1.0;
3157 0 : if (state.dataFaultsMgr->FaultsEconomizer(j).severitySched != nullptr) {
3158 0 : rSchVal = state.dataFaultsMgr->FaultsEconomizer(j).severitySched->getCurrentVal();
3159 : }
3160 : } else {
3161 0 : continue; // no fault
3162 : }
3163 :
3164 0 : Real64 rOffset = rSchVal * state.dataFaultsMgr->FaultsEconomizer(j).Offset;
3165 :
3166 0 : if (std::abs(rOffset) < 0.000000001) continue;
3167 :
3168 : // ECONOMIZER - outdoor air dry-bulb temperature sensor offset
3169 0 : switch (iEco) {
3170 0 : case EconoOp::FixedDryBulb:
3171 : case EconoOp::DifferentialDryBulb:
3172 : case EconoOp::FixedDewPointAndDryBulb:
3173 : case EconoOp::ElectronicEnthalpy:
3174 : case EconoOp::DifferentialDryBulbAndEnthalpy: {
3175 0 : if (state.dataFaultsMgr->FaultsEconomizer(j).type == FaultType::TemperatureSensorOffset_OutdoorAir) {
3176 : // FaultModel:TemperatureSensorOffset:OutdoorAir
3177 0 : thisOAController.OATemp += rOffset;
3178 0 : thisOAController.InletTemp += rOffset;
3179 : }
3180 0 : } break;
3181 0 : default:
3182 0 : break;
3183 : }
3184 :
3185 : // ECONOMIZER - outdoor air humidity ratio sensor offset. really needed ???
3186 0 : switch (iEco) {
3187 0 : case EconoOp::FixedDewPointAndDryBulb:
3188 : case EconoOp::ElectronicEnthalpy: {
3189 0 : if (state.dataFaultsMgr->FaultsEconomizer(j).type == FaultType::HumiditySensorOffset_OutdoorAir) {
3190 : // FaultModel:HumiditySensorOffset:OutdoorAir
3191 0 : thisOAController.OAHumRat += rOffset;
3192 0 : thisOAController.InletHumRat += rOffset;
3193 : }
3194 0 : } break;
3195 0 : default:
3196 0 : break;
3197 : }
3198 :
3199 : // ECONOMIZER - outdoor air enthalpy sensor offset
3200 0 : switch (iEco) {
3201 0 : case EconoOp::FixedEnthalpy:
3202 : case EconoOp::ElectronicEnthalpy:
3203 : case EconoOp::DifferentialDryBulbAndEnthalpy: {
3204 0 : if (state.dataFaultsMgr->FaultsEconomizer(j).type == FaultType::EnthalpySensorOffset_OutdoorAir) {
3205 : // FaultModel:EnthalpySensorOffset:OutdoorAir
3206 0 : thisOAController.OAEnth += rOffset;
3207 0 : thisOAController.InletEnth += rOffset;
3208 : }
3209 0 : } break;
3210 0 : default:
3211 0 : break;
3212 : }
3213 :
3214 : // ECONOMIZER - return air dry-bulb temperature sensor offset
3215 0 : switch (iEco) {
3216 0 : case EconoOp::DifferentialDryBulb:
3217 : case EconoOp::DifferentialDryBulbAndEnthalpy: {
3218 0 : if (state.dataFaultsMgr->FaultsEconomizer(j).type == FaultType::TemperatureSensorOffset_ReturnAir) {
3219 : // FaultModel:TemperatureSensorOffset:ReturnAir
3220 0 : thisOAController.RetTemp += rOffset;
3221 : }
3222 0 : } break;
3223 0 : default:
3224 0 : break;
3225 : }
3226 :
3227 : // ECONOMIZER - return air enthalpy sensor offset
3228 0 : switch (iEco) {
3229 0 : case EconoOp::ElectronicEnthalpy:
3230 : case EconoOp::DifferentialDryBulbAndEnthalpy: {
3231 0 : if (state.dataFaultsMgr->FaultsEconomizer(j).type == FaultType::EnthalpySensorOffset_ReturnAir) {
3232 : // FaultModel:EnthalpySensorOffset:ReturnAir
3233 0 : thisOAController.RetEnth += rOffset;
3234 : }
3235 0 : } break;
3236 0 : default:
3237 0 : break;
3238 : }
3239 : }
3240 : }
3241 :
3242 78423 : if (ErrorsFound) {
3243 0 : ShowFatalError(state, format("Error in {}; program terminated", CurrentModuleObjects[static_cast<int>(CMO::OAController)]));
3244 : }
3245 78423 : } // namespace MixedAir
3246 :
3247 383145 : void OAMixerProps::InitOAMixer(EnergyPlusData &state)
3248 : {
3249 : // SUBROUTINE INFORMATION:
3250 : // AUTHOR Fred Buhl
3251 : // DATE WRITTEN Oct 1998
3252 :
3253 : // PURPOSE OF THIS SUBROUTINE
3254 : // Initialize the OAMixer data structure with input node data
3255 :
3256 383145 : int RetNode = this->RetNode;
3257 383145 : int InletNode = this->InletNode;
3258 383145 : int RelNode = this->RelNode;
3259 :
3260 : // Return air stream data
3261 383145 : this->RetTemp = state.dataLoopNodes->Node(RetNode).Temp;
3262 383145 : this->RetHumRat = state.dataLoopNodes->Node(RetNode).HumRat;
3263 383145 : this->RetEnthalpy = state.dataLoopNodes->Node(RetNode).Enthalpy;
3264 383145 : this->RetPressure = state.dataLoopNodes->Node(RetNode).Press;
3265 383145 : this->RetMassFlowRate = state.dataLoopNodes->Node(RetNode).MassFlowRate;
3266 : // Outside air stream data
3267 383145 : this->OATemp = state.dataLoopNodes->Node(InletNode).Temp;
3268 383145 : this->OAHumRat = state.dataLoopNodes->Node(InletNode).HumRat;
3269 383145 : this->OAEnthalpy = state.dataLoopNodes->Node(InletNode).Enthalpy;
3270 383145 : this->OAPressure = state.dataLoopNodes->Node(InletNode).Press;
3271 383145 : this->OAMassFlowRate = state.dataLoopNodes->Node(InletNode).MassFlowRate;
3272 : // Relief air data
3273 383145 : this->RelMassFlowRate = state.dataLoopNodes->Node(RelNode).MassFlowRate;
3274 383145 : }
3275 :
3276 78438 : void OAControllerProps::CalcOAController(EnergyPlusData &state, int const AirLoopNum, bool const FirstHVACIteration)
3277 : {
3278 :
3279 : // SUBROUTINE INFORMATION:
3280 : // AUTHOR Fred Buhl
3281 : // DATE WRITTEN Oct 1998
3282 : // MODIFIED Shirey/Raustad FSEC, June 2003
3283 : // Tianzhen Hong, Feb 2009 for new DCV
3284 : // Brent Griffith ,EMS override of OA rate
3285 : // Mangesh Basarkar, 06/2011: Modifying outside air calculation based on DCV flag
3286 : // Chandan Sharma, FSEC, 25Aug 2011 - Added ProportionalControl
3287 : // to enhance CO2 based DCV control
3288 : // Tianzhen Hong, March 2012, zone maximum OA fraction - a TRACE feature
3289 : // Tianzhen Hong, March 2012, multi-path VRP based on ASHRAE 62.1-2010
3290 :
3291 : // PURPOSE OF THIS SUBROUTINE
3292 : // Determine the outside air flow
3293 :
3294 : // REFERENCES:
3295 : // DOE-2.1E Supplement pages 3.97 - 3.100
3296 : // BLAST User Reference pages 183 - 186
3297 : // ASHRAE Standard 62.1-2010
3298 :
3299 : // SUBROUTINE PARAMETER DEFINITIONS:
3300 : static constexpr std::string_view RoutineName("CalcOAController: ");
3301 :
3302 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
3303 78438 : Real64 OASignal = 0.0; // Outside air flow rate fraction (0.0 to 1.0)
3304 78438 : bool AirLoopCyclingFan = false; // Type of air loop fan (TRUE if Fan:OnOff)
3305 78438 : bool HighHumidityOperationFlag = false; // TRUE if zone humidistat senses a high humidity condition
3306 :
3307 78438 : if (AirLoopNum > 0) {
3308 78437 : AirLoopCyclingFan = (state.dataAirLoop->AirLoopControlInfo(AirLoopNum).fanOp == HVAC::FanOp::Cycling);
3309 : } else {
3310 1 : AirLoopCyclingFan = false;
3311 : }
3312 :
3313 78438 : this->OALimitingFactor = OALimitFactor::None; // oa controller limiting factor
3314 :
3315 : // Check for no flow
3316 78438 : if (this->MixMassFlow <= HVAC::SmallMassFlow) {
3317 :
3318 7595 : this->OAMassFlow = 0.0; // outside air mass flow rate
3319 7595 : this->RelMassFlow = 0.0; // relief air mass flow rate
3320 7595 : this->MixMassFlow = 0.0; // mixed air mass flow rate
3321 7595 : this->MinOAFracLimit = 0.0; // minimum OA fraction limit
3322 :
3323 7595 : this->EconomizerStatus = 0; // economizer status for reporting
3324 7595 : this->HeatRecoveryBypassStatus = 0; // HR bypass status for reporting
3325 7595 : this->HRHeatingCoilActive = 0; // resets report variable
3326 7595 : this->MixedAirTempAtMinOAFlow = state.dataLoopNodes->Node(this->RetNode).Temp; // track return T
3327 7595 : this->HighHumCtrlStatus = 0; // high humidity control status for reporting
3328 7595 : this->OAFractionRpt = 0.0; // actual OA fraction for reporting
3329 :
3330 7595 : this->EconoActive = false; // DataAirLoop variable (OA Controllers)
3331 7595 : this->HighHumCtrlActive = false; // DataAirLoop variable (OA Controllers)
3332 :
3333 : // also reset air loop data for use by other routines
3334 7595 : if (AirLoopNum > 0) {
3335 7594 : auto &curAirLoopControlInfo(state.dataAirLoop->AirLoopControlInfo(AirLoopNum));
3336 7594 : auto &curAirLoopFlow(state.dataAirLoop->AirLoopFlow(AirLoopNum));
3337 :
3338 7594 : curAirLoopControlInfo.EconoActive = false; // DataAirLoop variable (AirloopHVAC)
3339 7594 : curAirLoopControlInfo.HeatRecoveryBypass = false; // DataAirLoop variable (AirloopHVAC)
3340 7594 : curAirLoopControlInfo.HighHumCtrlActive = false; // DataAirLoop variable (AirloopHVAC)
3341 7594 : curAirLoopControlInfo.ResimAirLoopFlag = false; // DataAirLoop variable (AirloopHVAC)
3342 7594 : curAirLoopFlow.OAFrac = 0.0; // DataAirLoop variable (AirloopHVAC)
3343 7594 : curAirLoopFlow.OAMinFrac = 0.0; // DataAirLoop variable (AirloopHVAC)
3344 7594 : curAirLoopFlow.MinOutAir = 0.0;
3345 7594 : curAirLoopFlow.OAFlow = 0.0;
3346 : }
3347 7595 : return;
3348 : }
3349 :
3350 70843 : Real64 OutAirMinFrac = 0.0; // Local variable used to calculate min OA fraction
3351 70843 : if (AirLoopNum > 0) {
3352 70843 : auto const &curAirLoopFlow(state.dataAirLoop->AirLoopFlow(AirLoopNum));
3353 70843 : if (curAirLoopFlow.DesSupply >= HVAC::SmallAirVolFlow) {
3354 70843 : OutAirMinFrac = this->MinOAMassFlowRate / curAirLoopFlow.DesSupply;
3355 : }
3356 : } else {
3357 0 : if (this->MaxOA >= HVAC::SmallAirVolFlow) {
3358 0 : OutAirMinFrac = this->MinOA / this->MaxOA;
3359 : }
3360 : }
3361 70843 : Real64 MinOASchedVal = 1.0; // value of the minimum outside air schedule
3362 70843 : if (this->minOASched != nullptr) {
3363 28119 : MinOASchedVal = this->minOASched->getCurrentVal();
3364 28119 : MinOASchedVal = min(max(MinOASchedVal, 0.0), 1.0);
3365 28119 : OutAirMinFrac *= MinOASchedVal;
3366 28119 : this->OALimitingFactor = OALimitFactor::Limits;
3367 : }
3368 :
3369 : // Get outside air mass flow rate calculated by mechanical ventilation object [kg/s]
3370 70843 : Real64 MechVentOutsideAirMinFrac = 0.0; // fraction of OA specified by mechanical ventilation object
3371 70843 : if (AirLoopNum > 0 && this->VentMechObjectNum != 0) {
3372 17967 : auto const &curAirLoopControlInfo(state.dataAirLoop->AirLoopControlInfo(AirLoopNum));
3373 17967 : auto const &curAirLoopFlow(state.dataAirLoop->AirLoopFlow(AirLoopNum));
3374 :
3375 : // Get system supply air flow rate
3376 17967 : Real64 SysSA = 0.0; // System supply air mass flow rate [kg/s]
3377 17967 : if (curAirLoopControlInfo.LoopFlowRateSet) {
3378 : // if flow rate has been specified by a manager, set it to the specified value
3379 : // DesSupply and SupFlow are mass flow rate in kg/s
3380 8 : SysSA = curAirLoopFlow.ReqSupplyFrac * curAirLoopFlow.DesSupply;
3381 : } else {
3382 17959 : SysSA = curAirLoopFlow.SupFlow;
3383 : }
3384 :
3385 17967 : this->MechVentOAMassFlowRequest = state.dataMixedAir->VentilationMechanical(this->VentMechObjectNum).CalcMechVentController(state, SysSA);
3386 17967 : MechVentOutsideAirMinFrac = this->MechVentOAMassFlowRequest / curAirLoopFlow.DesSupply;
3387 17967 : if (curAirLoopFlow.FanPLR > 0.0) {
3388 17967 : MechVentOutsideAirMinFrac *= curAirLoopFlow.FanPLR;
3389 17967 : this->MechVentOAMassFlowRequest *= curAirLoopFlow.FanPLR;
3390 : }
3391 17967 : } else {
3392 52876 : this->MechVentOAMassFlowRequest = 0.0;
3393 : }
3394 : //****** use greater of Mechanical Ventilation Outside Air fraction and OutAirMinFrac
3395 70843 : if ((MechVentOutsideAirMinFrac > 0.0) && (OutAirMinFrac > MechVentOutsideAirMinFrac)) {
3396 1 : if (!state.dataGlobal->WarmupFlag) {
3397 1 : if (this->CountMechVentFrac == 0) {
3398 1 : ++this->CountMechVentFrac;
3399 2 : ShowWarningError(
3400 : state,
3401 2 : format("{}Minimum OA fraction > Mechanical Ventilation Controller request for Controller:OutdoorAir={}, Min OA fraction is used.",
3402 : RoutineName,
3403 1 : this->Name));
3404 2 : ShowContinueError(state,
3405 : "This may be overriding desired ventilation controls. Check inputs for Minimum Outdoor Air Flow Rate, Minimum "
3406 : "Outdoor Air Schedule Name and Controller:MechanicalVentilation");
3407 2 : ShowContinueErrorTimeStamp(
3408 2 : state, format("Minimum OA fraction = {:.4R}, Mech Vent OA fraction = {:.4R}", OutAirMinFrac, MechVentOutsideAirMinFrac));
3409 : } else {
3410 0 : ShowRecurringWarningErrorAtEnd(state,
3411 0 : "Controller:OutdoorAir=\"" + this->Name +
3412 : "\": Min OA fraction > Mechanical ventilation OA fraction, continues...",
3413 0 : this->IndexMechVentFrac,
3414 : OutAirMinFrac,
3415 : OutAirMinFrac);
3416 : }
3417 : }
3418 : }
3419 70843 : if (MechVentOutsideAirMinFrac > OutAirMinFrac) {
3420 16468 : OutAirMinFrac = MechVentOutsideAirMinFrac;
3421 16468 : this->OALimitingFactor = OALimitFactor::DCV;
3422 : }
3423 :
3424 70843 : OutAirMinFrac = min(max(OutAirMinFrac, 0.0), 1.0);
3425 :
3426 : // At this point, OutAirMinFrac is still based on AirLoopFlow.DesSupply
3427 70843 : if (AirLoopNum > 0) {
3428 70843 : auto &curAirLoopFlow(state.dataAirLoop->AirLoopFlow(AirLoopNum));
3429 :
3430 70843 : curAirLoopFlow.MinOutAir = OutAirMinFrac * curAirLoopFlow.DesSupply;
3431 :
3432 : // calculate mixed air temp at min OA flow rate
3433 70843 : Real64 ReliefMassFlowAtMinOA = max(curAirLoopFlow.MinOutAir - this->ExhMassFlow, 0.0);
3434 70843 : Real64 RecircMassFlowRateAtMinOAFlow = max(state.dataLoopNodes->Node(this->RetNode).MassFlowRate - ReliefMassFlowAtMinOA, 0.0);
3435 70843 : if ((RecircMassFlowRateAtMinOAFlow + curAirLoopFlow.MinOutAir) > 0.0) {
3436 70843 : Real64 RecircTemp = state.dataLoopNodes->Node(this->RetNode).Temp; // return air temp used for custom economizer control calculation
3437 70843 : this->MixedAirTempAtMinOAFlow =
3438 70843 : (RecircMassFlowRateAtMinOAFlow * RecircTemp + curAirLoopFlow.MinOutAir * state.dataLoopNodes->Node(this->OANode).Temp) /
3439 70843 : (RecircMassFlowRateAtMinOAFlow + curAirLoopFlow.MinOutAir);
3440 : } else {
3441 0 : this->MixedAirTempAtMinOAFlow = state.dataLoopNodes->Node(this->RetNode).Temp;
3442 : }
3443 : }
3444 :
3445 : // Economizer
3446 70843 : this->CalcOAEconomizer(state, AirLoopNum, OutAirMinFrac, OASignal, HighHumidityOperationFlag, FirstHVACIteration);
3447 :
3448 70843 : this->OAMassFlow = OASignal * this->MixMassFlow;
3449 :
3450 : // Do not allow OA to be below Ventilation:Mechanical flow rate or above mixed mass flow rate
3451 70843 : if (AirLoopNum > 0 && VentMechObjectNum != 0) {
3452 17967 : if (this->MechVentOAMassFlowRequest > this->OAMassFlow) {
3453 2103 : this->OAMassFlow = min(this->MechVentOAMassFlowRequest, this->MixMassFlow);
3454 : }
3455 : }
3456 :
3457 : // Do not allow OA to be below Exh for controller:outside air
3458 70843 : if (this->ControllerType == MixedAirControllerType::ControllerOutsideAir) {
3459 70843 : if (this->ExhMassFlow > this->OAMassFlow) {
3460 2 : this->OAMassFlow = this->ExhMassFlow;
3461 2 : this->OALimitingFactor = OALimitFactor::Exhaust;
3462 : }
3463 : }
3464 :
3465 : // if fixed minimum, don't let go below min OA
3466 70843 : if (this->FixedMin) {
3467 : // cycling fans allow "average" min OA to be below minimum
3468 48868 : if (!AirLoopCyclingFan) {
3469 48868 : Real64 minOASchedMassFlowRate = this->MinOAMassFlowRate * MinOASchedVal;
3470 48868 : if (minOASchedMassFlowRate > this->OAMassFlow) {
3471 6032 : this->OAMassFlow = minOASchedMassFlowRate;
3472 6032 : this->OALimitingFactor = OALimitFactor::Limits;
3473 : }
3474 : }
3475 : }
3476 :
3477 : // Apply Minimum Fraction of Outdoor Air Schedule
3478 70843 : if (this->minOAflowSched != nullptr) {
3479 0 : Real64 MinOAflowfracVal = this->minOAflowSched->getCurrentVal();
3480 0 : MinOAflowfracVal = min(max(MinOAflowfracVal, 0.0), 1.0);
3481 0 : OutAirMinFrac = max(MinOAflowfracVal, OutAirMinFrac);
3482 0 : Real64 minOAFracMassFlowRate = this->MixMassFlow * MinOAflowfracVal;
3483 0 : if (minOAFracMassFlowRate > this->OAMassFlow) {
3484 0 : this->OAMassFlow = minOAFracMassFlowRate;
3485 0 : this->OALimitingFactor = OALimitFactor::Limits;
3486 : }
3487 : }
3488 :
3489 : // Apply Maximum Fraction of Outdoor Air Schedule
3490 70843 : Real64 currentMaxOAMassFlowRate = this->MaxOAMassFlowRate;
3491 70843 : if (this->maxOAflowSched != nullptr) {
3492 0 : Real64 MaxOAflowfracVal = this->maxOAflowSched->getCurrentVal();
3493 0 : MaxOAflowfracVal = min(max(MaxOAflowfracVal, 0.0), 1.0);
3494 0 : currentMaxOAMassFlowRate = min(this->MaxOAMassFlowRate, this->MixMassFlow * MaxOAflowfracVal);
3495 0 : OutAirMinFrac = min(MaxOAflowfracVal, OutAirMinFrac);
3496 0 : if (currentMaxOAMassFlowRate < this->OAMassFlow) {
3497 0 : this->OAMassFlow = currentMaxOAMassFlowRate;
3498 0 : this->OALimitingFactor = OALimitFactor::Limits;
3499 : }
3500 : }
3501 :
3502 : // Don't let the OA flow be > than the max OA limit. OA for high humidity control is allowed to be greater than max OA.
3503 : // Night Ventilation has priority and may override an OASignal > 1 high humidity condition with OASignal = 1
3504 70843 : if (HighHumidityOperationFlag) {
3505 0 : Real64 maxOAMassFlow = this->MaxOAMassFlowRate * max(1.0, OASignal);
3506 0 : if (maxOAMassFlow < this->OAMassFlow) {
3507 0 : this->OAMassFlow = maxOAMassFlow;
3508 0 : this->OALimitingFactor = OALimitFactor::Limits;
3509 : }
3510 : } else {
3511 70843 : if (this->MaxOAMassFlowRate < this->OAMassFlow) {
3512 1180 : this->OAMassFlow = this->MaxOAMassFlowRate;
3513 1180 : this->OALimitingFactor = OALimitFactor::Limits;
3514 : }
3515 : }
3516 :
3517 70843 : if (!state.dataGlobal->WarmupFlag && !state.dataGlobal->DoingSizing && (this->ManageDemand) && (this->OAMassFlow > this->DemandLimitFlowRate)) {
3518 0 : this->OAMassFlow = this->DemandLimitFlowRate;
3519 0 : this->OALimitingFactor = OALimitFactor::DemandLimit;
3520 : }
3521 70843 : if (this->EMSOverrideOARate) {
3522 0 : this->OAMassFlow = this->EMSOARateValue;
3523 0 : this->OALimitingFactor = OALimitFactor::EMS;
3524 : }
3525 :
3526 : // Don't let OA flow be > mixed air flow.
3527 : // Seems if RAB (return air bypass) that this should be don't let OA flow be > design supply flow but that causes other issues
3528 70843 : if (this->MixMassFlow < this->OAMassFlow) {
3529 1938 : this->OAMassFlow = this->MixMassFlow;
3530 1938 : this->OALimitingFactor = OALimitFactor::MixedAir;
3531 : }
3532 :
3533 : // save the min outside air flow fraction and max outside air mass flow rate
3534 70843 : if (AirLoopNum > 0) {
3535 70843 : auto &curAirLoopControlInfo(state.dataAirLoop->AirLoopControlInfo(AirLoopNum));
3536 70843 : auto &curAirLoopFlow(state.dataAirLoop->AirLoopFlow(AirLoopNum));
3537 :
3538 70843 : curAirLoopFlow.OAMinFrac = OutAirMinFrac;
3539 70843 : if (this->FixedMin) {
3540 48868 : curAirLoopFlow.MinOutAir = min(OutAirMinFrac * curAirLoopFlow.DesSupply, this->MixMassFlow);
3541 : } else {
3542 21975 : curAirLoopFlow.MinOutAir = OutAirMinFrac * this->MixMassFlow;
3543 : }
3544 70843 : if (this->MixMassFlow > 0.0) {
3545 70843 : curAirLoopFlow.OAFrac = this->OAMassFlow / this->MixMassFlow;
3546 70843 : curAirLoopFlow.OAFlow = this->OAMassFlow;
3547 : } else {
3548 0 : curAirLoopFlow.OAFrac = 0.0;
3549 0 : curAirLoopFlow.OAFlow = 0.0;
3550 : }
3551 70843 : this->MinOAFracLimit = OutAirMinFrac;
3552 70843 : if (HighHumidityOperationFlag && OASignal > 1.0) {
3553 0 : curAirLoopFlow.MaxOutAir = this->MaxOAMassFlowRate * OASignal;
3554 : } else {
3555 70843 : curAirLoopFlow.MaxOutAir = currentMaxOAMassFlowRate;
3556 : }
3557 :
3558 : // MJW - Not sure if this is necessary but keeping it for now
3559 70843 : if (curAirLoopControlInfo.HeatingActiveFlag && curAirLoopControlInfo.EconomizerFlowLocked) {
3560 : // The airloop needs to be simulated again so that the heating coil & HX can be resimulated
3561 1 : if (curAirLoopControlInfo.HeatRecoveryResimFlag && curAirLoopControlInfo.OASysComponentsSimulated) {
3562 0 : curAirLoopControlInfo.ResimAirLoopFlag = true;
3563 0 : curAirLoopControlInfo.HeatRecoveryResimFlag = false;
3564 0 : curAirLoopControlInfo.HeatRecoveryResimFlag2 = true;
3565 : // on the first iteration, air loop heating coils have not be simulated so HeatingCoilActive=FALSE
3566 : // on the second iteration, the heating coils could have been on, but logic tests here could deactivate heating coil
3567 : // reset heating coil active status and HX since logic tests may turn off heating coil
3568 : // the ResimAirLoopFlag will force another iteration and things should line up on subsequent iterations
3569 0 : curAirLoopControlInfo.HeatingActiveFlag = false;
3570 0 : this->HRHeatingCoilActive = 0;
3571 0 : curAirLoopControlInfo.HeatRecoveryBypass = true;
3572 0 : this->HeatRecoveryBypassStatus = 1;
3573 1 : } else if (curAirLoopControlInfo.HeatRecoveryResimFlag2) {
3574 0 : curAirLoopControlInfo.ResimAirLoopFlag = true;
3575 0 : curAirLoopControlInfo.HeatRecoveryResimFlag2 = false;
3576 : } else {
3577 1 : curAirLoopControlInfo.ResimAirLoopFlag = false;
3578 : }
3579 70842 : } else if (curAirLoopControlInfo.HeatingActiveFlag) {
3580 8113 : this->HRHeatingCoilActive = 1;
3581 : } else {
3582 62729 : this->HRHeatingCoilActive = 0;
3583 : }
3584 :
3585 : } // if (AirLoopNum > 0)
3586 :
3587 : // Set the relief air flow rate (must be done last to account for changes in OAMassFlow
3588 70843 : this->RelMassFlow = max(this->OAMassFlow - this->ExhMassFlow, 0.0);
3589 :
3590 : // Save OA fraction for reporting
3591 70843 : if (this->MixMassFlow > 0) {
3592 70843 : this->OAFractionRpt = this->OAMassFlow / this->MixMassFlow;
3593 : } else {
3594 0 : if (this->OAMassFlow > 0) {
3595 0 : this->OAFractionRpt = OASignal;
3596 : } else {
3597 0 : this->OAFractionRpt = 0.0;
3598 : }
3599 : }
3600 70843 : this->RelTemp = this->RetTemp;
3601 70843 : this->RelEnth = this->RetEnth;
3602 70843 : this->RelSensiLossRate =
3603 70843 : this->RelMassFlow * Psychrometrics::PsyCpAirFnW(state.dataEnvrn->OutHumRat) * (this->RelTemp - state.dataEnvrn->OutDryBulbTemp);
3604 70843 : this->RelTotalLossRate = this->RelMassFlow * (this->RelEnth - state.dataEnvrn->OutEnthalpy);
3605 70843 : this->RelLatentLossRate = this->RelTotalLossRate - this->RelSensiLossRate;
3606 70843 : this->OALimitingFactorReport = static_cast<int>(OALimitingFactor);
3607 : }
3608 :
3609 17982 : Real64 VentilationMechanicalProps::CalcMechVentController(EnergyPlusData &state,
3610 : Real64 SysSA // System supply air mass flow rate [kg/s]
3611 : )
3612 : {
3613 : static constexpr std::string_view RoutineName("CalcMechVentController: ");
3614 : static std::string_view const &CurrentModuleObject(CurrentModuleObjects[static_cast<int>(CMO::MechVentilation)]);
3615 :
3616 : // new local variables for DCV
3617 : // Zone OA flow rate based on each calculation method [m3/s]
3618 : Real64 ZoneOA; // Zone OA flow rate [m3/s]
3619 : Real64 ZoneOAFrac; // Zone OA fraction (as a fraction of actual supply air flow rate)
3620 : Real64 SysOAuc; // System uncorrected OA flow rate
3621 : Real64 SysOA; // System supply OA volume flow rate [m3/s]
3622 : Real64 SysEv; // System ventilation efficiency
3623 : Real64 NodeTemp; // node temperature
3624 : Real64 NodeHumRat; // node humidity ratio
3625 17982 : Real64 ZoneMaxCO2 = 0.0; // Breathing-zone CO2 concentration
3626 17982 : Real64 ZoneMinCO2 = 0.0; // Minimum CO2 concentration in zone
3627 17982 : Real64 ZoneOAMin = 0.0; // Minimum Zone OA flow rate when the zone is unoccupied (i.e. ZoneOAPeople = 0)
3628 17982 : Real64 ZoneOAMax = 0.0; // Maximum Zone OA flow rate (ZoneOAPeople + ZoneOACalc[static_cast<int>(DataSizing::OAFlowCalcMethod::PerArea)])
3629 17982 : Real64 MechVentOAMassFlow = 0.0;
3630 :
3631 : // Apply mechanical ventilation only when it is available/allowed
3632 17982 : if (this->availSched->getCurrentVal() > 0) {
3633 17982 : Real64 SysOAMassFlow = 0.0; // System supply OA mass flow rate [kg/s]
3634 17982 : if (this->SystemOAMethod == DataSizing::SysOAMethod::IAQP) {
3635 : // IAQP for CO2 control
3636 3 : for (int ZoneIndex = 1; ZoneIndex <= this->NumofVentMechZones; ++ZoneIndex) {
3637 2 : auto &thisMechVentZone = this->VentMechZone(ZoneIndex);
3638 2 : int ZoneNum = thisMechVentZone.zoneNum;
3639 2 : SysOAMassFlow +=
3640 2 : state.dataContaminantBalance->ZoneSysContDemand(ZoneNum).OutputRequiredToCO2SP * thisMechVentZone.zoneOASched->getCurrentVal();
3641 : }
3642 1 : MechVentOAMassFlow = SysOAMassFlow;
3643 17981 : } else if (this->SystemOAMethod == DataSizing::SysOAMethod::IAQPGC) {
3644 : // IAQP for generic contaminant control
3645 3 : for (int ZoneIndex = 1; ZoneIndex <= this->NumofVentMechZones; ++ZoneIndex) {
3646 2 : auto &thisMechVentZone = this->VentMechZone(ZoneIndex);
3647 2 : int ZoneNum = thisMechVentZone.zoneNum;
3648 2 : SysOAMassFlow +=
3649 2 : state.dataContaminantBalance->ZoneSysContDemand(ZoneNum).OutputRequiredToGCSP * thisMechVentZone.zoneOASched->getCurrentVal();
3650 : }
3651 1 : MechVentOAMassFlow = SysOAMassFlow;
3652 17980 : } else if (this->SystemOAMethod == DataSizing::SysOAMethod::IAQPCOM) {
3653 : // IAQP for both CO2 and generic contaminant control
3654 2 : SysOAMassFlow = 0.0;
3655 6 : for (int ZoneIndex = 1; ZoneIndex <= this->NumofVentMechZones; ++ZoneIndex) {
3656 4 : auto &thisMechVentZone = this->VentMechZone(ZoneIndex);
3657 4 : int ZoneNum = thisMechVentZone.zoneNum;
3658 4 : SysOAMassFlow +=
3659 4 : state.dataContaminantBalance->ZoneSysContDemand(ZoneNum).OutputRequiredToCO2SP * thisMechVentZone.zoneOASched->getCurrentVal();
3660 : }
3661 2 : MechVentOAMassFlow = SysOAMassFlow;
3662 2 : SysOAMassFlow = 0.0;
3663 6 : for (int ZoneIndex = 1; ZoneIndex <= this->NumofVentMechZones; ++ZoneIndex) {
3664 4 : auto &thisMechVentZone = this->VentMechZone(ZoneIndex);
3665 4 : int ZoneNum = thisMechVentZone.zoneNum;
3666 4 : SysOAMassFlow +=
3667 4 : state.dataContaminantBalance->ZoneSysContDemand(ZoneNum).OutputRequiredToGCSP * thisMechVentZone.zoneOASched->getCurrentVal();
3668 : }
3669 2 : MechVentOAMassFlow = max(SysOAMassFlow, MechVentOAMassFlow);
3670 : } else {
3671 : // for system OA methods: Zone_Sum, VRP, CO2 methods
3672 : // new code for DCV method complying with the VRP defined in ASHRAE Standard 62.1-2010
3673 17978 : bool const useOccSch = this->DCVFlag && (this->SystemOAMethod != DataSizing::SysOAMethod::ProportionalControlDesOcc);
3674 :
3675 : // Loop through each zone first to calc uncorrected system OA flow rate
3676 17978 : SysOAuc = 0.0; // [m3/s]
3677 17978 : SysOA = 0.0; // [m3/s]
3678 51842 : for (int ZoneIndex = 1; ZoneIndex <= this->NumofVentMechZones; ++ZoneIndex) {
3679 33864 : auto &thisMechVentZone = this->VentMechZone(ZoneIndex);
3680 33864 : int const ZoneNum = thisMechVentZone.zoneNum;
3681 :
3682 : // Use calcDesignSpecificationOutdoorAir function to support simple DSOA and DSOA:SpaceList
3683 33864 : bool const useOASch = true;
3684 33864 : bool const perPersonNotSet = false; // placeholder function argument
3685 33864 : bool const maxOAVolFlowFlag = false; // placeholder function argument
3686 33864 : int constexpr spaceNum = 0; // placeholder function argument
3687 33864 : bool const calcIAQMethods = false; // If the zone or space OA method is IAQProcedure, PCOccSch or PCDesOcc then return zero
3688 33864 : thisMechVentZone.zoneOABZ = DataSizing::calcDesignSpecificationOutdoorAir(state,
3689 : thisMechVentZone.ZoneDesignSpecOAObjIndex,
3690 : ZoneNum,
3691 : useOccSch,
3692 : useOASch,
3693 : perPersonNotSet,
3694 : maxOAVolFlowFlag,
3695 : spaceNum,
3696 : calcIAQMethods);
3697 :
3698 33864 : if (this->SystemOAMethod == DataSizing::SysOAMethod::ZoneSum) {
3699 : // Sum the zone OA flow rates and done
3700 2119 : SysOA += thisMechVentZone.zoneOABZ;
3701 : } else {
3702 : // Calc the uncorrected system OA flow rate - VRP and ProportionalControl
3703 31745 : SysOAuc += thisMechVentZone.zoneOABZ;
3704 : }
3705 : }
3706 :
3707 : // get system supply air flow rate
3708 17978 : if (this->SystemOAMethod == DataSizing::SysOAMethod::VRP || this->SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlSchOcc ||
3709 2113 : this->SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlDesOcc ||
3710 2107 : this->SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlDesOARate ||
3711 2105 : this->SystemOAMethod == DataSizing::SysOAMethod::VRPL) {
3712 :
3713 : // System supply air flow rate is always greater than or equal the system outdoor air flow rate
3714 15874 : if ((SysSA > 0.0) && (SysSA < (SysOAuc * state.dataEnvrn->StdRhoAir))) SysSA = SysOAuc * state.dataEnvrn->StdRhoAir;
3715 :
3716 : // calc Xs - average outdoor air fraction
3717 15874 : if (SysSA > 0.0) {
3718 15182 : Xs = (SysOAuc * state.dataEnvrn->StdRhoAir) / SysSA;
3719 : } else {
3720 692 : Xs = 0.0;
3721 : }
3722 :
3723 : // Loop through each zone again
3724 15874 : SysEv = 2.0; // starting with a big fraction
3725 47619 : for (int ZoneIndex = 1; ZoneIndex <= this->NumofVentMechZones; ++ZoneIndex) {
3726 31745 : auto &thisMechVentZone = this->VentMechZone(ZoneIndex);
3727 31745 : int ZoneNum = thisMechVentZone.zoneNum;
3728 31745 : int ZoneEquipConfigNum = ZoneNum; // correspondence - 1:1 of ZoneEquipConfig to Zone index
3729 31745 : Real64 ZoneEz = 0.0; // Zone air distribution effectiveness
3730 :
3731 : // Assign references
3732 31745 : auto &curZone(state.dataHeatBal->Zone(ZoneNum));
3733 31745 : auto &curZoneSysEnergyDemand(state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneEquipConfigNum));
3734 :
3735 : // use the ventilation rate procedure in ASHRAE Standard 62.1-2007
3736 : // Calc the zone supplied OA flow rate counting the zone air distribution effectiveness
3737 : // First check whether the zone air distribution effectiveness schedule exists, if yes uses it;
3738 : // otherwise uses the inputs of zone distribution effectiveness in cooling mode or heating mode
3739 31745 : if (thisMechVentZone.zoneADEffSched != nullptr) {
3740 : // Get schedule value for the zone air distribution effectiveness
3741 14 : ZoneEz = thisMechVentZone.zoneADEffSched->getCurrentVal();
3742 : } else {
3743 31731 : Real64 ZoneLoad = state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneNum).TotalOutputRequired;
3744 :
3745 : // Zone in cooling mode
3746 31731 : if (ZoneLoad < 0.0) ZoneEz = thisMechVentZone.ZoneADEffCooling;
3747 :
3748 : // Zone in heating mode
3749 31731 : if (ZoneLoad > 0.0) ZoneEz = thisMechVentZone.ZoneADEffHeating;
3750 : }
3751 31745 : if (ZoneEz <= 0.0) {
3752 : // Enforce defaults
3753 85 : ZoneEz = 1.0;
3754 : }
3755 :
3756 : // Calc zone supply OA flow rate
3757 31745 : if (this->SystemOAMethod == DataSizing::SysOAMethod::VRP || this->SystemOAMethod == DataSizing::SysOAMethod::VRPL) {
3758 : // the VRP case
3759 31731 : ZoneOA = thisMechVentZone.zoneOABZ / ZoneEz;
3760 14 : } else if (this->SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlSchOcc ||
3761 14 : this->SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlDesOcc ||
3762 2 : this->SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlDesOARate) {
3763 : // Check whether "Carbon Dioxide Control Availability Schedule" for ZoneControl:ContaminantController is specified
3764 14 : if (curZone.zoneContamControllerSched != nullptr) {
3765 : // Check the availability schedule value for ZoneControl:ContaminantController
3766 14 : Real64 ZoneContamControllerSchedVal = curZone.zoneContamControllerSched->getCurrentVal();
3767 14 : if (ZoneContamControllerSchedVal > 0.0) {
3768 14 : auto &oaReq = state.dataSize->OARequirements(thisMechVentZone.ZoneDesignSpecOAObjIndex);
3769 14 : Real64 const zoneOAPeople = oaReq.oaFlowPeople(state, ZoneNum, useOccSch);
3770 14 : if (this->SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlDesOARate) {
3771 2 : ZoneOAMax = thisMechVentZone.zoneOABZ / ZoneEz;
3772 2 : if (thisMechVentZone.zonePropCtlMinRateSched != nullptr) {
3773 2 : ZoneOAMin = ZoneOAMax * thisMechVentZone.zonePropCtlMinRateSched->getCurrentVal();
3774 : } else {
3775 0 : ZoneOAMin = ZoneOAMax;
3776 : }
3777 2 : if (ZoneOAMax < ZoneOAMin) {
3778 0 : ZoneOAMin = ZoneOAMax;
3779 0 : ++this->OAMaxMinLimitErrorCount;
3780 0 : if (this->OAMaxMinLimitErrorCount < 2) {
3781 0 : ShowSevereError(state, format("{}{} = \"{}\".", RoutineName, CurrentModuleObject, this->Name));
3782 0 : ShowContinueError(
3783 : state,
3784 0 : format("For System Outdoor Air Method = ProportionalControlBasedOnDesignOARate, maximum zone "
3785 : "outdoor air rate ({:.4R}), is not greater than minimum zone outdoor air rate ({:.4R}).",
3786 : ZoneOAMax,
3787 : ZoneOAMin));
3788 0 : ShowContinueError(state,
3789 : " The minimum zone outdoor air rate is set to the maximum zone outdoor air rate. "
3790 : "Simulation continues...");
3791 0 : ShowContinueErrorTimeStamp(state, "");
3792 : } else {
3793 0 : ShowRecurringWarningErrorAtEnd(
3794 : state,
3795 0 : format("{} = \"{}\", For System Outdoor Air Method = ProportionalControlBasedOnDesignOARate, maximum "
3796 : "zone outdoor air rate is not greater than minimum zone outdoor air rate. Error continues...",
3797 : CurrentModuleObject,
3798 0 : this->Name),
3799 0 : this->OAMaxMinLimitErrorIndex);
3800 : }
3801 : }
3802 : } else {
3803 12 : Real64 const zoneOAArea = oaReq.oaFlowArea(state, ZoneNum);
3804 12 : ZoneOAMin = zoneOAArea / ZoneEz;
3805 12 : ZoneOAMax = (zoneOAArea + zoneOAPeople) / ZoneEz;
3806 : }
3807 :
3808 14 : if (zoneOAPeople > 0.0) {
3809 14 : if (state.dataContaminantBalance->ZoneCO2GainFromPeople(ZoneNum) > 0.0) {
3810 14 : if (curZone.zoneMinCO2Sched != nullptr) {
3811 : // Take the schedule value of "Minimum Carbon Dioxide Concentration Schedule Name"
3812 : // in the ZoneControl:ContaminantController
3813 2 : ZoneMinCO2 = curZone.zoneMinCO2Sched->getCurrentVal();
3814 : } else {
3815 12 : ZoneMinCO2 = state.dataContaminantBalance->OutdoorCO2;
3816 : }
3817 :
3818 : // Calculate zone maximum target CO2 concentration in PPM
3819 14 : if (this->SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlDesOcc) {
3820 : // Accumulate CO2 generation from people at design occupancy and current activity level
3821 12 : Real64 CO2PeopleGeneration = 0.0;
3822 24 : for (int const PeopleNum : thisMechVentZone.peopleIndexes) {
3823 12 : CO2PeopleGeneration += state.dataHeatBal->People(PeopleNum).NumberOfPeople *
3824 24 : state.dataHeatBal->People(PeopleNum).CO2RateFactor *
3825 12 : state.dataHeatBal->People(PeopleNum).activityLevelSched->getCurrentVal();
3826 : }
3827 12 : ZoneMaxCO2 = state.dataContaminantBalance->OutdoorCO2 +
3828 12 : (CO2PeopleGeneration * curZone.Multiplier * curZone.ListMultiplier * 1.0e6) / ZoneOAMax;
3829 2 : } else if (curZone.zoneMaxCO2Sched != nullptr) {
3830 2 : ZoneMaxCO2 = curZone.zoneMaxCO2Sched->getCurrentVal();
3831 : } else {
3832 0 : ZoneMaxCO2 = state.dataContaminantBalance->OutdoorCO2 +
3833 0 : (state.dataContaminantBalance->ZoneCO2GainFromPeople(ZoneNum) * curZone.Multiplier *
3834 0 : curZone.ListMultiplier * 1.0e6) /
3835 : ZoneOAMax;
3836 : }
3837 :
3838 14 : if (ZoneMaxCO2 <= ZoneMinCO2) {
3839 0 : ++this->CO2MaxMinLimitErrorCount;
3840 0 : if (this->SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlSchOcc) {
3841 0 : if (this->CO2MaxMinLimitErrorCount < 2) {
3842 0 : ShowSevereError(state, format("{}{} = \"{}\".", RoutineName, CurrentModuleObject, this->Name));
3843 0 : ShowContinueError(
3844 : state,
3845 0 : format("For System Outdoor Air Method = ProportionalControlBasedOnOccupancySchedule, "
3846 : "maximum target CO2 concentration ({:.2R}), is not greater than minimum target "
3847 : "CO2 concentration ({:.2R}).",
3848 : ZoneMaxCO2,
3849 : ZoneMinCO2));
3850 0 : ShowContinueError(state,
3851 : "\"ProportionalControlBasedOnOccupancySchedule\" will not be modeled. "
3852 : "Default \"Standard62.1VentilationRateProcedure\" will be modeled. Simulation "
3853 : "continues...");
3854 0 : ShowContinueErrorTimeStamp(state, "");
3855 : } else {
3856 0 : ShowRecurringWarningErrorAtEnd(state,
3857 0 : format("{} = \"{}\", For System Outdoor Air Method = "
3858 : "ProportionalControlBasedOnOccupancySchedule, maximum "
3859 : "target CO2 concentration is not greater than minimum "
3860 : "target CO2 concentration. Error continues...",
3861 : CurrentModuleObject,
3862 0 : this->Name),
3863 0 : this->CO2MaxMinLimitErrorIndex);
3864 : }
3865 : }
3866 0 : if (this->SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlDesOcc) {
3867 0 : if (this->CO2MaxMinLimitErrorCount < 2) {
3868 0 : ShowSevereError(state, format("{}{} = \"{}\".", RoutineName, CurrentModuleObject, this->Name));
3869 0 : ShowContinueError(
3870 : state,
3871 0 : format("For System Outdoor Air Method = ProportionalControlBasedOnDesignOccupancy, "
3872 : "maximum target CO2 concentration ({:.2R}), is not greater than minimum target "
3873 : "CO2 concentration ({:.2R}).",
3874 : ZoneMaxCO2,
3875 : ZoneMinCO2));
3876 0 : ShowContinueError(state,
3877 : "\"ProportionalControlBasedOnDesignOccupancy\" will not be modeled. "
3878 : "Default \"Standard62.1VentilationRateProcedure\" will be modeled. Simulation "
3879 : "continues...");
3880 0 : ShowContinueErrorTimeStamp(state, "");
3881 : } else {
3882 0 : ShowRecurringWarningErrorAtEnd(state,
3883 0 : format("{} = \"{}\", For System Outdoor Air Method = "
3884 : "ProportionalControlBasedOnDesignOccupancy, maximum "
3885 : "target CO2 concentration is not greater than minimum "
3886 : "target CO2 concentration. Error continues...",
3887 : CurrentModuleObject,
3888 0 : this->Name),
3889 0 : this->CO2MaxMinLimitErrorIndex);
3890 : }
3891 : }
3892 0 : if (this->SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlDesOARate) {
3893 0 : if (this->CO2MaxMinLimitErrorCount < 2) {
3894 0 : ShowSevereError(state, format("{}{} = \"{}\".", RoutineName, CurrentModuleObject, this->Name));
3895 0 : ShowContinueError(
3896 : state,
3897 0 : format("For System Outdoor Air Method = ProportionalControlBasedOnDesignOARate, maximum "
3898 : "target CO2 concentration ({:.2R}), is not greater than minimum target CO2 "
3899 : "concentration ({:.2R}).",
3900 : ZoneMaxCO2,
3901 : ZoneMinCO2));
3902 0 : ShowContinueError(
3903 : state,
3904 : "\"ProportionalControlBasedOnDesignOARate\" will not be modeled. Default "
3905 : "\"Standard62.1VentilationRateProcedure\" will be modeled. Simulation continues...");
3906 0 : ShowContinueErrorTimeStamp(state, "");
3907 : } else {
3908 0 : ShowRecurringWarningErrorAtEnd(state,
3909 0 : format("{} = \"{}\", For System Outdoor Air Method = "
3910 : "ProportionalControlBasedOnDesignOARate, maximum target "
3911 : "CO2 concentration is not greater than minimum target CO2 "
3912 : "concentration. Error continues...",
3913 : CurrentModuleObject,
3914 0 : this->Name),
3915 0 : this->CO2MaxMinLimitErrorIndex);
3916 : }
3917 : }
3918 :
3919 0 : ZoneOA = thisMechVentZone.zoneOABZ / ZoneEz;
3920 : } else {
3921 :
3922 14 : if (state.dataContaminantBalance->ZoneAirCO2(ZoneNum) <= ZoneMinCO2) {
3923 : // Zone air CO2 concentration is less than minimum zone CO2 concentration, set the Zone OA flow
3924 : // rate to minimum Zone OA flow rate when the zone is unoccupied
3925 4 : ZoneOA = ZoneOAMin;
3926 10 : } else if (state.dataContaminantBalance->ZoneAirCO2(ZoneNum) >= ZoneMaxCO2) {
3927 : // Zone air CO2 concentration is greater than maximum zone CO2 concentration, set the Zone OA flow
3928 : // rate to maximum Zone OA flow rate (i.e.
3929 : // ZoneOACalc[static_cast<int>(DataSizing::OAFlowCalcMethod::PerArea)] + ZoneOAPeople)
3930 4 : ZoneOA = ZoneOAMax;
3931 : } else {
3932 : // Zone air CO2 concentration is between maximum and minimum limits of zone CO2 concentration,
3933 : // set Zone OA flow rate by proportionally adjusting between ZoneOAMin and ZoneOAMax
3934 6 : ZoneOA = ZoneOAMin +
3935 6 : (ZoneOAMax - ZoneOAMin) * ((state.dataContaminantBalance->ZoneAirCO2(ZoneNum) - ZoneMinCO2) /
3936 6 : (ZoneMaxCO2 - ZoneMinCO2));
3937 : }
3938 : }
3939 : } else {
3940 0 : if (state.dataGlobal->DisplayExtraWarnings) {
3941 0 : ++this->CO2GainErrorCount;
3942 0 : if (this->SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlSchOcc) {
3943 0 : if (this->CO2GainErrorCount < 2) {
3944 0 : ShowSevereError(state, format("{}{} = \"{}\".", RoutineName, CurrentModuleObject, this->Name));
3945 0 : ShowContinueError(
3946 : state,
3947 0 : format("For System Outdoor Air Method = ProportionalControlBasedOnOccupancySchedule, CO2 "
3948 : "generation from people is not greater than zero. Occurs in Zone =\"{}\". ",
3949 0 : curZone.Name));
3950 0 : ShowContinueError(state,
3951 : "\"ProportionalControlBasedOnOccupancySchedule\" will not be modeled. "
3952 : "Default \"Standard62.1VentilationRateProcedure\" will be modeled. Simulation "
3953 : "continues...");
3954 0 : ShowContinueErrorTimeStamp(state, "");
3955 : } else {
3956 0 : ShowRecurringWarningErrorAtEnd(
3957 : state,
3958 0 : format("{} = \"{}\", For System Outdoor Air Method = "
3959 : "ProportionalControlBasedOnOccupancySchedule, "
3960 : "CO2 generation from people is not greater than zero. Error continues...",
3961 : CurrentModuleObject,
3962 0 : this->Name),
3963 0 : this->CO2GainErrorIndex);
3964 : }
3965 : }
3966 0 : if (this->SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlDesOcc) {
3967 0 : if (this->CO2GainErrorCount < 2) {
3968 0 : ShowSevereError(state, format("{}{} = \"{}\".", RoutineName, CurrentModuleObject, this->Name));
3969 0 : ShowContinueError(
3970 : state,
3971 0 : format("For System Outdoor Air Method = ProportionalControlBasedOnDesignOccupancy, CO2 "
3972 : "generation from people is not greater than zero. Occurs in Zone =\"{}\". ",
3973 0 : curZone.Name));
3974 0 : ShowContinueError(state,
3975 : "\"ProportionalControlBasedOnDesignOccupancy\" will not be modeled. "
3976 : "Default \"Standard62.1VentilationRateProcedure\" will be modeled. Simulation "
3977 : "continues...");
3978 0 : ShowContinueErrorTimeStamp(state, "");
3979 : } else {
3980 0 : ShowRecurringWarningErrorAtEnd(
3981 : state,
3982 0 : format(
3983 : "{} = \"{}\", For System Outdoor Air Method = ProportionalControlBasedOnDesignOccupancy, "
3984 : "CO2 generation from people is not greater than zero. Error continues...",
3985 : CurrentModuleObject,
3986 0 : this->Name),
3987 0 : this->CO2GainErrorIndex);
3988 : }
3989 : }
3990 : }
3991 0 : ZoneOA = thisMechVentZone.zoneOABZ / ZoneEz;
3992 : }
3993 : } else {
3994 : // zoneOAPeople is less than or equal to zero
3995 0 : ZoneOA = thisMechVentZone.zoneOABZ / ZoneEz;
3996 : }
3997 : } else {
3998 : // ZoneControl:ContaminantController is scheduled off (not available)
3999 0 : ZoneOA = thisMechVentZone.zoneOABZ / ZoneEz;
4000 : }
4001 : } else {
4002 : // "Carbon Dioxide Control Availability Schedule" for ZoneControl:ContaminantController not found
4003 0 : ZoneOA = thisMechVentZone.zoneOABZ / ZoneEz;
4004 : }
4005 14 : SysOA = SysOA + ZoneOA;
4006 : }
4007 :
4008 : // Get the zone supply air flow rate
4009 31745 : Real64 ZoneSA = 0.0; // Zone supply air flow rate
4010 31745 : Real64 ZonePA = 0.0; // Zone primary air flow rate
4011 31745 : Ep = 1.0;
4012 31745 : if (ZoneEquipConfigNum > 0) {
4013 31745 : auto &curZoneEquipConfig = state.dataZoneEquip->ZoneEquipConfig(ZoneEquipConfigNum);
4014 63490 : for (int InNodeIndex = 1; InNodeIndex <= curZoneEquipConfig.NumInletNodes; ++InNodeIndex) {
4015 : // Assume primary air is always stored at the AirDistUnitCool (cooling deck if dual duct)
4016 31745 : int PriNode = curZoneEquipConfig.AirDistUnitCool(InNodeIndex).InNode; // primary node of zone terminal unit
4017 31745 : Real64 MassFlowRate = 0.0;
4018 31745 : if (PriNode > 0) {
4019 31742 : NodeTemp = state.dataLoopNodes->Node(PriNode).Temp;
4020 31742 : NodeHumRat = state.dataLoopNodes->Node(PriNode).HumRat;
4021 31742 : MassFlowRate = state.dataLoopNodes->Node(PriNode).MassFlowRate;
4022 : }
4023 : // total primary air to terminal units of the zone
4024 31745 : if (MassFlowRate > 0.0)
4025 30341 : ZonePA +=
4026 30341 : MassFlowRate / Psychrometrics::PsyRhoAirFnPbTdbW(state, state.dataEnvrn->OutBaroPress, NodeTemp, NodeHumRat);
4027 :
4028 : // or InletNode = ZoneEquipConfig(ZoneEquipConfigNum)%AirDistUnitCool(InNodeIndex)%OutNode
4029 31745 : int InletNode = curZoneEquipConfig.InletNode(InNodeIndex); // outlet node of zone terminal unit
4030 31745 : MassFlowRate = 0.0;
4031 31745 : if (InletNode > 0) {
4032 31745 : NodeTemp = state.dataLoopNodes->Node(InletNode).Temp;
4033 31745 : NodeHumRat = state.dataLoopNodes->Node(InletNode).HumRat; // ZoneAirHumRat(ZoneNum)
4034 31745 : MassFlowRate = state.dataLoopNodes->Node(InletNode).MassFlowRate;
4035 : }
4036 : // total supply air to the zone
4037 31745 : if (MassFlowRate > 0.0)
4038 30341 : ZoneSA +=
4039 30341 : MassFlowRate / Psychrometrics::PsyRhoAirFnPbTdbW(state, state.dataEnvrn->OutBaroPress, NodeTemp, NodeHumRat);
4040 : }
4041 :
4042 : // calc zone primary air fraction
4043 31745 : if (ZoneSA > 0.0) Ep = ZonePA / ZoneSA;
4044 31745 : if (Ep > 1.0) Ep = 1.0;
4045 : }
4046 :
4047 : // Calc the zone OA fraction = Zone OA flow rate / Zone supply air flow rate
4048 31745 : if (ZoneSA > 0.0) {
4049 30341 : ZoneOAFrac = ZoneOA / ZoneSA;
4050 : // Zone OA fraction cannot be more than 1
4051 30341 : if (ZoneOAFrac > 1.0) ZoneOAFrac = 1.0;
4052 : } else {
4053 1404 : ZoneOAFrac = 0.0;
4054 : }
4055 :
4056 : // added for TRACE - zone maximum OA fraction - calculate the adjustment factor for the TU/zone supply air flow
4057 : // only for VRP system OA method
4058 31745 : curZoneSysEnergyDemand.SupplyAirAdjustFactor = 1.0;
4059 :
4060 31745 : if (this->SystemOAMethod == DataSizing::SysOAMethod::VRP || this->SystemOAMethod == DataSizing::SysOAMethod::VRPL) {
4061 31731 : if (ZoneOAFrac > this->ZoneMaxOAFraction) {
4062 0 : if (this->ZoneMaxOAFraction > 0.0) {
4063 0 : curZoneSysEnergyDemand.SupplyAirAdjustFactor = ZoneOAFrac / this->ZoneMaxOAFraction;
4064 : } else {
4065 0 : curZoneSysEnergyDemand.SupplyAirAdjustFactor = 1.0;
4066 : }
4067 :
4068 : // cap zone OA fraction at the maximum specified
4069 0 : ZoneOAFrac = this->ZoneMaxOAFraction;
4070 : }
4071 : }
4072 :
4073 : // Zone air secondary recirculation fraction
4074 31745 : Er = thisMechVentZone.ZoneSecondaryRecirculation;
4075 31745 : if (Er > 0.0) {
4076 : // multi-path ventilation system using VRP
4077 0 : Fa = Ep + (1.0 - Ep) * Er;
4078 0 : Fb = Ep;
4079 0 : Fc = 1.0 - (1.0 - ZoneEz) * (1.0 - Er) * (1.0 - Ep);
4080 :
4081 : // Calc zone ventilation efficiency
4082 0 : if (Fa > 0.0) {
4083 0 : Evz = 1.0 + Xs * Fb / Fa - ZoneOAFrac * Ep * Fc / Fa;
4084 : } else {
4085 0 : Evz = 1.0;
4086 : }
4087 : } else {
4088 : // single-path ventilation system
4089 31745 : Evz = 1.0 + Xs - ZoneOAFrac;
4090 : }
4091 :
4092 : // calc system ventilation efficiency = Minimum of zone ventilation efficiency
4093 31745 : if (Evz < 0.0) Evz = 0.0;
4094 31745 : if (Evz < SysEv) SysEv = Evz;
4095 :
4096 : } // zone loop
4097 :
4098 : // Calc the system supply OA flow rate counting the system ventilation efficiency
4099 15874 : if (SysEv <= 0.0) SysEv = 1.0;
4100 :
4101 : // Calc system outdoor air requirement
4102 15874 : if (this->SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlSchOcc ||
4103 15874 : this->SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlDesOcc ||
4104 15868 : this->SystemOAMethod == DataSizing::SysOAMethod::ProportionalControlDesOARate) {
4105 8 : SysOA = SysOA / SysEv;
4106 15866 : } else if (this->SystemOAMethod == DataSizing::SysOAMethod::VRPL && this->SysDesOA > 0.0) {
4107 : // Limit system OA to design OA minimum flow rate, as per ASHRAE Guideline 36-2018 Section 5.16.3.1
4108 : // If no system sizing run is done (i.e. no Sizing:System) the design outdoor air flow rate is not known
4109 1 : SysOA = min(SysOAuc / SysEv, this->SysDesOA);
4110 : } else {
4111 15865 : SysOA = SysOAuc / SysEv;
4112 : }
4113 : }
4114 :
4115 : // Finally calc the system supply OA mass flow rate
4116 17978 : MechVentOAMassFlow = SysOA * state.dataEnvrn->StdRhoAir;
4117 : }
4118 : }
4119 17982 : return MechVentOAMassFlow;
4120 : }
4121 :
4122 70847 : void OAControllerProps::CalcOAEconomizer(EnergyPlusData &state,
4123 : int const AirLoopNum,
4124 : Real64 const OutAirMinFrac,
4125 : Real64 &OASignal,
4126 : bool &HighHumidityOperationFlag,
4127 : bool const FirstHVACIteration)
4128 : {
4129 70847 : int constexpr MaxIte(500); // Maximum number of iterations
4130 70847 : Real64 constexpr Acc(0.0001); // Accuracy of result
4131 : bool AirLoopEconoLockout; // Economizer lockout flag
4132 : bool AirLoopNightVent; // Night Ventilation flag for air loop
4133 : bool EconomizerOperationFlag; // TRUE if OA economizer is active
4134 : Real64 EconomizerAirFlowScheduleValue; // value of economizer operation schedule (push-button type control schedule)
4135 : Real64 MaximumOAFracBySetPoint; // The maximum OA fraction due to freezing cooling coil check
4136 : Real64 OutAirSignal; // Used to set OA mass flow rate
4137 : Real64 minOAFrac;
4138 :
4139 70847 : if (AirLoopNum > 0) {
4140 : // Check lockout with heating for any airloop - will lockout economizer even on airloops without a unitary system
4141 70847 : if (this->Lockout == LockoutType::LockoutWithHeatingPossible) {
4142 : // For all system types (even ones that don't set AirLoopEconoLockout) lock out economizer if unfavorable for heating
4143 2 : if (state.dataAirLoop->AirLoopControlInfo(AirLoopNum).CheckHeatRecoveryBypassStatus &&
4144 1 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).OASysComponentsSimulated) {
4145 :
4146 1 : if (this->MixedAirTempAtMinOAFlow <= state.dataLoopNodes->Node(this->MixNode).TempSetPoint) {
4147 1 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).EconomizerFlowLocked = true;
4148 : // this->OAMassFlow = AirLoopFlow( AirLoopNum ).MinOutAir;
4149 : // AirLoopFlow( AirLoopNum ).OAFrac = this->OAMassFlow / this->MixMassFlow;
4150 1 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).EconoLockout = true;
4151 : } else {
4152 0 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).EconomizerFlowLocked = false;
4153 0 : this->HRHeatingCoilActive = 0;
4154 : }
4155 1 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).CheckHeatRecoveryBypassStatus = false;
4156 : }
4157 : }
4158 : }
4159 :
4160 70847 : if (AirLoopNum > 0) {
4161 70847 : AirLoopEconoLockout = state.dataAirLoop->AirLoopControlInfo(AirLoopNum).EconoLockout;
4162 70847 : AirLoopNightVent = state.dataAirLoop->AirLoopControlInfo(AirLoopNum).NightVent;
4163 : } else {
4164 0 : AirLoopEconoLockout = false;
4165 0 : AirLoopNightVent = false;
4166 : }
4167 :
4168 : // Define an outside air signal
4169 70847 : if (this->MixedAirSPMNum > 0) {
4170 0 : this->CoolCoilFreezeCheck = SetPointManager::GetCoilFreezingCheckFlag(state, this->MixedAirSPMNum);
4171 : } else {
4172 70847 : this->CoolCoilFreezeCheck = false;
4173 : }
4174 :
4175 70847 : if (std::abs(this->RetTemp - this->InletTemp) > HVAC::SmallTempDiff) {
4176 70836 : OutAirSignal = (this->RetTemp - this->MixSetTemp) / (this->RetTemp - this->InletTemp);
4177 70836 : if (this->CoolCoilFreezeCheck) {
4178 0 : this->MaxOAFracBySetPoint = 0.0;
4179 0 : MaximumOAFracBySetPoint = OutAirSignal;
4180 : }
4181 : } else {
4182 11 : if (this->RetTemp - this->MixSetTemp < 0.0) {
4183 0 : if (this->RetTemp - this->InletTemp >= 0.0) {
4184 0 : OutAirSignal = -1.0;
4185 : } else {
4186 0 : OutAirSignal = 1.0;
4187 : }
4188 : } else {
4189 11 : if (this->RetTemp - this->InletTemp >= 0.0) {
4190 11 : OutAirSignal = 1.0;
4191 : } else {
4192 0 : OutAirSignal = -1.0;
4193 : }
4194 : }
4195 : }
4196 70847 : OutAirSignal = min(max(OutAirSignal, OutAirMinFrac), 1.0);
4197 :
4198 : // If no economizer, set to minimum and disable economizer and high humidity control
4199 70847 : if (this->Econo == EconoOp::NoEconomizer) {
4200 43430 : OutAirSignal = OutAirMinFrac;
4201 43430 : EconomizerOperationFlag = false;
4202 43430 : EconomizerAirFlowScheduleValue = 0.0;
4203 43430 : HighHumidityOperationFlag = false;
4204 27417 : } else if (this->MaxOA < HVAC::SmallAirVolFlow) {
4205 0 : OutAirSignal = OutAirMinFrac;
4206 0 : EconomizerOperationFlag = false;
4207 0 : EconomizerAirFlowScheduleValue = 0.0;
4208 0 : HighHumidityOperationFlag = false;
4209 27417 : } else if (AirLoopEconoLockout) {
4210 1 : OutAirSignal = OutAirMinFrac;
4211 1 : EconomizerOperationFlag = false;
4212 1 : EconomizerAirFlowScheduleValue = 0.0;
4213 1 : HighHumidityOperationFlag = false;
4214 : } else {
4215 : // Changed by Amit for new implementation
4216 : // Otherwise do the limit checks
4217 27416 : EconomizerOperationFlag = true;
4218 : // Outside air temp greater than mix air setpoint
4219 27416 : if (this->InletTemp > this->MixSetTemp) {
4220 20272 : OutAirSignal = 1.0;
4221 : }
4222 : // Return air temp limit
4223 27416 : if (this->Econo == EconoOp::DifferentialDryBulb) {
4224 3487 : if (this->InletTemp > this->RetTemp) {
4225 44 : OutAirSignal = OutAirMinFrac;
4226 44 : EconomizerOperationFlag = false;
4227 : }
4228 3487 : this->Checksetpoints(state, OutAirMinFrac, OutAirSignal, EconomizerOperationFlag);
4229 : }
4230 : // Return air enthalpy limit
4231 27416 : if (this->Econo == EconoOp::DifferentialEnthalpy) {
4232 15688 : if (this->InletEnth > this->RetEnth) {
4233 13337 : OutAirSignal = OutAirMinFrac;
4234 13337 : EconomizerOperationFlag = false;
4235 : }
4236 15688 : this->Checksetpoints(state, OutAirMinFrac, OutAirSignal, EconomizerOperationFlag);
4237 : }
4238 : // Outside air temperature limit
4239 27416 : if (this->Econo == EconoOp::FixedDryBulb) {
4240 8241 : this->Checksetpoints(state, OutAirMinFrac, OutAirSignal, EconomizerOperationFlag);
4241 : }
4242 : // Fixed Enthalpy limit
4243 27416 : if (this->Econo == EconoOp::FixedEnthalpy) {
4244 0 : this->Checksetpoints(state, OutAirMinFrac, OutAirSignal, EconomizerOperationFlag);
4245 : }
4246 : // FIXED DEW POINT AND DRY BULB TEMPERATURE STRATEGY
4247 27416 : if (this->Econo == EconoOp::FixedDewPointAndDryBulb) {
4248 0 : this->Checksetpoints(state, OutAirMinFrac, OutAirSignal, EconomizerOperationFlag);
4249 : }
4250 : // ELECRONIC ENTHALPY, HUMIDITY RATIO CURVE
4251 27416 : if (this->Econo == EconoOp::ElectronicEnthalpy) {
4252 0 : this->Checksetpoints(state, OutAirMinFrac, OutAirSignal, EconomizerOperationFlag);
4253 : }
4254 : // Differential dry bulb and enthalpy strategy
4255 27416 : if (this->Econo == EconoOp::DifferentialDryBulbAndEnthalpy) {
4256 0 : if (this->InletTemp > this->RetTemp) {
4257 0 : OutAirSignal = OutAirMinFrac;
4258 0 : EconomizerOperationFlag = false;
4259 : }
4260 0 : if (this->InletEnth > this->RetEnth) {
4261 0 : OutAirSignal = OutAirMinFrac;
4262 0 : EconomizerOperationFlag = false;
4263 : }
4264 0 : this->Checksetpoints(state, OutAirMinFrac, OutAirSignal, EconomizerOperationFlag);
4265 : }
4266 :
4267 27416 : if (this->TempLowLim != HVAC::BlankNumeric && this->OATemp < this->TempLowLim) {
4268 5748 : OutAirSignal = OutAirMinFrac;
4269 5748 : EconomizerOperationFlag = false;
4270 : }
4271 : // Increase air flow for humidity control
4272 : // (HumidistatZoneNum is greater than 0 IF High Humidity Control Flag = YES, checked in GetInput)
4273 27416 : if (this->HumidistatZoneNum > 0) {
4274 : // IF humidistat senses a moisture load check to see if modifying air flow is appropriate, otherwise disable modified air flow
4275 4 : if (state.dataZoneEnergyDemand->ZoneSysMoistureDemand(this->HumidistatZoneNum).TotalOutputRequired < 0.0) {
4276 : // IF OAController is not allowed to modify air flow during high outdoor humrat condition, then disable modified air flow
4277 : // if indoor humrat is less than or equal to outdoor humrat
4278 8 : if (!this->ModifyDuringHighOAMoisture &&
4279 4 : (state.dataLoopNodes->Node(this->NodeNumofHumidistatZone).HumRat - this->OAHumRat) <= HVAC::SmallHumRatDiff) {
4280 3 : HighHumidityOperationFlag = false;
4281 : } else {
4282 1 : HighHumidityOperationFlag = true;
4283 : }
4284 : } else {
4285 0 : HighHumidityOperationFlag = false;
4286 : }
4287 : } else {
4288 27412 : HighHumidityOperationFlag = false;
4289 : }
4290 :
4291 : // Check time of day economizer schedule, enable economizer if schedule value > 0
4292 27416 : EconomizerAirFlowScheduleValue = 0.0;
4293 27416 : if (this->economizerOASched != nullptr) {
4294 0 : EconomizerAirFlowScheduleValue = this->economizerOASched->getCurrentVal();
4295 0 : if (EconomizerAirFlowScheduleValue > 0.0) {
4296 0 : EconomizerOperationFlag = true;
4297 0 : OutAirSignal = 1.0;
4298 : }
4299 : }
4300 : }
4301 :
4302 : // OutAirSignal will not give exactly the correct mixed air temperature (equal to the setpoint) since
4303 : // it was calculated using the approximate method of sensible energy balance. Now we have to get the
4304 : // accurate result using a full mass, enthalpy and moisture balance and iteration.
4305 70847 : if (OutAirSignal > OutAirMinFrac && OutAirSignal < 1.0 && this->MixMassFlow > HVAC::VerySmallMassFlow &&
4306 1382 : this->ControllerType == MixedAirControllerType::ControllerOutsideAir && !AirLoopNightVent) {
4307 : int SolFla; // Flag of solver
4308 :
4309 1382 : if (AirLoopNum > 0) {
4310 :
4311 1382 : if (state.dataAirLoop->OutsideAirSys(state.dataAirLoop->AirLoopControlInfo(AirLoopNum).OASysNum).NumComponents == 1) {
4312 : // no need to simulate OA System if only a mixer is used in the OutsideAirSystem
4313 :
4314 4131 : auto f = [&state, this](Real64 const OASignal) {
4315 4131 : Real64 const OAMassFlowRate = OASignal * this->MixMassFlow;
4316 4131 : Real64 const RecircMassFlowRate = max(this->MixMassFlow - OAMassFlowRate, 0.0);
4317 4131 : Real64 const RecircEnth = state.dataLoopNodes->Node(this->RetNode).Enthalpy;
4318 4131 : Real64 const RecircHumRat = state.dataLoopNodes->Node(this->RetNode).HumRat;
4319 : Real64 const MixEnth =
4320 4131 : (RecircMassFlowRate * RecircEnth + OAMassFlowRate * state.dataLoopNodes->Node(this->OANode).Enthalpy) / this->MixMassFlow;
4321 : Real64 const MixHumRat =
4322 4131 : (RecircMassFlowRate * RecircHumRat + OAMassFlowRate * state.dataLoopNodes->Node(this->OANode).HumRat) / this->MixMassFlow;
4323 4131 : Real64 const MixTemp = Psychrometrics::PsyTdbFnHW(MixEnth, MixHumRat);
4324 4131 : return state.dataLoopNodes->Node(this->MixNode).TempSetPoint - MixTemp;
4325 1377 : };
4326 :
4327 1377 : General::SolveRoot(state, Acc, MaxIte, SolFla, OASignal, f, OutAirMinFrac, 1.0);
4328 1377 : if (SolFla < 0) {
4329 0 : OASignal = OutAirSignal;
4330 : }
4331 :
4332 : } else {
4333 :
4334 : // simulate OA System if equipment exists other than the mixer (e.g., heating/cooling coil, HX, etc.)
4335 :
4336 : // 1 - check min OA flow result
4337 5 : if (this->FixedMin) {
4338 4 : state.dataLoopNodes->Node(this->OANode).MassFlowRate =
4339 4 : min(max(this->ExhMassFlow, OutAirMinFrac * state.dataAirLoop->AirLoopFlow(AirLoopNum).DesSupply),
4340 4 : state.dataLoopNodes->Node(this->MixNode).MassFlowRate);
4341 4 : state.dataLoopNodes->Node(this->RelNode).MassFlowRate =
4342 4 : max(state.dataLoopNodes->Node(this->OANode).MassFlowRate - this->ExhMassFlow, 0.0);
4343 : // save actual OA flow frac for use as min value for RegulaFalsi call
4344 4 : minOAFrac = max(OutAirMinFrac, state.dataLoopNodes->Node(this->OANode).MassFlowRate / this->MixMassFlow);
4345 : } else {
4346 1 : state.dataLoopNodes->Node(this->OANode).MassFlowRate =
4347 1 : max(this->ExhMassFlow, OutAirMinFrac * state.dataLoopNodes->Node(this->MixNode).MassFlowRate);
4348 1 : state.dataLoopNodes->Node(this->RelNode).MassFlowRate =
4349 1 : max(state.dataLoopNodes->Node(this->OANode).MassFlowRate - this->ExhMassFlow, 0.0);
4350 : // save actual OA flow frac for use as min value for RegulaFalsi call
4351 1 : minOAFrac = max(OutAirMinFrac, state.dataLoopNodes->Node(this->OANode).MassFlowRate / this->MixMassFlow);
4352 : }
4353 5 : SimOASysComponents(state, state.dataAirLoop->AirLoopControlInfo(AirLoopNum).OASysNum, FirstHVACIteration, AirLoopNum);
4354 5 : Real64 lowFlowResiduum = state.dataLoopNodes->Node(this->MixNode).TempSetPoint - state.dataLoopNodes->Node(this->MixNode).Temp;
4355 :
4356 : // 2 - check max OA flow result
4357 5 : state.dataLoopNodes->Node(this->OANode).MassFlowRate = max(this->ExhMassFlow, state.dataLoopNodes->Node(this->MixNode).MassFlowRate);
4358 5 : state.dataLoopNodes->Node(this->RelNode).MassFlowRate =
4359 5 : max(state.dataLoopNodes->Node(this->OANode).MassFlowRate - this->ExhMassFlow, 0.0);
4360 5 : SimOASysComponents(state, state.dataAirLoop->AirLoopControlInfo(AirLoopNum).OASysNum, FirstHVACIteration, AirLoopNum);
4361 5 : Real64 highFlowResiduum = state.dataLoopNodes->Node(this->MixNode).TempSetPoint - state.dataLoopNodes->Node(this->MixNode).Temp;
4362 :
4363 : // 3 - test to ensure RegulaFalsi can find an answer
4364 5 : if ((sign(lowFlowResiduum) == sign(highFlowResiduum))) {
4365 4 : OASignal = OutAirSignal;
4366 : } else {
4367 : // 4 - find result
4368 :
4369 4 : auto f = [&state, this, FirstHVACIteration, AirLoopNum](Real64 const OASignal) {
4370 4 : Real64 const MixMassFlowRate = this->MixMassFlow;
4371 4 : int const OASysNum = state.dataAirLoop->AirLoopControlInfo(AirLoopNum).OASysNum;
4372 4 : Real64 localExhMassFlow = state.dataAirLoop->AirLoopControlInfo(AirLoopNum).ZoneExhMassFlow;
4373 4 : Real64 const OAMassFlowRate = max(localExhMassFlow, OASignal * MixMassFlowRate);
4374 4 : state.dataLoopNodes->Node(this->OANode).MassFlowRate = OAMassFlowRate; // set OA node mass flow rate
4375 4 : state.dataLoopNodes->Node(this->RelNode).MassFlowRate =
4376 4 : max(OAMassFlowRate - localExhMassFlow, 0.0); // set relief node mass flow rate to maintain mixer continuity calcs
4377 4 : SimOASysComponents(state, OASysNum, FirstHVACIteration, AirLoopNum);
4378 4 : return state.dataLoopNodes->Node(this->MixNode).TempSetPoint - state.dataLoopNodes->Node(this->MixNode).Temp;
4379 1 : };
4380 :
4381 1 : General::SolveRoot(state, (Acc / 10.0), MaxIte, SolFla, OASignal, f, minOAFrac, 1.0);
4382 1 : if (SolFla < 0) { // if RegulaFalsi fails to find a solution, returns -1 or -2, set to existing OutAirSignal
4383 0 : OASignal = OutAirSignal;
4384 : }
4385 : }
4386 : }
4387 :
4388 : } else {
4389 :
4390 0 : auto f = [&state, this](Real64 const OASignal) {
4391 0 : Real64 const MixMassFlowRate = this->MixMassFlow;
4392 0 : Real64 OAMassFlowRate = OASignal * MixMassFlowRate;
4393 0 : Real64 RecircMassFlowRate = max(MixMassFlowRate - OAMassFlowRate, 0.0);
4394 0 : Real64 RecircEnth = state.dataLoopNodes->Node(this->RetNode).Enthalpy;
4395 0 : Real64 RecircHumRat = state.dataLoopNodes->Node(this->RetNode).HumRat;
4396 : Real64 MixEnth =
4397 0 : (RecircMassFlowRate * RecircEnth + OAMassFlowRate * state.dataLoopNodes->Node(this->OANode).Enthalpy) / MixMassFlowRate;
4398 : Real64 MixHumRat =
4399 0 : (RecircMassFlowRate * RecircHumRat + OAMassFlowRate * state.dataLoopNodes->Node(this->OANode).HumRat) / MixMassFlowRate;
4400 0 : Real64 MixTemp = Psychrometrics::PsyTdbFnHW(MixEnth, MixHumRat);
4401 0 : return state.dataLoopNodes->Node(this->MixNode).TempSetPoint - MixTemp;
4402 0 : };
4403 :
4404 0 : General::SolveRoot(state, Acc, MaxIte, SolFla, OASignal, f, OutAirMinFrac, 1.0);
4405 0 : if (SolFla < 0) {
4406 0 : OASignal = OutAirSignal;
4407 : }
4408 : }
4409 :
4410 1382 : } else {
4411 69465 : OASignal = OutAirSignal;
4412 : }
4413 :
4414 : // Economizer choice "Bypass" forces minimum OA except when high humidity air flow is active based on indoor RH
4415 70847 : if (this->EconBypass && EconomizerAirFlowScheduleValue == 0.0) {
4416 31549 : OASignal = OutAirMinFrac;
4417 : }
4418 :
4419 : // Set outdoor air signal based on OA flow ratio if high humidity air flow is enabled
4420 70847 : if (HighHumidityOperationFlag) {
4421 1 : if (this->MixMassFlow > 0.0) {
4422 : // calculate the actual ratio of outside air to mixed air so the magnitude of OA during high humidity control is correct
4423 1 : OASignal = max(OutAirMinFrac, (this->HighRHOAFlowRatio * this->MaxOAMassFlowRate / this->MixMassFlow));
4424 1 : this->OALimitingFactor = OALimitFactor::HighHum;
4425 : }
4426 : }
4427 :
4428 70847 : if (this->CoolCoilFreezeCheck) {
4429 0 : MaximumOAFracBySetPoint = min(max(MaximumOAFracBySetPoint, 0.0), 1.0);
4430 0 : this->MaxOAFracBySetPoint = MaximumOAFracBySetPoint;
4431 :
4432 : // This should not be messing with OutAirMinFrac, freeze protection should only limit economizer operation
4433 : // if (MaximumOAFracBySetPoint < OutAirMinFrac) {
4434 : // OutAirMinFrac = MaximumOAFracBySetPoint;
4435 : // if (AirLoopNum > 0) AirLoopFlow(AirLoopNum).MinOutAir = OutAirMinFrac * this->MixMassFlow;
4436 : //}
4437 0 : if (MaximumOAFracBySetPoint < OASignal) {
4438 0 : OASignal = MaximumOAFracBySetPoint;
4439 0 : this->OALimitingFactor = OALimitFactor::Limits;
4440 : }
4441 0 : if (OutAirMinFrac > OASignal) {
4442 0 : OASignal = OutAirMinFrac;
4443 0 : this->OALimitingFactor = OALimitFactor::Limits;
4444 : }
4445 : }
4446 :
4447 70847 : if (AirLoopNum > 0) {
4448 :
4449 : // Set the air loop economizer and high humidity control flags.
4450 70847 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).EconoActive = EconomizerOperationFlag;
4451 70847 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).HighHumCtrlActive = HighHumidityOperationFlag;
4452 70847 : if (state.dataAirLoop->AirLoopControlInfo(AirLoopNum).EconomizerFlowLocked) {
4453 1 : this->OAMassFlow = state.dataAirLoop->AirLoopFlow(AirLoopNum).MinOutAir;
4454 1 : state.dataAirLoop->AirLoopFlow(AirLoopNum).OAFrac = this->OAMassFlow / this->MixMassFlow;
4455 1 : state.dataAirLoop->AirLoopFlow(AirLoopNum).OAFlow = this->OAMassFlow;
4456 : }
4457 :
4458 : // Check heat exchanger bypass control
4459 70847 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).HeatRecoveryBypass = false;
4460 70847 : this->HeatRecoveryBypassStatus = 0;
4461 70847 : if (EconomizerOperationFlag) {
4462 1411 : if (this->HeatRecoveryBypassControlType == HVAC::BypassWhenWithinEconomizerLimits) {
4463 1402 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).HeatRecoveryBypass = true;
4464 1402 : this->HeatRecoveryBypassStatus = 1;
4465 9 : } else if (this->HeatRecoveryBypassControlType == HVAC::BypassWhenOAFlowGreaterThanMinimum) {
4466 9 : Real64 OAMassFlowMin = OutAirMinFrac * state.dataAirLoop->AirLoopFlow(AirLoopNum).DesSupply;
4467 9 : Real64 OAMassFlowActual = OASignal * this->MixMassFlow;
4468 9 : Real64 reasonablySmallMassFlow = 1e-6;
4469 9 : if (OAMassFlowActual > (OAMassFlowMin + reasonablySmallMassFlow)) {
4470 6 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).HeatRecoveryBypass = true;
4471 6 : this->HeatRecoveryBypassStatus = 1;
4472 : }
4473 : }
4474 : }
4475 : }
4476 :
4477 : // Set economizer report variable and status flag
4478 70847 : if (this->Econo == EconoOp::NoEconomizer) {
4479 : // No economizer
4480 43430 : this->EconomizerStatus = 0;
4481 43430 : this->EconoActive = false;
4482 : } else {
4483 : // With economizer.
4484 27417 : if (EconomizerOperationFlag) {
4485 : // Economizer is enabled
4486 1411 : this->EconomizerStatus = 1;
4487 1411 : this->EconoActive = true;
4488 1411 : if ((OASignal > OutAirMinFrac) && !HighHumidityOperationFlag) {
4489 1382 : this->OALimitingFactor = OALimitFactor::Economizer;
4490 : }
4491 : } else {
4492 : // Economizer is disabled
4493 26006 : this->EconomizerStatus = 0;
4494 26006 : this->EconoActive = false;
4495 : }
4496 : }
4497 :
4498 : // Night ventilation control overrides economizer and high humidity control.
4499 70847 : if (AirLoopNightVent) {
4500 0 : OASignal = 1.0;
4501 0 : this->OALimitingFactor = OALimitFactor::NightVent;
4502 : }
4503 :
4504 : // Set high humidity control report variable and status flag
4505 70847 : if (HighHumidityOperationFlag) {
4506 1 : this->HighHumCtrlStatus = 1;
4507 1 : this->HighHumCtrlActive = true;
4508 : } else {
4509 70846 : this->HighHumCtrlStatus = 0;
4510 70846 : this->HighHumCtrlActive = false;
4511 : }
4512 70847 : }
4513 :
4514 383145 : void OAMixerProps::CalcOAMixer(EnergyPlusData &state)
4515 : {
4516 :
4517 : // SUBROUTINE INFORMATION:
4518 : // AUTHOR Fred Buhl
4519 : // DATE WRITTEN Oct 1998
4520 :
4521 : // PURPOSE OF THIS SUBROUTINE
4522 : // Calculate the mixed air flow and conditions
4523 :
4524 : // Define a recirculation mass flow rate
4525 383145 : Real64 RecircMassFlowRate = this->RetMassFlowRate - this->RelMassFlowRate;
4526 : // In certain low flow conditions the return air mass flow rate can be below the outside air value established
4527 : // by the user. This check will ensure that this condition does not result in non-physical air properties.
4528 383145 : if (RecircMassFlowRate < 0.0) {
4529 36 : RecircMassFlowRate = 0.0;
4530 36 : this->RelMassFlowRate = this->RetMassFlowRate;
4531 : }
4532 :
4533 : // Pass through the return air conditions to the relief air stream. The return air is "split" to
4534 : // the relief air and the recirculation air.
4535 383145 : this->RelTemp = this->RetTemp;
4536 383145 : this->RelHumRat = this->RetHumRat;
4537 383145 : this->RelEnthalpy = this->RetEnthalpy;
4538 383145 : this->RelPressure = this->RetPressure;
4539 383145 : Real64 RecircPressure = this->RetPressure;
4540 383145 : Real64 RecircEnthalpy = this->RetEnthalpy;
4541 383145 : Real64 RecircHumRat = this->RetHumRat;
4542 : // The recirculation air and the outside air are mixed to form the mixed air stream
4543 383145 : this->MixMassFlowRate = this->OAMassFlowRate + RecircMassFlowRate;
4544 : // Check for zero flow
4545 383145 : if (this->MixMassFlowRate <= HVAC::VerySmallMassFlow) {
4546 57733 : this->MixEnthalpy = this->RetEnthalpy;
4547 57733 : this->MixHumRat = this->RetHumRat;
4548 57733 : this->MixPressure = this->RetPressure;
4549 57733 : this->MixTemp = this->RetTemp;
4550 57733 : return;
4551 : }
4552 :
4553 325412 : this->MixEnthalpy = (RecircMassFlowRate * RecircEnthalpy + this->OAMassFlowRate * this->OAEnthalpy) / this->MixMassFlowRate;
4554 325412 : this->MixHumRat = (RecircMassFlowRate * RecircHumRat + this->OAMassFlowRate * this->OAHumRat) / this->MixMassFlowRate;
4555 325412 : this->MixPressure = (RecircMassFlowRate * RecircPressure + this->OAMassFlowRate * this->OAPressure) / this->MixMassFlowRate;
4556 : // Mixed air temperature is calculated from the mixed air enthalpy and humidity ratio.
4557 325412 : this->MixTemp = Psychrometrics::PsyTdbFnHW(this->MixEnthalpy, this->MixHumRat);
4558 :
4559 : // Check for saturation temperature > dry-bulb temperature and modify temperature at constant enthalpy
4560 325412 : Real64 T_sat = Psychrometrics::PsyTsatFnHPb(state, this->MixEnthalpy, this->MixPressure);
4561 325412 : if (this->MixTemp < T_sat) {
4562 13241 : this->MixTemp = T_sat;
4563 13241 : this->MixHumRat = Psychrometrics::PsyWFnTdbH(state, T_sat, this->MixEnthalpy);
4564 : }
4565 : }
4566 :
4567 : // End of Calculation/Simulation Section of the Module
4568 : //******************************************************************************
4569 :
4570 : // Beginning Sizing Section of the Module
4571 : //******************************************************************************
4572 :
4573 29 : void OAControllerProps::SizeOAController(EnergyPlusData &state)
4574 : {
4575 :
4576 : // SUBROUTINE INFORMATION:
4577 : // AUTHOR Fred Buhl
4578 : // DATE WRITTEN September 2001
4579 :
4580 : // PURPOSE OF THIS SUBROUTINE:
4581 : // This subroutine is for sizing OAController Components for which flow rates have not been
4582 : // specified in the input.
4583 :
4584 : // METHODOLOGY EMPLOYED:
4585 : // Obtains flow rates from the zone or system sizing arrays.
4586 :
4587 : // SUBROUTINE PARAMETER DEFINITIONS:
4588 : static std::string_view const &CurrentModuleObject(CurrentModuleObjects[static_cast<int>(CMO::OAController)]);
4589 :
4590 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
4591 29 : bool ErrorsFound = false;
4592 29 : if (this->MaxOA == AutoSize) {
4593 :
4594 19 : if (state.dataSize->CurSysNum > 0) {
4595 :
4596 18 : switch (this->ControllerType) {
4597 18 : case MixedAirControllerType::ControllerOutsideAir: {
4598 18 : CheckSysSizing(state, CurrentModuleObject, this->Name);
4599 18 : switch (state.dataSize->CurDuctType) {
4600 0 : case HVAC::AirDuctType::Cooling: {
4601 0 : this->MaxOA = state.dataSize->FinalSysSizing(state.dataSize->CurSysNum).DesCoolVolFlow;
4602 0 : } break;
4603 0 : case HVAC::AirDuctType::Heating: {
4604 0 : this->MaxOA = state.dataSize->FinalSysSizing(state.dataSize->CurSysNum).DesHeatVolFlow;
4605 0 : } break;
4606 18 : case HVAC::AirDuctType::Main:
4607 : case HVAC::AirDuctType::Other:
4608 : default: {
4609 18 : this->MaxOA = state.dataSize->FinalSysSizing(state.dataSize->CurSysNum).DesMainVolFlow;
4610 18 : } break;
4611 : }
4612 18 : } break;
4613 0 : case MixedAirControllerType::ControllerStandAloneERV: {
4614 0 : } break;
4615 0 : default:
4616 0 : break;
4617 : }
4618 :
4619 1 : } else if (state.dataSize->CurZoneEqNum > 0) {
4620 :
4621 0 : switch (this->ControllerType) {
4622 0 : case MixedAirControllerType::ControllerOutsideAir: {
4623 0 : CheckZoneSizing(state, CurrentModuleObject, this->Name);
4624 0 : this->MaxOA = max(state.dataSize->FinalZoneSizing(state.dataSize->CurZoneEqNum).DesCoolVolFlow,
4625 0 : state.dataSize->FinalZoneSizing(state.dataSize->CurZoneEqNum).DesHeatVolFlow);
4626 0 : } break;
4627 0 : case MixedAirControllerType::ControllerStandAloneERV: {
4628 0 : } break;
4629 0 : default:
4630 0 : break;
4631 : }
4632 : }
4633 :
4634 19 : if (this->MaxOA < HVAC::SmallAirVolFlow) {
4635 1 : this->MaxOA = 0.0;
4636 : }
4637 :
4638 19 : BaseSizer::reportSizerOutput(state, CurrentModuleObject, this->Name, "Maximum Outdoor Air Flow Rate [m3/s]", this->MaxOA);
4639 : }
4640 :
4641 29 : if (this->MinOA == AutoSize) {
4642 :
4643 10 : if (state.dataSize->CurSysNum > 0) {
4644 :
4645 9 : CheckSysSizing(state, CurrentModuleObject, this->Name);
4646 9 : if (state.dataSize->FinalSysSizing(state.dataSize->CurSysNum).DesOutAirVolFlow >= HVAC::SmallAirVolFlow) {
4647 6 : this->MinOA = min(state.dataSize->FinalSysSizing(state.dataSize->CurSysNum).DesOutAirVolFlow, this->MaxOA);
4648 : } else {
4649 3 : this->MinOA = 0.0;
4650 : }
4651 : }
4652 :
4653 10 : BaseSizer::reportSizerOutput(state, CurrentModuleObject, this->Name, "Minimum Outdoor Air Flow Rate [m3/s]", this->MinOA);
4654 :
4655 10 : if (this->HumidistatZoneNum > 0 && this->FixedMin) {
4656 0 : if (this->MaxOA > 0.0) {
4657 0 : Real64 OAFlowRatio = this->MinOA / this->MaxOA;
4658 0 : if (this->HighRHOAFlowRatio < OAFlowRatio) {
4659 0 : ShowWarningError(state, format("{} \"{}\"", CurrentModuleObject, this->Name));
4660 0 : ShowContinueError(state, "... A fixed minimum outdoor air flow rate and high humidity control have been specified.");
4661 0 : ShowContinueError(state,
4662 : "... The High Humidity Outdoor Air Flow Ratio is less than the ratio of the outdoor air controllers "
4663 : "minimum to maximum outside air flow rate.");
4664 0 : ShowContinueError(state, format("... Controller minimum flow rate = {:.4T} m3/s.", this->MinOA));
4665 0 : ShowContinueError(state, format("... Controller maximum flow rate = {:.4T} m3/s.", this->MaxOA));
4666 0 : ShowContinueError(state, format("... Controller minimum to maximum flow ratio = {:.4T}.", OAFlowRatio));
4667 0 : ShowContinueError(state, format("... High humidity control flow ratio = {:.4T}.", this->HighRHOAFlowRatio));
4668 : }
4669 : }
4670 : }
4671 : }
4672 : // If there is an outside air system, loop over components in the OA system; pass the design air flow rate
4673 : // to the coil components that don't have design air flow as an input.
4674 29 : if (state.dataSize->CurOASysNum > 0) {
4675 56 : for (int CompNum = 1; CompNum <= state.dataAirLoop->OutsideAirSys(state.dataSize->CurOASysNum).NumComponents; ++CompNum) {
4676 31 : std::string const &CompType = state.dataAirLoop->OutsideAirSys(state.dataSize->CurOASysNum).ComponentType(CompNum);
4677 31 : std::string const &CompName = state.dataAirLoop->OutsideAirSys(state.dataSize->CurOASysNum).ComponentName(CompNum);
4678 60 : if (Util::SameString(CompType, "COIL:COOLING:WATER:DETAILEDGEOMETRY") || Util::SameString(CompType, "COIL:HEATING:WATER") ||
4679 60 : Util::SameString(CompType, "COILSYSTEM:COOLING:WATER:HEATEXCHANGERASSISTED")) {
4680 2 : std::string CoilName;
4681 2 : std::string CoilType;
4682 :
4683 2 : if (Util::SameString(CompType, "COILSYSTEM:COOLING:WATER:HEATEXCHANGERASSISTED")) {
4684 0 : CoilName = HVACHXAssistedCoolingCoil::GetHXDXCoilName(state, CompType, CompName, ErrorsFound);
4685 0 : CoilType = HVACHXAssistedCoolingCoil::GetHXCoilType(state, CompType, CompName, ErrorsFound);
4686 : } else {
4687 2 : CoilName = CompName;
4688 2 : CoilType = CompType;
4689 : }
4690 2 : WaterCoils::SetCoilDesFlow(state, CoilType, CoilName, this->MinOA, ErrorsFound);
4691 2 : }
4692 : } // End of component loop
4693 : }
4694 29 : if (ErrorsFound) {
4695 0 : ShowFatalError(state, "Preceding sizing errors cause program termination");
4696 : }
4697 29 : }
4698 :
4699 : // End of Sizing Section of the Module
4700 : //******************************************************************************
4701 :
4702 : // Beginning Update/Reporting Section of the Module
4703 : //******************************************************************************
4704 :
4705 78419 : void OAControllerProps::UpdateOAController(EnergyPlusData &state)
4706 : {
4707 :
4708 : // SUBROUTINE INFORMATION:
4709 : // AUTHOR Fred Buhl
4710 : // DATE WRITTEN Oct 1998
4711 : // MODIFIED Shirey/Raustad FSEC, June 2003
4712 :
4713 : // PURPOSE OF THIS SUBROUTINE
4714 : // Move the results of CalcOAController to the affected nodes
4715 :
4716 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
4717 78419 : int OutAirNodeNum = this->OANode;
4718 78419 : int InletAirNodeNum = this->InletNode;
4719 78419 : int RelAirNodeNum = this->RelNode;
4720 78419 : int RetAirNodeNum = this->RetNode;
4721 :
4722 78419 : if (this->ControllerType == MixedAirControllerType::ControllerOutsideAir) {
4723 : // The outside air controller sets the outside air flow rate and the relief air flow rate
4724 78419 : if (!state.dataGlobal->WarmupFlag && !state.dataGlobal->DoingSizing && (this->ManageDemand) &&
4725 0 : (this->OAMassFlow > this->DemandLimitFlowRate)) {
4726 0 : state.dataLoopNodes->Node(OutAirNodeNum).MassFlowRate = this->DemandLimitFlowRate;
4727 0 : state.dataLoopNodes->Node(InletAirNodeNum).MassFlowRate = this->DemandLimitFlowRate;
4728 0 : state.dataLoopNodes->Node(OutAirNodeNum).MassFlowRateMaxAvail = this->DemandLimitFlowRate;
4729 : } else {
4730 78419 : state.dataLoopNodes->Node(OutAirNodeNum).MassFlowRate = this->OAMassFlow;
4731 78419 : state.dataLoopNodes->Node(InletAirNodeNum).MassFlowRate = this->OAMassFlow;
4732 78419 : state.dataLoopNodes->Node(OutAirNodeNum).MassFlowRateMaxAvail = this->OAMassFlow;
4733 : }
4734 78419 : state.dataLoopNodes->Node(RelAirNodeNum).MassFlowRate = this->RelMassFlow;
4735 : } else {
4736 : // The ERV controller sets the supply and secondary inlet node information for the Stand Alone ERV
4737 : // Currently, the Stand Alone ERV only has constant air flows (supply and exhaust), and these are
4738 : // already set in HVACStandAloneERV.cc (subroutine init). Therefore, these flow assignments below are
4739 : // currently redundant but may be useful in the future as mass flow rates can vary based on the controller signal.
4740 0 : if (!state.dataGlobal->WarmupFlag && !state.dataGlobal->DoingSizing && (this->ManageDemand) &&
4741 0 : (this->OAMassFlow > this->DemandLimitFlowRate)) {
4742 0 : state.dataLoopNodes->Node(OutAirNodeNum).MassFlowRate = this->DemandLimitFlowRate;
4743 0 : state.dataLoopNodes->Node(OutAirNodeNum).MassFlowRateMaxAvail = this->DemandLimitFlowRate;
4744 : } else {
4745 0 : state.dataLoopNodes->Node(OutAirNodeNum).MassFlowRate = this->OAMassFlow;
4746 0 : state.dataLoopNodes->Node(OutAirNodeNum).MassFlowRateMaxAvail = this->OAMassFlow;
4747 : }
4748 0 : state.dataLoopNodes->Node(RetAirNodeNum).MassFlowRate = state.dataLoopNodes->Node(this->RetNode).MassFlowRate;
4749 0 : state.dataLoopNodes->Node(RetAirNodeNum).MassFlowRateMaxAvail = state.dataLoopNodes->Node(this->RetNode).MassFlowRate;
4750 : }
4751 78419 : }
4752 :
4753 383145 : void OAMixerProps::UpdateOAMixer(EnergyPlusData &state) const
4754 : {
4755 :
4756 : // SUBROUTINE INFORMATION:
4757 : // AUTHOR Fred Buhl
4758 : // DATE WRITTEN Oct 1998
4759 :
4760 : // PURPOSE OF THIS SUBROUTINE
4761 : // Move the results of CalcOAMixer to the affected nodes
4762 :
4763 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
4764 383145 : int MixNode = this->MixNode;
4765 383145 : int RelNode = this->RelNode;
4766 383145 : int RetNode = this->RetNode;
4767 : // Move mixed air data to the mixed air node
4768 383145 : state.dataLoopNodes->Node(MixNode).MassFlowRate = this->MixMassFlowRate;
4769 383145 : state.dataLoopNodes->Node(MixNode).Temp = this->MixTemp;
4770 383145 : state.dataLoopNodes->Node(MixNode).HumRat = this->MixHumRat;
4771 383145 : state.dataLoopNodes->Node(MixNode).Enthalpy = this->MixEnthalpy;
4772 383145 : state.dataLoopNodes->Node(MixNode).Press = this->MixPressure;
4773 383145 : state.dataLoopNodes->Node(MixNode).MassFlowRateMaxAvail = this->MixMassFlowRate;
4774 : // Move the relief air data to the relief air node
4775 383145 : state.dataLoopNodes->Node(RelNode).MassFlowRate = this->RelMassFlowRate;
4776 383145 : state.dataLoopNodes->Node(RelNode).Temp = this->RelTemp;
4777 383145 : state.dataLoopNodes->Node(RelNode).HumRat = this->RelHumRat;
4778 383145 : state.dataLoopNodes->Node(RelNode).Enthalpy = this->RelEnthalpy;
4779 383145 : state.dataLoopNodes->Node(RelNode).Press = this->RelPressure;
4780 383145 : state.dataLoopNodes->Node(RelNode).MassFlowRateMaxAvail = this->RelMassFlowRate;
4781 :
4782 383145 : if (state.dataContaminantBalance->Contaminant.CO2Simulation) {
4783 0 : state.dataLoopNodes->Node(RelNode).CO2 = state.dataLoopNodes->Node(RetNode).CO2;
4784 0 : if (this->MixMassFlowRate <= HVAC::VerySmallMassFlow) {
4785 0 : state.dataLoopNodes->Node(MixNode).CO2 = state.dataLoopNodes->Node(RetNode).CO2;
4786 : } else {
4787 0 : state.dataLoopNodes->Node(MixNode).CO2 =
4788 0 : ((state.dataLoopNodes->Node(RetNode).MassFlowRate - state.dataLoopNodes->Node(RelNode).MassFlowRate) *
4789 0 : state.dataLoopNodes->Node(RetNode).CO2 +
4790 0 : this->OAMassFlowRate * state.dataContaminantBalance->OutdoorCO2) /
4791 0 : this->MixMassFlowRate;
4792 : }
4793 : }
4794 :
4795 383145 : if (state.dataContaminantBalance->Contaminant.GenericContamSimulation) {
4796 0 : state.dataLoopNodes->Node(RelNode).GenContam = state.dataLoopNodes->Node(RetNode).GenContam;
4797 0 : if (this->MixMassFlowRate <= HVAC::VerySmallMassFlow) {
4798 0 : state.dataLoopNodes->Node(MixNode).GenContam = state.dataLoopNodes->Node(RetNode).GenContam;
4799 : } else {
4800 0 : state.dataLoopNodes->Node(MixNode).GenContam =
4801 0 : ((state.dataLoopNodes->Node(RetNode).MassFlowRate - state.dataLoopNodes->Node(RelNode).MassFlowRate) *
4802 0 : state.dataLoopNodes->Node(RetNode).GenContam +
4803 0 : this->OAMassFlowRate * state.dataContaminantBalance->OutdoorGC) /
4804 0 : this->MixMassFlowRate;
4805 : }
4806 : }
4807 383145 : }
4808 :
4809 : // End of Sizing Section of the Module
4810 : //******************************************************************************
4811 :
4812 : // Beginning Utility Section of the Module
4813 : //******************************************************************************
4814 :
4815 81 : Array1D_int GetOAMixerNodeNumbers(EnergyPlusData &state,
4816 : std::string const &OAMixerName, // must match OA mixer names for the OA mixer type
4817 : bool &ErrorsFound // set to true if problem
4818 : )
4819 : {
4820 :
4821 : // FUNCTION INFORMATION:
4822 : // AUTHOR Richard Raustad
4823 : // DATE WRITTEN June 2006
4824 :
4825 : // PURPOSE OF THIS FUNCTION:
4826 : // This function looks up the given OA mixer and returns the node numbers. If
4827 : // incorrect OA mixer name is given, ErrorsFound is returned as true
4828 : // as zero.
4829 :
4830 : // Return value
4831 81 : Array1D_int OANodeNumbers(4); // return OA mixer nodes
4832 :
4833 : // Obtains and Allocates OA mixer related parameters from input file
4834 81 : if (state.dataMixedAir->GetOAMixerInputFlag) { // First time subroutine has been entered
4835 38 : GetOAMixerInputs(state);
4836 38 : state.dataMixedAir->GetOAMixerInputFlag = false;
4837 : }
4838 :
4839 81 : int WhichOAMixer = Util::FindItemInList(OAMixerName, state.dataMixedAir->OAMixer);
4840 81 : if (WhichOAMixer != 0) {
4841 81 : OANodeNumbers(1) = state.dataMixedAir->OAMixer(WhichOAMixer).InletNode;
4842 81 : OANodeNumbers(2) = state.dataMixedAir->OAMixer(WhichOAMixer).RelNode;
4843 81 : OANodeNumbers(3) = state.dataMixedAir->OAMixer(WhichOAMixer).RetNode;
4844 81 : OANodeNumbers(4) = state.dataMixedAir->OAMixer(WhichOAMixer).MixNode;
4845 : }
4846 :
4847 81 : if (WhichOAMixer == 0) {
4848 0 : ShowSevereError(state, format("GetOAMixerNodeNumbers: Could not find OA Mixer = \"{}\"", OAMixerName));
4849 0 : ErrorsFound = true;
4850 0 : OANodeNumbers = 0;
4851 : }
4852 :
4853 81 : return OANodeNumbers;
4854 0 : }
4855 :
4856 29 : int GetNumOAMixers(EnergyPlusData &state)
4857 : {
4858 :
4859 : // FUNCTION INFORMATION:
4860 : // AUTHOR Linda Lawrie
4861 : // DATE WRITTEN October 2006
4862 :
4863 : // PURPOSE OF THIS FUNCTION:
4864 : // After making sure get input is done, the number of OA mixers is returned.
4865 :
4866 29 : if (state.dataMixedAir->GetOAMixerInputFlag) { // First time subroutine has been entered
4867 5 : GetOAMixerInputs(state);
4868 5 : state.dataMixedAir->GetOAMixerInputFlag = false;
4869 : }
4870 :
4871 29 : return state.dataMixedAir->NumOAMixers;
4872 : }
4873 :
4874 2 : int GetNumOAControllers(EnergyPlusData &state)
4875 : {
4876 :
4877 : // FUNCTION INFORMATION:
4878 : // AUTHOR Linda Lawrie
4879 : // DATE WRITTEN October 2006
4880 :
4881 : // PURPOSE OF THIS FUNCTION:
4882 : // After making sure get input is done, the number of OA Controllers is returned.
4883 :
4884 2 : if (state.dataMixedAir->AllocateOAControllersFlag) {
4885 : // Make sure OAControllers are allocated
4886 0 : AllocateOAControllers(state);
4887 : }
4888 :
4889 2 : return state.dataMixedAir->NumOAControllers;
4890 : }
4891 :
4892 2 : int GetOAMixerReliefNodeNumber(EnergyPlusData &state, int const OAMixerNum) // Which Mixer
4893 : {
4894 :
4895 : // FUNCTION INFORMATION:
4896 : // AUTHOR Linda Lawrie
4897 : // DATE WRITTEN October 2006
4898 :
4899 : // PURPOSE OF THIS FUNCTION:
4900 : // After making sure get input is done, the relief node number of indicated mixer is returned.
4901 :
4902 2 : if (state.dataMixedAir->GetOAMixerInputFlag) { // First time subroutine has been entered
4903 0 : GetOAMixerInputs(state);
4904 0 : state.dataMixedAir->GetOAMixerInputFlag = false;
4905 : }
4906 :
4907 2 : if (OAMixerNum > state.dataMixedAir->NumOAMixers) {
4908 0 : ShowFatalError(state,
4909 0 : format("GetOAMixerReliefNodeNumber: Requested Mixer #={}, which is > number of OA Mixers={}",
4910 : OAMixerNum,
4911 0 : state.dataMixedAir->NumOAMixers));
4912 : }
4913 :
4914 2 : return state.dataMixedAir->OAMixer(OAMixerNum).RelNode;
4915 : }
4916 :
4917 48 : int GetOASysControllerListIndex(EnergyPlusData &state, int const OASysNumber) // OA Sys Number
4918 : {
4919 :
4920 : // FUNCTION INFORMATION:
4921 : // AUTHOR Fred Buhl
4922 : // DATE WRITTEN April 2007
4923 :
4924 : // PURPOSE OF THIS FUNCTION:
4925 : // After making sure get input is done, the Controller List index of the indicated OA System is returned.
4926 :
4927 48 : if (state.dataMixedAir->GetOASysInputFlag) {
4928 0 : GetOutsideAirSysInputs(state);
4929 0 : state.dataMixedAir->GetOASysInputFlag = false;
4930 : }
4931 :
4932 48 : return state.dataAirLoop->OutsideAirSys(OASysNumber).ControllerListNum;
4933 : }
4934 :
4935 48 : int GetOASysNumSimpControllers(EnergyPlusData &state, int const OASysNumber) // OA Sys Number
4936 : {
4937 :
4938 : // FUNCTION INFORMATION:
4939 : // AUTHOR Fred Buhl
4940 : // DATE WRITTEN April 2007
4941 :
4942 : // PURPOSE OF THIS FUNCTION:
4943 : // After making sure get input is done, the number of Controller:Simple objects in the OA System is returned.
4944 :
4945 48 : if (state.dataMixedAir->GetOASysInputFlag) {
4946 0 : GetOutsideAirSysInputs(state);
4947 0 : state.dataMixedAir->GetOASysInputFlag = false;
4948 : }
4949 :
4950 48 : return state.dataAirLoop->OutsideAirSys(OASysNumber).NumSimpleControllers;
4951 : }
4952 :
4953 48 : int GetOASysNumHeatingCoils(EnergyPlusData &state, int const OASysNumber) // OA Sys Number
4954 : {
4955 :
4956 : // FUNCTION INFORMATION:
4957 : // AUTHOR Fred Buhl
4958 : // DATE WRITTEN May 2007
4959 :
4960 : // PURPOSE OF THIS FUNCTION:
4961 : // After making sure get input is done, the number of heating coils in the OA System is returned.
4962 :
4963 : // FUNCTION LOCAL VARIABLE DECLARATIONS:
4964 48 : bool Sim(false);
4965 48 : bool FirstHVACIteration(false);
4966 48 : bool OAHeatingCoil(false);
4967 48 : bool OACoolingCoil(false);
4968 48 : int AirLoopNum(0);
4969 48 : bool OAHX(false);
4970 :
4971 48 : if (state.dataMixedAir->GetOASysInputFlag) {
4972 0 : GetOutsideAirSysInputs(state);
4973 0 : state.dataMixedAir->GetOASysInputFlag = false;
4974 : }
4975 :
4976 48 : int NumHeatingCoils = 0;
4977 105 : for (int CompNum = 1; CompNum <= state.dataAirLoop->OutsideAirSys(OASysNumber).NumComponents; ++CompNum) {
4978 57 : std::string const &CompType = state.dataAirLoop->OutsideAirSys(OASysNumber).ComponentType(CompNum);
4979 57 : std::string const &CompName = state.dataAirLoop->OutsideAirSys(OASysNumber).ComponentName(CompNum);
4980 228 : SimOAComponent(state,
4981 : CompType,
4982 : CompName,
4983 57 : state.dataAirLoop->OutsideAirSys(OASysNumber).ComponentTypeEnum(CompNum),
4984 : FirstHVACIteration,
4985 57 : state.dataAirLoop->OutsideAirSys(OASysNumber).ComponentIndex(CompNum),
4986 : AirLoopNum,
4987 : Sim,
4988 : OASysNumber,
4989 : OAHeatingCoil,
4990 : OACoolingCoil,
4991 : OAHX);
4992 57 : if (OAHeatingCoil) {
4993 3 : ++NumHeatingCoils;
4994 : }
4995 : }
4996 :
4997 48 : return NumHeatingCoils;
4998 : }
4999 :
5000 48 : int GetOASysNumHXs(EnergyPlusData &state, int const OASysNumber)
5001 : {
5002 :
5003 : // FUNCTION INFORMATION:
5004 : // AUTHOR Fred Buhl, Rongpeng Zhang
5005 : // DATE WRITTEN Oct. 2015
5006 :
5007 : // PURPOSE OF THIS FUNCTION:
5008 : // After making sure get input is done, the number of heat recovery exchangers in the OA System is returned.
5009 :
5010 48 : if (state.dataMixedAir->GetOASysInputFlag) {
5011 0 : GetOutsideAirSysInputs(state);
5012 0 : state.dataMixedAir->GetOASysInputFlag = false;
5013 : }
5014 :
5015 48 : int NumHX = 0;
5016 :
5017 48 : auto const &componentType_Num = state.dataAirLoop->OutsideAirSys(OASysNumber).ComponentTypeEnum;
5018 105 : for (int CompNum = 1; CompNum <= state.dataAirLoop->OutsideAirSys(OASysNumber).NumComponents; ++CompNum) {
5019 57 : SimAirServingZones::CompType const componentTypeNum = componentType_Num(CompNum);
5020 57 : if (SimAirServingZones::CompType::HeatXchngr == componentTypeNum || SimAirServingZones::CompType::Desiccant == componentTypeNum) {
5021 3 : ++NumHX;
5022 : }
5023 : }
5024 :
5025 48 : return NumHX;
5026 : }
5027 :
5028 48 : int GetOASysNumCoolingCoils(EnergyPlusData &state, int const OASysNumber) // OA Sys Number
5029 : {
5030 :
5031 : // FUNCTION INFORMATION:
5032 : // AUTHOR Fred Buhl
5033 : // DATE WRITTEN May 2007
5034 :
5035 : // PURPOSE OF THIS FUNCTION:
5036 : // After making sure get input is done, the number of cooling coils in the OA System is returned.
5037 :
5038 : // FUNCTION LOCAL VARIABLE DECLARATIONS:
5039 48 : bool Sim(false);
5040 48 : bool FirstHVACIteration(false);
5041 48 : bool OAHeatingCoil(false);
5042 48 : bool OACoolingCoil(false);
5043 48 : int AirLoopNum(0);
5044 48 : bool OAHX(false);
5045 :
5046 48 : if (state.dataMixedAir->GetOASysInputFlag) {
5047 0 : GetOutsideAirSysInputs(state);
5048 0 : state.dataMixedAir->GetOASysInputFlag = false;
5049 : }
5050 :
5051 48 : int NumCoolingCoils = 0;
5052 105 : for (int CompNum = 1; CompNum <= state.dataAirLoop->OutsideAirSys(OASysNumber).NumComponents; ++CompNum) {
5053 57 : std::string const &CompType = state.dataAirLoop->OutsideAirSys(OASysNumber).ComponentType(CompNum);
5054 57 : std::string const &CompName = state.dataAirLoop->OutsideAirSys(OASysNumber).ComponentName(CompNum);
5055 228 : SimOAComponent(state,
5056 : CompType,
5057 : CompName,
5058 57 : state.dataAirLoop->OutsideAirSys(OASysNumber).ComponentTypeEnum(CompNum),
5059 : FirstHVACIteration,
5060 57 : state.dataAirLoop->OutsideAirSys(OASysNumber).ComponentIndex(CompNum),
5061 : AirLoopNum,
5062 : Sim,
5063 : OASysNumber,
5064 : OAHeatingCoil,
5065 : OACoolingCoil,
5066 : OAHX);
5067 57 : if (OACoolingCoil) {
5068 3 : ++NumCoolingCoils;
5069 : }
5070 : }
5071 :
5072 48 : return NumCoolingCoils;
5073 : }
5074 :
5075 48 : int GetOASystemNumber(EnergyPlusData &state, std::string const &OASysName) // OA Sys Name
5076 : {
5077 :
5078 : // FUNCTION INFORMATION:
5079 : // AUTHOR Linda Lawrie
5080 : // DATE WRITTEN October 2006
5081 :
5082 : // PURPOSE OF THIS FUNCTION:
5083 : // After making sure get input is done, the OA System number of indicated OA System is returned.
5084 :
5085 48 : if (state.dataMixedAir->GetOASysInputFlag) {
5086 35 : GetOutsideAirSysInputs(state);
5087 35 : state.dataMixedAir->GetOASysInputFlag = false;
5088 : }
5089 :
5090 48 : return Util::FindItemInList(OASysName, state.dataAirLoop->OutsideAirSys);
5091 : }
5092 :
5093 48 : int FindOAMixerMatchForOASystem(EnergyPlusData &state, int const OASysNumber) // Which OA System
5094 : {
5095 :
5096 : // FUNCTION INFORMATION:
5097 : // AUTHOR Linda Lawrie
5098 : // DATE WRITTEN October 2006
5099 :
5100 : // PURPOSE OF THIS FUNCTION:
5101 : // After making sure get input is done, the matched mixer number is found.
5102 : // Note -- only the first is looked at for an Outside Air System.
5103 :
5104 48 : if (state.dataMixedAir->GetOAMixerInputFlag) {
5105 36 : GetOAMixerInputs(state);
5106 36 : state.dataMixedAir->GetOAMixerInputFlag = false;
5107 : }
5108 :
5109 48 : int OAMixerNumber = 0;
5110 48 : if (OASysNumber > 0 && OASysNumber <= state.dataAirLoop->NumOASystems) {
5111 57 : for (int OACompNum = 1; OACompNum <= state.dataAirLoop->OutsideAirSys(OASysNumber).NumComponents; ++OACompNum) {
5112 57 : if (Util::SameString(state.dataAirLoop->OutsideAirSys(OASysNumber).ComponentType(OACompNum), "OUTDOORAIR:MIXER")) {
5113 : OAMixerNumber =
5114 48 : Util::FindItemInList(state.dataAirLoop->OutsideAirSys(OASysNumber).ComponentName(OACompNum), state.dataMixedAir->OAMixer);
5115 48 : break;
5116 : }
5117 : }
5118 : }
5119 :
5120 48 : return OAMixerNumber;
5121 : }
5122 :
5123 20 : int GetOAMixerIndex(EnergyPlusData &state, std::string const &OAMixerName) // Which Mixer
5124 : {
5125 :
5126 : // FUNCTION INFORMATION:
5127 : // AUTHOR Linda Lawrie
5128 : // DATE WRITTEN December 2010
5129 :
5130 : // PURPOSE OF THIS FUNCTION:
5131 : // After making sure get input is done, the mixer index of indicated mixer is returned.
5132 :
5133 20 : if (state.dataMixedAir->GetOAMixerInputFlag) {
5134 6 : GetOAMixerInputs(state);
5135 6 : state.dataMixedAir->GetOAMixerInputFlag = false;
5136 : }
5137 :
5138 20 : int OAMixerIndex = Util::FindItem(OAMixerName, state.dataMixedAir->OAMixer);
5139 :
5140 20 : if (OAMixerIndex == 0) {
5141 0 : ShowSevereError(state, format("GetOAMixerIndex: Could not find OutdoorAir:Mixer, Name=\"{}\"", OAMixerName));
5142 : }
5143 :
5144 20 : return OAMixerIndex;
5145 : }
5146 :
5147 48 : int GetOAMixerInletNodeNumber(EnergyPlusData &state, int const OAMixerNumber) // Which Mixer
5148 : {
5149 :
5150 : // FUNCTION INFORMATION:
5151 : // AUTHOR Linda Lawrie
5152 : // DATE WRITTEN October 2006
5153 :
5154 : // PURPOSE OF THIS FUNCTION:
5155 : // After making sure get input is done, the mixer inlet node number of indicated mixer is returned.
5156 :
5157 48 : if (state.dataMixedAir->GetOAMixerInputFlag) {
5158 0 : GetOAMixerInputs(state);
5159 0 : state.dataMixedAir->GetOAMixerInputFlag = false;
5160 : }
5161 :
5162 48 : int OAMixerInletNodeNumber = 0;
5163 48 : if (OAMixerNumber > 0 && OAMixerNumber <= state.dataMixedAir->NumOAMixers) {
5164 48 : OAMixerInletNodeNumber = state.dataMixedAir->OAMixer(OAMixerNumber).InletNode;
5165 : }
5166 :
5167 48 : return OAMixerInletNodeNumber;
5168 : }
5169 :
5170 1584 : int GetOAMixerReturnNodeNumber(EnergyPlusData &state, int const OAMixerNumber) // Which Mixer
5171 : {
5172 :
5173 : // FUNCTION INFORMATION:
5174 : // AUTHOR Brent Griffith
5175 : // DATE WRITTEN December 2006
5176 :
5177 : // PURPOSE OF THIS FUNCTION:
5178 : // After making sure get input is done, the mixer return node number of indicated mixer is returned.
5179 :
5180 : // METHODOLOGY EMPLOYED:
5181 : // followed Linda Lawrie's GetOAMixerInletNodeNumber
5182 :
5183 1584 : if (state.dataMixedAir->GetOAMixerInputFlag) {
5184 0 : GetOAMixerInputs(state);
5185 0 : state.dataMixedAir->GetOAMixerInputFlag = false;
5186 : }
5187 :
5188 1584 : int OAMixerReturnNodeNumber = 0;
5189 1584 : if (OAMixerNumber > 0 && OAMixerNumber <= state.dataMixedAir->NumOAMixers) {
5190 1584 : OAMixerReturnNodeNumber = state.dataMixedAir->OAMixer(OAMixerNumber).RetNode;
5191 : }
5192 :
5193 1584 : return OAMixerReturnNodeNumber;
5194 : }
5195 :
5196 1584 : int GetOAMixerMixedNodeNumber(EnergyPlusData &state, int const OAMixerNumber) // Which Mixer
5197 : {
5198 :
5199 : // FUNCTION INFORMATION:
5200 : // AUTHOR Brent Griffith
5201 : // DATE WRITTEN December 2006
5202 :
5203 : // PURPOSE OF THIS FUNCTION:
5204 : // After making sure get input is done, the mixer mixed air node number of indicated mixer is returned.
5205 :
5206 1584 : if (state.dataMixedAir->GetOAMixerInputFlag) {
5207 0 : GetOAMixerInputs(state);
5208 0 : state.dataMixedAir->GetOAMixerInputFlag = false;
5209 : }
5210 :
5211 1584 : int OAMixerMixedNodeNumber = 0;
5212 1584 : if (OAMixerNumber > 0 && OAMixerNumber <= state.dataMixedAir->NumOAMixers) {
5213 1584 : OAMixerMixedNodeNumber = state.dataMixedAir->OAMixer(OAMixerNumber).MixNode;
5214 : }
5215 :
5216 1584 : return OAMixerMixedNodeNumber;
5217 : }
5218 :
5219 30 : bool CheckForControllerWaterCoil(EnergyPlusData &state,
5220 : DataAirLoop::ControllerKind ControllerType, // should be passed in as UPPERCASE
5221 : std::string const &ControllerName // should be passed in as UPPERCASE
5222 : )
5223 : {
5224 :
5225 : // FUNCTION INFORMATION:
5226 : // AUTHOR Linda Lawrie
5227 : // DATE WRITTEN May 2009
5228 :
5229 : // PURPOSE OF THIS FUNCTION:
5230 : // This routine checks the controller list for existence of the reference coil.
5231 :
5232 30 : if (state.dataMixedAir->GetOASysInputFlag) {
5233 11 : GetOutsideAirSysInputs(state);
5234 11 : state.dataMixedAir->GetOASysInputFlag = false;
5235 : }
5236 :
5237 30 : int OnControllerList = false;
5238 :
5239 72 : for (int Num = 1; Num <= state.dataMixedAir->NumControllerLists; ++Num) {
5240 64 : for (int CompNum = 1; CompNum <= state.dataMixedAir->ControllerLists(Num).NumControllers; ++CompNum) {
5241 :
5242 52 : if (state.dataMixedAir->ControllerLists(Num).ControllerType(CompNum) != ControllerType) continue;
5243 36 : if (!Util::SameString(state.dataMixedAir->ControllerLists(Num).ControllerName(CompNum), ControllerName)) continue;
5244 30 : OnControllerList = true;
5245 30 : break;
5246 : }
5247 : }
5248 :
5249 30 : return OnControllerList;
5250 : }
5251 :
5252 73 : void CheckControllerLists(EnergyPlusData &state, bool &ErrFound)
5253 : {
5254 :
5255 : // SUBROUTINE INFORMATION:
5256 : // AUTHOR Linda Lawrie
5257 : // DATE WRITTEN May 2009
5258 :
5259 : // PURPOSE OF THIS SUBROUTINE:
5260 : // This routine checks for a "dangling" controller list (AirLoopHVAC:ControllerList).
5261 : // It must be either found on a AirLoopHVAC or AirLoopHVAC:OutdoorAirSystem.
5262 :
5263 : // SUBROUTINE PARAMETER DEFINITIONS:
5264 201 : static std::string const CurrentModuleObject("AirLoopHVAC:ControllerList");
5265 201 : static std::string const AirLoopObject("AirLoopHVAC");
5266 :
5267 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
5268 : int NumAlphas;
5269 : int NumNumbers;
5270 : int IOStat;
5271 :
5272 73 : if (state.dataMixedAir->GetOASysInputFlag) {
5273 51 : GetOutsideAirSysInputs(state);
5274 51 : state.dataMixedAir->GetOASysInputFlag = false;
5275 : }
5276 :
5277 73 : int NumControllers = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, CurrentModuleObject);
5278 73 : int NumAirLoop = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, AirLoopObject);
5279 73 : std::string_view AirLoopName = "";
5280 :
5281 96 : for (int Item = 1; Item <= NumControllers; ++Item) {
5282 :
5283 46 : state.dataInputProcessing->inputProcessor->getObjectItem(
5284 23 : state, CurrentModuleObject, Item, state.dataIPShortCut->cAlphaArgs, NumAlphas, state.dataIPShortCut->rNumericArgs, NumNumbers, IOStat);
5285 23 : std::string const ControllerListName = state.dataIPShortCut->cAlphaArgs(1);
5286 23 : int Count = 0;
5287 :
5288 : // Check AirLoopHVAC -- brute force, get each AirLoopHVAC
5289 :
5290 46 : for (int Loop = 1; Loop <= NumAirLoop; ++Loop) {
5291 46 : state.dataInputProcessing->inputProcessor->getObjectItem(
5292 23 : state, AirLoopObject, Loop, state.dataIPShortCut->cAlphaArgs, NumAlphas, state.dataIPShortCut->rNumericArgs, NumNumbers, IOStat);
5293 23 : if (state.dataIPShortCut->cAlphaArgs(2) != ControllerListName) continue;
5294 3 : if (++Count == 1) AirLoopName = state.dataIPShortCut->cAlphaArgs(1);
5295 : }
5296 :
5297 : // Now check AirLoopHVAC and AirLoopHVAC:OutdoorAirSystem
5298 23 : int Found = 0;
5299 23 : if (state.dataAirLoop->NumOASystems > 0) {
5300 22 : Found = Util::FindItemInList(ControllerListName, state.dataAirLoop->OutsideAirSys, &OutsideAirSysProps::ControllerListName);
5301 22 : if (Found > 0) ++Count;
5302 : }
5303 :
5304 23 : if (Count == 0) {
5305 0 : ShowSevereError(state,
5306 0 : format("{}=\"{}\" is not referenced on a AirLoopHVAC or AirLoopHVAC:OutdoorAirSystem object.",
5307 : CurrentModuleObject,
5308 : ControllerListName));
5309 0 : ErrFound = true;
5310 23 : } else if (Count > 1) {
5311 0 : ShowSevereError(state,
5312 0 : format("{}=\"{}\" has too many references on AirLoopHVAC or AirLoopHVAC:OutdoorAirSystem objects.",
5313 : CurrentModuleObject,
5314 : ControllerListName));
5315 0 : if (Found > 0) {
5316 0 : ShowContinueError(state, format("...AirLoopHVAC:OutdoorAirSystem=\"{}\".", state.dataAirLoop->OutsideAirSys(Found).Name));
5317 : }
5318 0 : ShowContinueError(state, format("...also on AirLoopHVAC=\"{}\".", AirLoopName));
5319 0 : ErrFound = true;
5320 : }
5321 23 : }
5322 73 : }
5323 :
5324 0 : void CheckOAControllerName(
5325 : EnergyPlusData &state, std::string &OAControllerName, std::string const &ObjectType, std::string const &FieldName, bool &ErrorsFound)
5326 : {
5327 :
5328 : // SUBROUTINE INFORMATION:
5329 : // AUTHOR Linda Lawrie
5330 : // DATE WRITTEN October 2006
5331 :
5332 : // PURPOSE OF THIS SUBROUTINE:
5333 : // When OA Controller data is gotten from other routines, must check to make sure
5334 : // new name doesn't duplicate. (Essentially a pass through to call Verify Name)
5335 : // Currently, this is only called from HVACStandAlongERV::GetStandaloneERV()
5336 :
5337 0 : if (state.dataMixedAir->AllocateOAControllersFlag) {
5338 : // Make sure OAControllers are allocated
5339 0 : AllocateOAControllers(state);
5340 : }
5341 :
5342 0 : GlobalNames::VerifyUniqueInterObjectName(
5343 0 : state, state.dataMixedAir->OAControllerUniqueNames, OAControllerName, ObjectType, FieldName, ErrorsFound);
5344 0 : }
5345 :
5346 27416 : void OAControllerProps::Checksetpoints(EnergyPlusData &state,
5347 : Real64 const OutAirMinFrac, // Local variable used to calculate min OA fraction
5348 : Real64 &OutAirSignal, // Used to set OA mass flow rate
5349 : bool &EconomizerOperationFlag // logical used to show economizer status
5350 : )
5351 : {
5352 :
5353 : // SUBROUTINE INFORMATION:
5354 : // AUTHOR Amit bhansali
5355 : // DATE WRITTEN August 2008?
5356 :
5357 : // PURPOSE OF THIS SUBROUTINE:
5358 : // This subroutine checks the setpoints of the upper limits of temperatures, limit enthalpy
5359 : // Limit dew point, Enthalpy curve
5360 :
5361 27416 : if (this->TempLim != HVAC::BlankNumeric && this->OATemp > this->TempLim) {
5362 6869 : OutAirSignal = OutAirMinFrac;
5363 6869 : EconomizerOperationFlag = false;
5364 : }
5365 : // Outside air enthalpy limit
5366 27416 : if (this->EnthLim != HVAC::BlankNumeric && this->OAEnth > this->EnthLim) {
5367 47 : OutAirSignal = OutAirMinFrac;
5368 47 : EconomizerOperationFlag = false;
5369 : }
5370 :
5371 27416 : if (this->DPTempLim != HVAC::BlankNumeric) {
5372 2842 : Real64 OADPTemp = Psychrometrics::PsyTdpFnWPb(state, this->OAHumRat, this->OAPress);
5373 2842 : if (OADPTemp > this->DPTempLim) {
5374 0 : OutAirSignal = OutAirMinFrac;
5375 0 : EconomizerOperationFlag = false;
5376 : }
5377 : }
5378 :
5379 27416 : if (this->EnthalpyCurvePtr > 0) {
5380 0 : if (this->OAHumRat > Curve::CurveValue(state, this->EnthalpyCurvePtr, this->OATemp)) {
5381 0 : OutAirSignal = OutAirMinFrac;
5382 0 : EconomizerOperationFlag = false;
5383 : }
5384 : }
5385 27416 : }
5386 :
5387 60 : int GetNumOASystems(EnergyPlusData &state)
5388 : {
5389 :
5390 : // FUNCTION INFORMATION:
5391 : // AUTHOR Linda Lawrie
5392 : // DATE WRITTEN November 2010
5393 :
5394 : // PURPOSE OF THIS FUNCTION:
5395 : // Get Number of OA Systems, After making sure get input is done
5396 :
5397 60 : if (state.dataMixedAir->GetOASysInputFlag) {
5398 10 : GetOutsideAirSysInputs(state);
5399 10 : state.dataMixedAir->GetOASysInputFlag = false;
5400 : }
5401 :
5402 60 : return state.dataAirLoop->NumOASystems;
5403 : }
5404 :
5405 52 : int GetOACompListNumber(EnergyPlusData &state, int const OASysNum) // OA Sys Number
5406 : {
5407 :
5408 : // FUNCTION INFORMATION:
5409 : // AUTHOR Heejin Cho
5410 : // DATE WRITTEN November 2010
5411 :
5412 : // PURPOSE OF THIS FUNCTION:
5413 : // After making sure get input is done, the OA System number of indicated OA System is returned.
5414 :
5415 52 : if (state.dataMixedAir->GetOASysInputFlag) {
5416 0 : GetOutsideAirSysInputs(state);
5417 0 : state.dataMixedAir->GetOASysInputFlag = false;
5418 : }
5419 :
5420 52 : return state.dataAirLoop->OutsideAirSys(OASysNum).NumComponents;
5421 : }
5422 :
5423 4 : std::string GetOACompName(EnergyPlusData &state,
5424 : int const OASysNum, // OA Sys Number
5425 : int const InListNum // In-list Number
5426 : )
5427 : {
5428 :
5429 : // FUNCTION INFORMATION:
5430 : // AUTHOR Heejin Cho
5431 : // DATE WRITTEN November 2010
5432 :
5433 : // PURPOSE OF THIS FUNCTION:
5434 : // After making sure get input is done, the number of heating coils in the OA System is returned.
5435 :
5436 4 : if (state.dataMixedAir->GetOASysInputFlag) {
5437 0 : GetOutsideAirSysInputs(state);
5438 0 : state.dataMixedAir->GetOASysInputFlag = false;
5439 : }
5440 :
5441 4 : return state.dataAirLoop->OutsideAirSys(OASysNum).ComponentName(InListNum);
5442 : }
5443 :
5444 4 : std::string GetOACompType(EnergyPlusData &state,
5445 : int const OASysNum, // OA Sys Number
5446 : int const InListNum // In-list Number
5447 : )
5448 : {
5449 :
5450 : // FUNCTION INFORMATION:
5451 : // AUTHOR Heejin Cho
5452 : // DATE WRITTEN November 2010
5453 :
5454 : // PURPOSE OF THIS FUNCTION:
5455 : // After making sure get input is done, the number of heating coils in the OA System is returned.
5456 :
5457 4 : if (state.dataMixedAir->GetOASysInputFlag) {
5458 0 : GetOutsideAirSysInputs(state);
5459 0 : state.dataMixedAir->GetOASysInputFlag = false;
5460 : }
5461 :
5462 4 : return state.dataAirLoop->OutsideAirSys(OASysNum).ComponentType(InListNum);
5463 : }
5464 :
5465 67 : SimAirServingZones::CompType GetOACompTypeNum(EnergyPlusData &state,
5466 : int const OASysNum, // OA Sys Number
5467 : int const InListNum // In-list Number
5468 : )
5469 : {
5470 :
5471 : // FUNCTION INFORMATION:
5472 : // AUTHOR Heejin Cho
5473 : // DATE WRITTEN November 2010
5474 :
5475 : // PURPOSE OF THIS FUNCTION:
5476 : // After making sure get input is done, the number of heating coils in the OA System is returned.
5477 :
5478 67 : if (state.dataMixedAir->GetOASysInputFlag) {
5479 0 : GetOutsideAirSysInputs(state);
5480 0 : state.dataMixedAir->GetOASysInputFlag = false;
5481 : }
5482 :
5483 67 : return state.dataAirLoop->OutsideAirSys(OASysNum).ComponentTypeEnum(InListNum);
5484 : }
5485 :
5486 5 : int GetOAMixerNumber(EnergyPlusData &state, std::string const &OAMixerName // must match OA mixer names for the OA mixer type
5487 : )
5488 : {
5489 :
5490 : // FUNCTION INFORMATION:
5491 : // AUTHOR Lixing Gu
5492 : // DATE WRITTEN Feb. 2018
5493 :
5494 : // PURPOSE OF THIS FUNCTION:
5495 : // This function looks up the given OA mixer and returns the OAMixer number. If
5496 : // incorrect OA mixer name is given, ErrorsFound is returned as true
5497 :
5498 : // Obtains and Allocates OA mixer related parameters from input file
5499 5 : if (state.dataMixedAir->GetOAMixerInputFlag) { // First time subroutine has been entered
5500 2 : GetOAMixerInputs(state);
5501 2 : state.dataMixedAir->GetOAMixerInputFlag = false;
5502 : }
5503 :
5504 5 : return Util::FindItemInList(OAMixerName, state.dataMixedAir->OAMixer);
5505 : }
5506 : // End of Utility Section of the Module
5507 : //******************************************************************************
5508 :
5509 : } // namespace EnergyPlus::MixedAir
|