Line data Source code
1 : // EnergyPlus, Copyright (c) 1996-2025, The Board of Trustees of the University of Illinois,
2 : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
3 : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
4 : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
5 : // contributors. All rights reserved.
6 : //
7 : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
8 : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
9 : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
10 : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
11 : // derivative works, and perform publicly and display publicly, and to permit others to do so.
12 : //
13 : // Redistribution and use in source and binary forms, with or without modification, are permitted
14 : // provided that the following conditions are met:
15 : //
16 : // (1) Redistributions of source code must retain the above copyright notice, this list of
17 : // conditions and the following disclaimer.
18 : //
19 : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
20 : // conditions and the following disclaimer in the documentation and/or other materials
21 : // provided with the distribution.
22 : //
23 : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
24 : // the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
25 : // used to endorse or promote products derived from this software without specific prior
26 : // written permission.
27 : //
28 : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
29 : // without changes from the version obtained under this License, or (ii) Licensee makes a
30 : // reference solely to the software portion of its product, Licensee must refer to the
31 : // software as "EnergyPlus version X" software, where "X" is the version number Licensee
32 : // obtained under this License and may not use a different name for the software. Except as
33 : // specifically required in this Section (4), Licensee shall not use in a company name, a
34 : // product name, in advertising, publicity, or other promotional activities any name, trade
35 : // name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
36 : // similar designation, without the U.S. Department of Energy's prior written consent.
37 : //
38 : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
39 : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
40 : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
41 : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
43 : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44 : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
45 : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46 : // POSSIBILITY OF SUCH DAMAGE.
47 :
48 : // ObjexxFCL Headers
49 : #include <ObjexxFCL/Array.functions.hh>
50 : #include <ObjexxFCL/Fmath.hh>
51 :
52 : // EnergyPlus Headers
53 : #include <AirflowNetwork/Solver.hpp>
54 : #include <EnergyPlus/Autosizing/Base.hh>
55 : #include <EnergyPlus/BranchNodeConnections.hh>
56 : #include <EnergyPlus/Data/EnergyPlusData.hh>
57 : #include <EnergyPlus/DataContaminantBalance.hh>
58 : #include <EnergyPlus/DataEnvironment.hh>
59 : #include <EnergyPlus/DataHVACGlobals.hh>
60 : #include <EnergyPlus/DataHeatBalance.hh>
61 : #include <EnergyPlus/DataIPShortCuts.hh>
62 : #include <EnergyPlus/DataLoopNode.hh>
63 : #include <EnergyPlus/DataSizing.hh>
64 : #include <EnergyPlus/DataZoneEquipment.hh>
65 : #include <EnergyPlus/ExhaustAirSystemManager.hh>
66 : #include <EnergyPlus/Fans.hh>
67 : #include <EnergyPlus/GeneralRoutines.hh>
68 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
69 : #include <EnergyPlus/MixerComponent.hh>
70 : #include <EnergyPlus/NodeInputManager.hh>
71 : #include <EnergyPlus/Psychrometrics.hh>
72 : #include <EnergyPlus/ScheduleManager.hh>
73 : #include <EnergyPlus/UtilityRoutines.hh>
74 : #include <EnergyPlus/ZoneTempPredictorCorrector.hh>
75 :
76 : namespace EnergyPlus {
77 :
78 : namespace ExhaustAirSystemManager {
79 : // Module containing the routines dealing with the AirLoopHVAC:ExhaustSystem
80 :
81 : static constexpr std::array<std::string_view, static_cast<int>(ZoneExhaustControl::FlowControlType::Num)> flowControlTypeNamesUC = {
82 : "SCHEDULED", "FOLLOWSUPPLY"};
83 :
84 424465 : void SimExhaustAirSystem(EnergyPlusData &state, bool FirstHVACIteration)
85 : {
86 : // Obtains and Allocates Mixer related parameters from input file
87 424465 : if (state.dataExhAirSystemMrg->GetInputFlag) { // First time subroutine has been entered
88 110 : GetExhaustAirSystemInput(state);
89 110 : state.dataExhAirSystemMrg->GetInputFlag = false;
90 : }
91 :
92 424465 : for (int ExhaustAirSystemNum = 1; ExhaustAirSystemNum <= state.dataZoneEquip->NumExhaustAirSystems; ++ExhaustAirSystemNum) {
93 0 : CalcExhaustAirSystem(state, ExhaustAirSystemNum, FirstHVACIteration);
94 : }
95 :
96 : // After this, update the exhaust flows according to zone grouping:
97 424465 : UpdateZoneExhaustControl(state);
98 424465 : }
99 :
100 112 : void GetExhaustAirSystemInput(EnergyPlusData &state)
101 : {
102 112 : if (!state.dataExhAirSystemMrg->GetInputFlag) return;
103 : // state.dataExhAirSystemMrg->GetInputFlag = false;
104 :
105 : // Locals
106 112 : bool ErrorsFound = false;
107 :
108 112 : constexpr std::string_view RoutineName("GetExhaustAirSystemInput: ");
109 112 : constexpr std::string_view routineName = "GetExhaustAirSystemInput";
110 112 : std::string const cCurrentModuleObject = "AirLoopHVAC:ExhaustSystem";
111 :
112 112 : auto &ip = state.dataInputProcessing->inputProcessor;
113 112 : auto const instances = ip->epJSON.find(cCurrentModuleObject);
114 112 : if (instances != ip->epJSON.end()) {
115 2 : auto const &objectSchemaProps = ip->getObjectSchemaProps(state, cCurrentModuleObject);
116 2 : auto &instancesValue = instances.value();
117 2 : int numExhaustSystems = instancesValue.size();
118 2 : int exhSysNum = 0;
119 :
120 2 : if (numExhaustSystems > 0) {
121 2 : state.dataZoneEquip->ExhaustAirSystem.allocate(numExhaustSystems);
122 : }
123 :
124 6 : for (auto instance = instancesValue.begin(); instance != instancesValue.end(); ++instance) {
125 4 : ++exhSysNum;
126 4 : auto const &objectFields = instance.value();
127 4 : auto &thisExhSys = state.dataZoneEquip->ExhaustAirSystem(exhSysNum);
128 4 : thisExhSys.Name = Util::makeUPPER(instance.key());
129 4 : ip->markObjectAsUsed(cCurrentModuleObject, instance.key());
130 :
131 8 : std::string zoneMixerName = ip->getAlphaFieldValue(objectFields, objectSchemaProps, "zone_mixer_name");
132 4 : int zoneMixerIndex = 0;
133 4 : bool zoneMixerErrFound = false;
134 4 : MixerComponent::GetZoneMixerIndex(state, zoneMixerName, zoneMixerIndex, zoneMixerErrFound, thisExhSys.Name);
135 :
136 4 : if (!zoneMixerErrFound) {
137 : // With the correct MixerNum Initialize
138 4 : MixerComponent::InitAirMixer(state, zoneMixerIndex); // Initialize all Mixer related parameters
139 :
140 : // See if need to do the zone mixer's CheckEquipName() function
141 4 : bool IsNotOK = false; // Flag to verify name
142 4 : ValidateComponent(state, "AirLoopHVAC:ZoneMixer", zoneMixerName, IsNotOK, "AirLoopHVAC:ExhaustSystem");
143 4 : if (IsNotOK) {
144 0 : ShowSevereError(state, format("{}{}={}", RoutineName, cCurrentModuleObject, thisExhSys.Name));
145 0 : ShowContinueError(state, format("ZoneMixer Name ={} mismatch or not found.", zoneMixerName));
146 0 : ErrorsFound = true;
147 : } else {
148 : // normal conditions
149 : }
150 : } else {
151 0 : ShowSevereError(state, format("{}{}={}", RoutineName, cCurrentModuleObject, thisExhSys.Name));
152 0 : ShowContinueError(state, format("Zone Mixer Name ={} not found.", zoneMixerName));
153 0 : ErrorsFound = true;
154 : }
155 4 : thisExhSys.ZoneMixerName = zoneMixerName;
156 4 : thisExhSys.ZoneMixerIndex = zoneMixerIndex;
157 :
158 4 : thisExhSys.centralFanType = static_cast<HVAC::FanType>(
159 8 : getEnumValue(HVAC::fanTypeNamesUC, Util::makeUPPER(ip->getAlphaFieldValue(objectFields, objectSchemaProps, "fan_object_type"))));
160 4 : if (thisExhSys.centralFanType != HVAC::FanType::SystemModel && thisExhSys.centralFanType != HVAC::FanType::ComponentModel) {
161 0 : ShowSevereError(state, format("{}{}={}", RoutineName, cCurrentModuleObject, thisExhSys.Name));
162 0 : ShowContinueError(state, format("Fan Type ={} is not supported.", HVAC::fanTypeNames[(int)thisExhSys.centralFanType]));
163 0 : ShowContinueError(state, "It needs to be either a Fan:SystemModel or a Fan:ComponentModel type.");
164 0 : ErrorsFound = true;
165 : }
166 :
167 8 : std::string centralFanName = ip->getAlphaFieldValue(objectFields, objectSchemaProps, "fan_name");
168 :
169 4 : ErrorObjectHeader eoh{routineName, cCurrentModuleObject, thisExhSys.Name};
170 4 : int centralFanIndex = Fans::GetFanIndex(state, centralFanName);
171 4 : if (centralFanIndex == 0) {
172 0 : ShowSevereItemNotFound(state, eoh, "fan_name", centralFanName);
173 0 : ErrorsFound = true;
174 : } else {
175 4 : auto *fan = state.dataFans->fans(centralFanIndex);
176 :
177 4 : thisExhSys.availSched = fan->availSched;
178 :
179 12 : BranchNodeConnections::SetUpCompSets(state,
180 : cCurrentModuleObject,
181 : thisExhSys.Name,
182 4 : HVAC::fanTypeNames[(int)thisExhSys.centralFanType],
183 : centralFanName,
184 4 : state.dataLoopNodes->NodeID(fan->inletNodeNum),
185 4 : state.dataLoopNodes->NodeID(fan->outletNodeNum));
186 :
187 8 : SetupOutputVariable(state,
188 : "Central Exhaust Fan Mass Flow Rate",
189 : Constant::Units::kg_s,
190 4 : thisExhSys.centralFan_MassFlowRate,
191 : OutputProcessor::TimeStepType::System,
192 : OutputProcessor::StoreType::Average,
193 4 : thisExhSys.Name);
194 :
195 8 : SetupOutputVariable(state,
196 : "Central Exhaust Fan Volumetric Flow Rate Standard",
197 : Constant::Units::m3_s,
198 4 : thisExhSys.centralFan_VolumeFlowRate_Std,
199 : OutputProcessor::TimeStepType::System,
200 : OutputProcessor::StoreType::Average,
201 4 : thisExhSys.Name);
202 :
203 8 : SetupOutputVariable(state,
204 : "Central Exhaust Fan Volumetric Flow Rate Current",
205 : Constant::Units::m3_s,
206 4 : thisExhSys.centralFan_VolumeFlowRate_Cur,
207 : OutputProcessor::TimeStepType::System,
208 : OutputProcessor::StoreType::Average,
209 4 : thisExhSys.Name);
210 :
211 8 : SetupOutputVariable(state,
212 : "Central Exhaust Fan Power",
213 : Constant::Units::W,
214 4 : thisExhSys.centralFan_Power,
215 : OutputProcessor::TimeStepType::System,
216 : OutputProcessor::StoreType::Average,
217 4 : thisExhSys.Name);
218 :
219 8 : SetupOutputVariable(state,
220 : "Central Exhaust Fan Energy",
221 : Constant::Units::J,
222 4 : thisExhSys.centralFan_Energy,
223 : OutputProcessor::TimeStepType::System,
224 : OutputProcessor::StoreType::Sum,
225 4 : thisExhSys.Name);
226 : }
227 :
228 4 : thisExhSys.CentralFanName = centralFanName;
229 4 : thisExhSys.CentralFanIndex = centralFanIndex;
230 :
231 : // sizing
232 4 : if (thisExhSys.SizingFlag) {
233 4 : SizeExhaustSystem(state, exhSysNum);
234 : }
235 4 : }
236 2 : state.dataZoneEquip->NumExhaustAirSystems = numExhaustSystems;
237 : } else {
238 : // If no exhaust systems are defined, then do something <or nothing>:
239 : }
240 :
241 112 : if (ErrorsFound) {
242 0 : ShowFatalError(state, "Errors found getting AirLoopHVAC:ExhaustSystem. Preceding condition(s) causes termination.");
243 : }
244 112 : }
245 :
246 0 : void CalcExhaustAirSystem(EnergyPlusData &state, int const ExhaustAirSystemNum, bool FirstHVACIteration)
247 : {
248 0 : auto &thisExhSys = state.dataZoneEquip->ExhaustAirSystem(ExhaustAirSystemNum);
249 0 : constexpr std::string_view RoutineName = "CalExhaustAirSystem: ";
250 0 : constexpr std::string_view cCurrentModuleObject = "AirloopHVAC:ExhaustSystem";
251 0 : bool ErrorsFound = false;
252 0 : if (!(state.afn->AirflowNetworkFanActivated && state.afn->distribution_simulated)) {
253 0 : MixerComponent::SimAirMixer(state, thisExhSys.ZoneMixerName, thisExhSys.ZoneMixerIndex);
254 : } else {
255 : // Give a warning that the current model does not work with AirflowNetwork for now
256 0 : ShowSevereError(state, format("{}{}={}", RoutineName, cCurrentModuleObject, thisExhSys.Name));
257 0 : ShowContinueError(state, "AirloopHVAC:ExhaustSystem currently does not work with AirflowNetwork.");
258 0 : ErrorsFound = true;
259 : }
260 :
261 0 : if (ErrorsFound) {
262 0 : ShowFatalError(state, "Errors found conducting CalcExhasutAirSystem(). Preceding condition(s) causes termination.");
263 : }
264 :
265 0 : Real64 mixerFlow_Prior = 0.0;
266 0 : int outletNode_index = state.dataMixerComponent->MixerCond(thisExhSys.ZoneMixerIndex).OutletNode;
267 0 : mixerFlow_Prior = state.dataLoopNodes->Node(outletNode_index).MassFlowRate;
268 : if (mixerFlow_Prior == 0.0) {
269 : // No flow coming out from the exhaust controls;
270 : // fan should be cut off now;
271 : }
272 :
273 0 : int outletNode_Num = 0;
274 0 : Real64 RhoAirCurrent = state.dataEnvrn->StdRhoAir;
275 :
276 0 : if (thisExhSys.centralFanType == HVAC::FanType::SystemModel) {
277 0 : state.dataHVACGlobal->OnOffFanPartLoadFraction = 1.0;
278 0 : state.dataFans->fans(thisExhSys.CentralFanIndex)->simulate(state, false, _, _);
279 :
280 : // Update report variables
281 0 : outletNode_Num = state.dataFans->fans(thisExhSys.CentralFanIndex)->outletNodeNum;
282 :
283 0 : thisExhSys.centralFan_MassFlowRate = state.dataLoopNodes->Node(outletNode_Num).MassFlowRate;
284 :
285 0 : thisExhSys.centralFan_VolumeFlowRate_Std = state.dataLoopNodes->Node(outletNode_Num).MassFlowRate / state.dataEnvrn->StdRhoAir;
286 :
287 0 : RhoAirCurrent = EnergyPlus::Psychrometrics::PsyRhoAirFnPbTdbW(state,
288 0 : state.dataEnvrn->OutBaroPress,
289 0 : state.dataLoopNodes->Node(outletNode_Num).Temp,
290 0 : state.dataLoopNodes->Node(outletNode_Num).HumRat);
291 0 : if (RhoAirCurrent <= 0.0) RhoAirCurrent = state.dataEnvrn->StdRhoAir;
292 0 : thisExhSys.centralFan_VolumeFlowRate_Cur = state.dataLoopNodes->Node(outletNode_Num).MassFlowRate / RhoAirCurrent;
293 :
294 0 : thisExhSys.centralFan_Power = state.dataFans->fans(thisExhSys.CentralFanIndex)->totalPower;
295 :
296 0 : thisExhSys.centralFan_Energy = thisExhSys.centralFan_Power * state.dataHVACGlobal->TimeStepSysSec;
297 :
298 0 : } else if (thisExhSys.centralFanType == HVAC::FanType::ComponentModel) {
299 0 : auto *fan = state.dataFans->fans(thisExhSys.CentralFanIndex);
300 0 : fan->simulate(state, FirstHVACIteration);
301 :
302 : // Update output variables
303 :
304 0 : outletNode_Num = fan->outletNodeNum;
305 :
306 0 : thisExhSys.centralFan_MassFlowRate = fan->outletAirMassFlowRate;
307 :
308 0 : thisExhSys.centralFan_VolumeFlowRate_Std = fan->outletAirMassFlowRate / state.dataEnvrn->StdRhoAir;
309 :
310 0 : RhoAirCurrent = EnergyPlus::Psychrometrics::PsyRhoAirFnPbTdbW(state,
311 0 : state.dataEnvrn->OutBaroPress,
312 0 : state.dataLoopNodes->Node(outletNode_Num).Temp,
313 0 : state.dataLoopNodes->Node(outletNode_Num).HumRat);
314 0 : if (RhoAirCurrent <= 0.0) RhoAirCurrent = state.dataEnvrn->StdRhoAir;
315 0 : thisExhSys.centralFan_VolumeFlowRate_Cur = fan->outletAirMassFlowRate / RhoAirCurrent;
316 :
317 0 : thisExhSys.centralFan_Power = fan->totalPower * 1000.0;
318 :
319 0 : thisExhSys.centralFan_Energy = fan->totalEnergy * 1000.0;
320 : }
321 0 : thisExhSys.exhTotalHVACReliefHeatLoss = state.dataLoopNodes->Node(outletNode_Num).MassFlowRate *
322 0 : (state.dataLoopNodes->Node(outletNode_Num).Enthalpy - state.dataEnvrn->OutEnthalpy);
323 :
324 0 : Real64 mixerFlow_Posterior = 0.0;
325 0 : mixerFlow_Posterior = state.dataLoopNodes->Node(outletNode_index).MassFlowRate;
326 : if (mixerFlow_Posterior < HVAC::SmallMassFlow) {
327 : // fan flow is nearly zero and should be considered off
328 : // but this still can use the ratio
329 : }
330 : if (mixerFlow_Prior < HVAC::SmallMassFlow) {
331 : // this is the case where the fan flow should be resetted to zeros and not run the ratio
332 : }
333 0 : if ((mixerFlow_Prior - mixerFlow_Posterior > HVAC::SmallMassFlow) || (mixerFlow_Prior - mixerFlow_Posterior < -HVAC::SmallMassFlow)) {
334 : // calculate a ratio
335 0 : Real64 flowRatio = mixerFlow_Posterior / mixerFlow_Prior;
336 0 : if (flowRatio > 1.0) {
337 0 : ShowWarningError(state, format("{}{}={}", RoutineName, cCurrentModuleObject, thisExhSys.Name));
338 0 : ShowContinueError(state, "Requested flow rate is lower than the exhasut fan flow rate.");
339 0 : ShowContinueError(state, "Will scale up the requested flow rate to meet fan flow rate.");
340 : }
341 :
342 : // get the mixer inlet node index
343 0 : int zoneMixerIndex = thisExhSys.ZoneMixerIndex;
344 0 : for (int i = 1; i <= state.dataMixerComponent->MixerCond(zoneMixerIndex).NumInletNodes; ++i) {
345 0 : int exhLegIndex = state.dataExhAirSystemMrg->mixerIndexMap[state.dataMixerComponent->MixerCond(zoneMixerIndex).InletNode(i)];
346 0 : CalcZoneHVACExhaustControl(state, exhLegIndex, flowRatio);
347 : }
348 :
349 : // Simulate the mixer again to update the flow
350 0 : MixerComponent::SimAirMixer(state, thisExhSys.ZoneMixerName, thisExhSys.ZoneMixerIndex);
351 :
352 : // if the adjustment matches, then no need to run fan simulation again.
353 : }
354 0 : }
355 :
356 112 : void GetZoneExhaustControlInput(EnergyPlusData &state)
357 : {
358 : // Process ZoneExhaust Control inputs
359 :
360 : // Locals
361 112 : bool ErrorsFound = false;
362 :
363 : // Use the json helper to process input
364 112 : constexpr std::string_view RoutineName("GetZoneExhaustControlInput: ");
365 112 : constexpr std::string_view routineName = "GetZoneExhaustControlInput";
366 :
367 112 : std::string const cCurrentModuleObject = "ZoneHVAC:ExhaustControl";
368 112 : auto &ip = state.dataInputProcessing->inputProcessor;
369 112 : auto const instances = ip->epJSON.find(cCurrentModuleObject);
370 112 : if (instances != ip->epJSON.end()) {
371 2 : auto const &objectSchemaProps = ip->getObjectSchemaProps(state, cCurrentModuleObject);
372 2 : auto &instancesValue = instances.value();
373 2 : int numZoneExhaustControls = instancesValue.size();
374 2 : int exhCtrlNum = 0;
375 : int NumAlphas;
376 : int NumNums;
377 :
378 2 : if (numZoneExhaustControls > 0) {
379 2 : state.dataZoneEquip->ZoneExhaustControlSystem.allocate(numZoneExhaustControls);
380 : }
381 :
382 10 : for (auto instance = instancesValue.begin(); instance != instancesValue.end(); ++instance) {
383 8 : ++exhCtrlNum;
384 8 : auto const &objectFields = instance.value();
385 8 : auto &thisExhCtrl = state.dataZoneEquip->ZoneExhaustControlSystem(exhCtrlNum);
386 :
387 8 : ErrorObjectHeader eoh{routineName, cCurrentModuleObject, instance.key()};
388 :
389 8 : thisExhCtrl.Name = Util::makeUPPER(instance.key());
390 8 : ip->markObjectAsUsed(cCurrentModuleObject, instance.key());
391 :
392 16 : std::string availSchName = ip->getAlphaFieldValue(objectFields, objectSchemaProps, "availability_schedule_name");
393 8 : if (availSchName.empty()) {
394 : // blank
395 0 : thisExhCtrl.availSched = Sched::GetScheduleAlwaysOn(state);
396 8 : } else if ((thisExhCtrl.availSched = Sched::GetSchedule(state, availSchName)) == nullptr) {
397 : // mismatch, reset to always on
398 0 : thisExhCtrl.availSched = Sched::GetScheduleAlwaysOn(state);
399 0 : ShowWarningItemNotFound(state, eoh, "Avaiability Schedule Name", availSchName, "Availability Schedule is reset to Always ON.");
400 : }
401 :
402 16 : std::string zoneName = ip->getAlphaFieldValue(objectFields, objectSchemaProps, "zone_name");
403 8 : thisExhCtrl.ZoneName = zoneName;
404 8 : int zoneNum = Util::FindItemInList(zoneName, state.dataHeatBal->Zone);
405 8 : thisExhCtrl.ZoneNum = zoneNum;
406 :
407 8 : thisExhCtrl.ControlledZoneNum = Util::FindItemInList(zoneName, state.dataHeatBal->Zone);
408 :
409 : // These two nodes are required inputs:
410 16 : std::string inletNodeName = ip->getAlphaFieldValue(objectFields, objectSchemaProps, "inlet_node_name");
411 8 : int inletNodeNum = NodeInputManager::GetOnlySingleNode(state,
412 : inletNodeName,
413 : ErrorsFound,
414 : DataLoopNode::ConnectionObjectType::ZoneHVACExhaustControl,
415 8 : thisExhCtrl.Name,
416 : DataLoopNode::NodeFluidType::Air,
417 : DataLoopNode::ConnectionType::Inlet,
418 : NodeInputManager::CompFluidStream::Primary,
419 : DataLoopNode::ObjectIsParent);
420 8 : thisExhCtrl.InletNodeNum = inletNodeNum;
421 :
422 16 : std::string outletNodeName = ip->getAlphaFieldValue(objectFields, objectSchemaProps, "outlet_node_name");
423 :
424 8 : int outletNodeNum = NodeInputManager::GetOnlySingleNode(state,
425 : outletNodeName,
426 : ErrorsFound,
427 : DataLoopNode::ConnectionObjectType::ZoneHVACExhaustControl,
428 8 : thisExhCtrl.Name,
429 : DataLoopNode::NodeFluidType::Air,
430 : DataLoopNode::ConnectionType::Outlet,
431 : NodeInputManager::CompFluidStream::Primary,
432 8 : DataLoopNode::ObjectIsParent);
433 8 : thisExhCtrl.OutletNodeNum = outletNodeNum;
434 :
435 8 : if (!state.dataExhAirSystemMrg->mappingDone) {
436 8 : state.dataExhAirSystemMrg->mixerIndexMap.emplace(outletNodeNum, exhCtrlNum);
437 : }
438 :
439 16 : Real64 designExhaustFlowRate = ip->getRealFieldValue(objectFields, objectSchemaProps, "design_exhaust_flow_rate");
440 8 : thisExhCtrl.DesignExhaustFlowRate = designExhaustFlowRate;
441 :
442 16 : std::string flowControlTypeName = Util::makeUPPER(ip->getAlphaFieldValue(objectFields, objectSchemaProps, "flow_control_type"));
443 : // std::string flowControlTypeName = Util::makeUPPER(fields.at("flow_control_type").get<std::string>());
444 8 : thisExhCtrl.FlowControlOption =
445 8 : static_cast<ZoneExhaustControl::FlowControlType>(getEnumValue(flowControlTypeNamesUC, flowControlTypeName));
446 :
447 : std::string exhaustFlowFractionSchedName =
448 16 : ip->getAlphaFieldValue(objectFields, objectSchemaProps, "exhaust_flow_fraction_schedule_name");
449 :
450 8 : if (exhaustFlowFractionSchedName.empty()) {
451 2 : thisExhCtrl.exhaustFlowFractionSched =
452 2 : Sched::GetScheduleAlwaysOn(state); // Not an availability schedule, but defaults to constant-1.0
453 6 : } else if ((thisExhCtrl.exhaustFlowFractionSched = Sched::GetSchedule(state, exhaustFlowFractionSchedName)) == nullptr) {
454 0 : ShowSevereItemNotFound(state, eoh, "Exhaust Flow Fraction Schedule Name", exhaustFlowFractionSchedName);
455 : }
456 :
457 16 : thisExhCtrl.SupplyNodeOrNodelistName = ip->getAlphaFieldValue(objectFields, objectSchemaProps, "supply_node_or_nodelist_name");
458 :
459 8 : bool NodeListError = false;
460 8 : int NumParams = 0;
461 8 : int NumNodes = 0;
462 :
463 8 : ip->getObjectDefMaxArgs(state, "NodeList", NumParams, NumAlphas, NumNums);
464 8 : thisExhCtrl.SuppNodeNums.dimension(NumParams, 0);
465 8 : NodeInputManager::GetNodeNums(state,
466 8 : thisExhCtrl.SupplyNodeOrNodelistName,
467 : NumNodes,
468 8 : thisExhCtrl.SuppNodeNums,
469 : NodeListError,
470 : DataLoopNode::NodeFluidType::Air,
471 : DataLoopNode::ConnectionObjectType::ZoneHVACExhaustControl, // maybe zone inlets?
472 8 : thisExhCtrl.Name,
473 : DataLoopNode::ConnectionType::Sensor,
474 : NodeInputManager::CompFluidStream::Primary,
475 : DataLoopNode::ObjectIsNotParent); // , // _, // supplyNodeOrNodelistName);
476 :
477 : // Verify these nodes are indeed supply nodes:
478 8 : if (thisExhCtrl.FlowControlOption == ZoneExhaustControl::FlowControlType::FollowSupply) { // FollowSupply
479 0 : bool nodeNotFound = false;
480 0 : for (size_t i = 1; i <= thisExhCtrl.SuppNodeNums.size(); ++i) {
481 0 : CheckForSupplyNode(state, exhCtrlNum, nodeNotFound);
482 0 : if (nodeNotFound) {
483 0 : ShowSevereError(state, format("{}{}={}", RoutineName, cCurrentModuleObject, thisExhCtrl.Name));
484 0 : ShowContinueError(state,
485 0 : format("Node or NodeList Name ={}. Must all be supply nodes.", thisExhCtrl.SupplyNodeOrNodelistName));
486 0 : ErrorsFound = true;
487 : }
488 : }
489 : }
490 :
491 : // Deal with design exhaust autosize here;
492 8 : if (thisExhCtrl.DesignExhaustFlowRate == DataSizing::AutoSize) {
493 2 : SizeExhaustControlFlow(state, exhCtrlNum, thisExhCtrl.SuppNodeNums);
494 : }
495 :
496 : std::string minZoneTempLimitSchedName =
497 16 : ip->getAlphaFieldValue(objectFields, objectSchemaProps, "minimum_zone_temperature_limit_schedule_name");
498 8 : if (minZoneTempLimitSchedName.empty()) {
499 2 : } else if ((thisExhCtrl.minZoneTempLimitSched = Sched::GetSchedule(state, minZoneTempLimitSchedName)) == nullptr) {
500 0 : ShowSevereItemNotFound(state, eoh, "Minimum Zone Temperature Limit Schedule Name", minZoneTempLimitSchedName);
501 : }
502 :
503 : std::string minExhFlowFracSchedName =
504 16 : ip->getAlphaFieldValue(objectFields, objectSchemaProps, "minimum_exhaust_flow_fraction_schedule_name");
505 : // to do so schedule matching
506 8 : if (minExhFlowFracSchedName.empty()) {
507 8 : } else if ((thisExhCtrl.minExhFlowFracSched = Sched::GetSchedule(state, minExhFlowFracSchedName)) == nullptr) {
508 0 : ShowSevereItemNotFound(state, eoh, "Minimum Exhaust Flow Fraction Schedule Name", minExhFlowFracSchedName);
509 : }
510 :
511 : std::string balancedExhFracSchedName =
512 16 : ip->getAlphaFieldValue(objectFields, objectSchemaProps, "balanced_exhaust_fraction_schedule_name");
513 : // to do so schedule matching
514 8 : if (balancedExhFracSchedName.empty()) {
515 8 : } else if ((thisExhCtrl.balancedExhFracSched = Sched::GetSchedule(state, balancedExhFracSchedName)) == nullptr) {
516 8 : ShowSevereItemNotFound(state, eoh, "Balanced Exhaust Fraction Schedule Name", balancedExhFracSchedName);
517 : }
518 :
519 : // Maybe an additional check per IORef:
520 : // This input field must be blank when the zone air flow balance is enforced. If user specifies a schedule and zone air flow balance
521 : // is enforced, then EnergyPlus throws a warning error message, ignores the schedule and simulation continues.
522 8 : }
523 :
524 2 : state.dataZoneEquip->NumZoneExhaustControls = numZoneExhaustControls; // or exhCtrlNum
525 :
526 : // Done with creating a map that contains a table of for each zone to exhasut controls
527 2 : state.dataExhAirSystemMrg->mappingDone = true;
528 : }
529 :
530 112 : if (ErrorsFound) {
531 0 : ShowFatalError(state, "Errors found getting ZoneHVAC:ExhaustControl. Preceding condition(s) causes termination.");
532 : }
533 112 : }
534 :
535 424465 : void SimZoneHVACExhaustControls(EnergyPlusData &state)
536 : {
537 424465 : if (state.dataExhCtrlSystemMrg->GetInputFlag) { // First time subroutine has been entered
538 110 : GetZoneExhaustControlInput(state);
539 110 : state.dataExhCtrlSystemMrg->GetInputFlag = false;
540 : }
541 :
542 424465 : for (int ExhaustControlNum = 1; ExhaustControlNum <= state.dataZoneEquip->NumZoneExhaustControls; ++ExhaustControlNum) {
543 0 : CalcZoneHVACExhaustControl(state, ExhaustControlNum);
544 : }
545 :
546 : // report results if needed
547 424465 : }
548 :
549 1 : void CalcZoneHVACExhaustControl(EnergyPlusData &state, int const ZoneHVACExhaustControlNum, Real64 const FlowRatio)
550 : {
551 : // Calculate a zonehvac exhaust control system
552 :
553 1 : auto &thisExhCtrl = state.dataZoneEquip->ZoneExhaustControlSystem(ZoneHVACExhaustControlNum);
554 :
555 1 : int InletNode = thisExhCtrl.InletNodeNum;
556 1 : int OutletNode = thisExhCtrl.OutletNodeNum;
557 1 : auto &thisExhInlet = state.dataLoopNodes->Node(InletNode);
558 1 : auto &thisExhOutlet = state.dataLoopNodes->Node(OutletNode);
559 : Real64 MassFlow;
560 1 : Real64 Tin = state.dataZoneTempPredictorCorrector->zoneHeatBalance(thisExhCtrl.ZoneNum).ZT;
561 1 : Real64 thisExhCtrlAvailScheVal = thisExhCtrl.availSched->getCurrentVal();
562 :
563 1 : if (FlowRatio >= 0.0) {
564 0 : thisExhCtrl.BalancedFlow *= FlowRatio;
565 0 : thisExhCtrl.UnbalancedFlow *= FlowRatio;
566 :
567 0 : thisExhInlet.MassFlowRate *= FlowRatio;
568 : } else {
569 : // Availability schedule:
570 1 : if (thisExhCtrlAvailScheVal <= 0.0) {
571 1 : MassFlow = 0.0;
572 1 : thisExhInlet.MassFlowRate = 0.0;
573 : } else {
574 : //
575 : }
576 :
577 1 : Real64 DesignFlowRate = thisExhCtrl.DesignExhaustFlowRate;
578 1 : Real64 FlowFrac = 0.0;
579 1 : if (thisExhCtrl.minExhFlowFracSched != nullptr) {
580 1 : FlowFrac = thisExhCtrl.exhaustFlowFractionSched->getCurrentVal();
581 1 : if (FlowFrac < 0.0) {
582 0 : ShowWarningError(
583 0 : state, format("Exhaust Flow Fraction Schedule value is negative for Zone Exhaust Control Named: {};", thisExhCtrl.Name));
584 0 : ShowContinueError(state, "Reset value to zero and continue the simulation.");
585 0 : FlowFrac = 0.0;
586 : }
587 : }
588 :
589 1 : Real64 MinFlowFrac = 0.0;
590 1 : if (thisExhCtrl.minExhFlowFracSched != nullptr) {
591 1 : MinFlowFrac = thisExhCtrl.minExhFlowFracSched->getCurrentVal();
592 1 : if (MinFlowFrac < 0.0) {
593 0 : ShowWarningError(
594 : state,
595 0 : format("Minimum Exhaust Flow Fraction Schedule value is negative for Zone Exhaust Control Named: {};", thisExhCtrl.Name));
596 0 : ShowContinueError(state, "Reset value to zero and continue the simulation.");
597 0 : MinFlowFrac = 0.0;
598 : }
599 : }
600 :
601 1 : if (FlowFrac < MinFlowFrac) {
602 0 : FlowFrac = MinFlowFrac;
603 : }
604 :
605 1 : if (thisExhCtrlAvailScheVal > 0.0) { // available
606 0 : if (thisExhCtrl.minZoneTempLimitSched != nullptr) {
607 0 : if (Tin >= thisExhCtrl.minZoneTempLimitSched->getCurrentVal()) {
608 : } else {
609 0 : FlowFrac = MinFlowFrac;
610 : }
611 : } else {
612 : // flow not changed
613 : }
614 : } else {
615 1 : FlowFrac = 0.0; // directly set flow rate to zero.
616 : }
617 :
618 1 : if (thisExhCtrl.FlowControlOption == ZoneExhaustControl::FlowControlType::FollowSupply) { // follow-supply
619 0 : Real64 supplyFlowRate = 0.0;
620 0 : int numOfSuppNodes = thisExhCtrl.SuppNodeNums.size();
621 0 : for (int i = 1; i <= numOfSuppNodes; ++i) {
622 0 : supplyFlowRate += state.dataLoopNodes->Node(thisExhCtrl.SuppNodeNums(i)).MassFlowRate;
623 : }
624 0 : MassFlow = supplyFlowRate * FlowFrac;
625 : } else { // Scheduled or Invalid
626 1 : MassFlow = DesignFlowRate * FlowFrac;
627 : }
628 :
629 1 : if (thisExhCtrl.balancedExhFracSched != nullptr) {
630 0 : thisExhCtrl.BalancedFlow = // state.dataHVACGlobal->BalancedExhMassFlow =
631 0 : MassFlow * // state.dataHVACGlobal->UnbalExhMassFlow *
632 0 : thisExhCtrl.balancedExhFracSched->getCurrentVal();
633 0 : thisExhCtrl.UnbalancedFlow = // state.dataHVACGlobal->UnbalExhMassFlow =
634 0 : MassFlow - // = state.dataHVACGlobal->UnbalExhMassFlow -
635 0 : thisExhCtrl.BalancedFlow; // state.dataHVACGlobal->BalancedExhMassFlow;
636 : } else {
637 1 : thisExhCtrl.BalancedFlow = 0.0;
638 1 : thisExhCtrl.UnbalancedFlow = MassFlow;
639 : }
640 :
641 1 : thisExhInlet.MassFlowRate = MassFlow;
642 : }
643 :
644 1 : thisExhOutlet.MassFlowRate = thisExhInlet.MassFlowRate;
645 :
646 1 : thisExhOutlet.Temp = thisExhInlet.Temp;
647 1 : thisExhOutlet.HumRat = thisExhInlet.HumRat;
648 1 : thisExhOutlet.Enthalpy = thisExhInlet.Enthalpy;
649 : // Set the outlet nodes for properties that just pass through & not used
650 1 : thisExhOutlet.Quality = thisExhInlet.Quality;
651 1 : thisExhOutlet.Press = thisExhInlet.Press;
652 :
653 : // Set the Node Flow Control Variables from the Fan Control Variables
654 1 : thisExhOutlet.MassFlowRateMax = thisExhInlet.MassFlowRateMax;
655 1 : thisExhOutlet.MassFlowRateMaxAvail = thisExhInlet.MassFlowRateMaxAvail;
656 1 : thisExhOutlet.MassFlowRateMinAvail = thisExhInlet.MassFlowRateMinAvail;
657 :
658 : // these might also be useful to pass through
659 1 : if (state.dataContaminantBalance->Contaminant.CO2Simulation) {
660 0 : thisExhOutlet.CO2 = thisExhInlet.CO2;
661 : }
662 :
663 1 : if (state.dataContaminantBalance->Contaminant.GenericContamSimulation) {
664 0 : thisExhOutlet.GenContam = thisExhInlet.GenContam;
665 : }
666 1 : }
667 :
668 4 : void SizeExhaustSystem(EnergyPlusData &state, int const exhSysNum)
669 : {
670 4 : auto &thisExhSys = state.dataZoneEquip->ExhaustAirSystem(exhSysNum);
671 :
672 4 : if (!thisExhSys.SizingFlag) {
673 0 : return;
674 : }
675 :
676 : // mixer outlet sizing:
677 4 : Real64 outletFlowMaxAvail = 0.0;
678 12 : for (int i = 1; i <= state.dataMixerComponent->MixerCond(thisExhSys.ZoneMixerIndex).NumInletNodes; ++i) {
679 8 : int inletNode_index = state.dataMixerComponent->MixerCond(thisExhSys.ZoneMixerIndex).InletNode(i);
680 8 : outletFlowMaxAvail += state.dataLoopNodes->Node(inletNode_index).MassFlowRateMaxAvail;
681 : }
682 :
683 : // mixer outlet considered OutletMassFlowRateMaxAvail?
684 4 : int outletNode_index = state.dataMixerComponent->MixerCond(thisExhSys.ZoneMixerIndex).OutletNode;
685 4 : state.dataLoopNodes->Node(outletNode_index).MassFlowRateMaxAvail = outletFlowMaxAvail;
686 :
687 4 : auto *fan = state.dataFans->fans(thisExhSys.CentralFanIndex);
688 : // then central exhasut fan sizing here:
689 4 : if (thisExhSys.centralFanType == HVAC::FanType::SystemModel) {
690 4 : if (fan->maxAirFlowRate == DataSizing::AutoSize) {
691 0 : fan->maxAirFlowRate = outletFlowMaxAvail / state.dataEnvrn->StdRhoAir;
692 : }
693 4 : BaseSizer::reportSizerOutput(state, "FAN:SYSTEMMODEL", fan->Name, "Design Fan Airflow [m3/s]", fan->maxAirFlowRate);
694 0 : } else if (thisExhSys.centralFanType == HVAC::FanType::ComponentModel) {
695 0 : if (fan->maxAirMassFlowRate == DataSizing::AutoSize) {
696 0 : fan->maxAirMassFlowRate = outletFlowMaxAvail * dynamic_cast<Fans::FanComponent *>(fan)->sizingFactor;
697 : }
698 0 : BaseSizer::reportSizerOutput(state,
699 0 : HVAC::fanTypeNames[(int)fan->type],
700 : fan->Name,
701 : "Design Fan Airflow [m3/s]",
702 0 : fan->maxAirMassFlowRate / state.dataEnvrn->StdRhoAir);
703 : } else {
704 : //
705 : }
706 :
707 : // after evertyhing sized, set the sizing flag to be false
708 4 : thisExhSys.SizingFlag = false;
709 : }
710 :
711 2 : void SizeExhaustControlFlow(EnergyPlusData &state, int const zoneExhCtrlNum, Array1D_int &NodeNums)
712 : {
713 2 : auto &thisExhCtrl = state.dataZoneEquip->ZoneExhaustControlSystem(zoneExhCtrlNum);
714 :
715 2 : Real64 designFlow = 0.0;
716 :
717 2 : if (thisExhCtrl.FlowControlOption == ZoneExhaustControl::FlowControlType::FollowSupply) { // FollowSupply
718 : // size based on supply nodelist flow
719 0 : for (size_t i = 1; i <= NodeNums.size(); ++i) {
720 0 : designFlow += state.dataLoopNodes->Node(NodeNums(i)).MassFlowRateMax;
721 : }
722 : } else { // scheduled etc.
723 : // based on zone OA.
724 2 : designFlow = state.dataSize->FinalZoneSizing(thisExhCtrl.ZoneNum).MinOA;
725 : }
726 :
727 2 : thisExhCtrl.DesignExhaustFlowRate = designFlow;
728 2 : }
729 :
730 424465 : void UpdateZoneExhaustControl(EnergyPlusData &state)
731 : {
732 424465 : for (int i = 1; i <= state.dataZoneEquip->NumZoneExhaustControls; ++i) {
733 0 : int controlledZoneNum = state.dataZoneEquip->ZoneExhaustControlSystem(i).ControlledZoneNum;
734 0 : state.dataZoneEquip->ZoneEquipConfig(controlledZoneNum).ZoneExh +=
735 0 : state.dataZoneEquip->ZoneExhaustControlSystem(i).BalancedFlow + state.dataZoneEquip->ZoneExhaustControlSystem(i).UnbalancedFlow;
736 0 : state.dataZoneEquip->ZoneEquipConfig(controlledZoneNum).ZoneExhBalanced += state.dataZoneEquip->ZoneExhaustControlSystem(i).BalancedFlow;
737 : }
738 424465 : }
739 :
740 2 : void CheckForSupplyNode(EnergyPlusData &state, int const ExhCtrlNum, bool &NodeNotFound)
741 : {
742 : // Trying to check a node to see if it is truely a supply node
743 : // for a nodelist, need a call loop to check each node in the list
744 :
745 2 : auto &thisExhCtrl = state.dataZoneEquip->ZoneExhaustControlSystem(ExhCtrlNum);
746 :
747 : // check a node is a zone inlet node.
748 2 : std::string_view constexpr RoutineName = "GetExhaustControlInput: ";
749 2 : std::string_view constexpr CurrentModuleObject = "ZoneHVAC:ExhaustControl";
750 :
751 2 : bool ZoneNodeNotFound = true;
752 2 : bool ErrorsFound = false;
753 4 : for (size_t i = 1; i <= thisExhCtrl.SuppNodeNums.size(); ++i) {
754 2 : int supplyNodeNum = thisExhCtrl.SuppNodeNums(i);
755 3 : for (int NodeNum = 1; NodeNum <= state.dataZoneEquip->ZoneEquipConfig(thisExhCtrl.ZoneNum).NumInletNodes; ++NodeNum) {
756 2 : if (supplyNodeNum == state.dataZoneEquip->ZoneEquipConfig(thisExhCtrl.ZoneNum).InletNode(NodeNum)) {
757 1 : ZoneNodeNotFound = false;
758 1 : break;
759 : }
760 : }
761 2 : if (ZoneNodeNotFound) {
762 1 : ShowSevereError(state, format("{}{}={}", RoutineName, CurrentModuleObject, thisExhCtrl.Name));
763 2 : ShowContinueError(
764 : state,
765 2 : format("Supply or supply list = \"{}\" contains at least one node that is not a zone inlet node for Zone Name = \"{}\"",
766 1 : thisExhCtrl.SupplyNodeOrNodelistName,
767 1 : thisExhCtrl.ZoneName));
768 2 : ShowContinueError(state, "..Nodes in the supply node or nodelist must be a zone inlet node.");
769 1 : ErrorsFound = true;
770 : }
771 : }
772 :
773 2 : NodeNotFound = ErrorsFound;
774 2 : }
775 :
776 0 : bool ExhaustSystemHasMixer(EnergyPlusData &state, std::string_view CompName) // component (mixer) name
777 : {
778 : // Given a mixer name, this routine determines if that mixer is found on Exhaust Systems.
779 :
780 0 : if (state.dataExhAirSystemMrg->GetInputFlag) {
781 0 : GetExhaustAirSystemInput(state);
782 0 : state.dataExhAirSystemMrg->GetInputFlag = false;
783 : }
784 :
785 : return // ( state.dataZoneEquip->NumExhaustAirSystems > 0) &&
786 0 : (Util::FindItemInList(CompName, state.dataZoneEquip->ExhaustAirSystem, &ExhaustAir::ZoneMixerName) > 0);
787 : }
788 :
789 : } // namespace ExhaustAirSystemManager
790 :
791 : } // namespace EnergyPlus
|