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) {
103 0 : return;
104 : }
105 : // state.dataExhAirSystemMrg->GetInputFlag = false;
106 :
107 : // Locals
108 112 : bool ErrorsFound = false;
109 :
110 112 : constexpr std::string_view RoutineName("GetExhaustAirSystemInput: ");
111 112 : constexpr std::string_view routineName = "GetExhaustAirSystemInput";
112 112 : std::string const cCurrentModuleObject = "AirLoopHVAC:ExhaustSystem";
113 :
114 112 : auto &ip = state.dataInputProcessing->inputProcessor;
115 112 : auto const instances = ip->epJSON.find(cCurrentModuleObject);
116 112 : if (instances != ip->epJSON.end()) {
117 2 : auto const &objectSchemaProps = ip->getObjectSchemaProps(state, cCurrentModuleObject);
118 2 : auto &instancesValue = instances.value();
119 2 : int numExhaustSystems = instancesValue.size();
120 2 : int exhSysNum = 0;
121 :
122 2 : if (numExhaustSystems > 0) {
123 2 : state.dataZoneEquip->ExhaustAirSystem.allocate(numExhaustSystems);
124 : }
125 :
126 6 : for (auto instance = instancesValue.begin(); instance != instancesValue.end(); ++instance) {
127 4 : ++exhSysNum;
128 4 : auto const &objectFields = instance.value();
129 4 : auto &thisExhSys = state.dataZoneEquip->ExhaustAirSystem(exhSysNum);
130 4 : thisExhSys.Name = Util::makeUPPER(instance.key());
131 4 : ip->markObjectAsUsed(cCurrentModuleObject, instance.key());
132 :
133 8 : std::string zoneMixerName = ip->getAlphaFieldValue(objectFields, objectSchemaProps, "zone_mixer_name");
134 4 : int zoneMixerIndex = 0;
135 4 : bool zoneMixerErrFound = false;
136 4 : MixerComponent::GetZoneMixerIndex(state, zoneMixerName, zoneMixerIndex, zoneMixerErrFound, thisExhSys.Name);
137 :
138 4 : if (!zoneMixerErrFound) {
139 : // With the correct MixerNum Initialize
140 4 : MixerComponent::InitAirMixer(state, zoneMixerIndex); // Initialize all Mixer related parameters
141 :
142 : // See if need to do the zone mixer's CheckEquipName() function
143 4 : bool IsNotOK = false; // Flag to verify name
144 4 : ValidateComponent(state, "AirLoopHVAC:ZoneMixer", zoneMixerName, IsNotOK, "AirLoopHVAC:ExhaustSystem");
145 4 : if (IsNotOK) {
146 0 : ShowSevereError(state, format("{}{}={}", RoutineName, cCurrentModuleObject, thisExhSys.Name));
147 0 : ShowContinueError(state, format("ZoneMixer Name ={} mismatch or not found.", zoneMixerName));
148 0 : ErrorsFound = true;
149 : } else {
150 : // normal conditions
151 : }
152 : } else {
153 0 : ShowSevereError(state, format("{}{}={}", RoutineName, cCurrentModuleObject, thisExhSys.Name));
154 0 : ShowContinueError(state, format("Zone Mixer Name ={} not found.", zoneMixerName));
155 0 : ErrorsFound = true;
156 : }
157 4 : thisExhSys.ZoneMixerName = zoneMixerName;
158 4 : thisExhSys.ZoneMixerIndex = zoneMixerIndex;
159 :
160 4 : thisExhSys.centralFanType = static_cast<HVAC::FanType>(
161 8 : getEnumValue(HVAC::fanTypeNamesUC, Util::makeUPPER(ip->getAlphaFieldValue(objectFields, objectSchemaProps, "fan_object_type"))));
162 4 : if (thisExhSys.centralFanType != HVAC::FanType::SystemModel && thisExhSys.centralFanType != HVAC::FanType::ComponentModel) {
163 0 : ShowSevereError(state, format("{}{}={}", RoutineName, cCurrentModuleObject, thisExhSys.Name));
164 0 : ShowContinueError(state, format("Fan Type ={} is not supported.", HVAC::fanTypeNames[(int)thisExhSys.centralFanType]));
165 0 : ShowContinueError(state, "It needs to be either a Fan:SystemModel or a Fan:ComponentModel type.");
166 0 : ErrorsFound = true;
167 : }
168 :
169 8 : std::string centralFanName = ip->getAlphaFieldValue(objectFields, objectSchemaProps, "fan_name");
170 :
171 4 : ErrorObjectHeader eoh{routineName, cCurrentModuleObject, thisExhSys.Name};
172 4 : int centralFanIndex = Fans::GetFanIndex(state, centralFanName);
173 4 : if (centralFanIndex == 0) {
174 0 : ShowSevereItemNotFound(state, eoh, "fan_name", centralFanName);
175 0 : ErrorsFound = true;
176 : } else {
177 4 : auto *fan = state.dataFans->fans(centralFanIndex);
178 :
179 4 : thisExhSys.availSched = fan->availSched;
180 :
181 12 : BranchNodeConnections::SetUpCompSets(state,
182 : cCurrentModuleObject,
183 : thisExhSys.Name,
184 4 : HVAC::fanTypeNames[(int)thisExhSys.centralFanType],
185 : centralFanName,
186 4 : state.dataLoopNodes->NodeID(fan->inletNodeNum),
187 4 : state.dataLoopNodes->NodeID(fan->outletNodeNum));
188 :
189 8 : SetupOutputVariable(state,
190 : "Central Exhaust Fan Mass Flow Rate",
191 : Constant::Units::kg_s,
192 4 : thisExhSys.centralFan_MassFlowRate,
193 : OutputProcessor::TimeStepType::System,
194 : OutputProcessor::StoreType::Average,
195 4 : thisExhSys.Name);
196 :
197 8 : SetupOutputVariable(state,
198 : "Central Exhaust Fan Volumetric Flow Rate Standard",
199 : Constant::Units::m3_s,
200 4 : thisExhSys.centralFan_VolumeFlowRate_Std,
201 : OutputProcessor::TimeStepType::System,
202 : OutputProcessor::StoreType::Average,
203 4 : thisExhSys.Name);
204 :
205 8 : SetupOutputVariable(state,
206 : "Central Exhaust Fan Volumetric Flow Rate Current",
207 : Constant::Units::m3_s,
208 4 : thisExhSys.centralFan_VolumeFlowRate_Cur,
209 : OutputProcessor::TimeStepType::System,
210 : OutputProcessor::StoreType::Average,
211 4 : thisExhSys.Name);
212 :
213 8 : SetupOutputVariable(state,
214 : "Central Exhaust Fan Power",
215 : Constant::Units::W,
216 4 : thisExhSys.centralFan_Power,
217 : OutputProcessor::TimeStepType::System,
218 : OutputProcessor::StoreType::Average,
219 4 : thisExhSys.Name);
220 :
221 8 : SetupOutputVariable(state,
222 : "Central Exhaust Fan Energy",
223 : Constant::Units::J,
224 4 : thisExhSys.centralFan_Energy,
225 : OutputProcessor::TimeStepType::System,
226 : OutputProcessor::StoreType::Sum,
227 4 : thisExhSys.Name);
228 : }
229 :
230 4 : thisExhSys.CentralFanName = centralFanName;
231 4 : thisExhSys.CentralFanIndex = centralFanIndex;
232 :
233 : // sizing
234 4 : if (thisExhSys.SizingFlag) {
235 4 : SizeExhaustSystem(state, exhSysNum);
236 : }
237 6 : }
238 2 : state.dataZoneEquip->NumExhaustAirSystems = numExhaustSystems;
239 : } else {
240 : // If no exhaust systems are defined, then do something <or nothing>:
241 : }
242 :
243 112 : if (ErrorsFound) {
244 0 : ShowFatalError(state, "Errors found getting AirLoopHVAC:ExhaustSystem. Preceding condition(s) causes termination.");
245 : }
246 112 : }
247 :
248 0 : void CalcExhaustAirSystem(EnergyPlusData &state, int const ExhaustAirSystemNum, bool FirstHVACIteration)
249 : {
250 0 : auto &thisExhSys = state.dataZoneEquip->ExhaustAirSystem(ExhaustAirSystemNum);
251 0 : constexpr std::string_view RoutineName = "CalExhaustAirSystem: ";
252 0 : constexpr std::string_view cCurrentModuleObject = "AirloopHVAC:ExhaustSystem";
253 0 : bool ErrorsFound = false;
254 0 : if (!(state.afn->AirflowNetworkFanActivated && state.afn->distribution_simulated)) {
255 0 : MixerComponent::SimAirMixer(state, thisExhSys.ZoneMixerName, thisExhSys.ZoneMixerIndex);
256 : } else {
257 : // Give a warning that the current model does not work with AirflowNetwork for now
258 0 : ShowSevereError(state, format("{}{}={}", RoutineName, cCurrentModuleObject, thisExhSys.Name));
259 0 : ShowContinueError(state, "AirloopHVAC:ExhaustSystem currently does not work with AirflowNetwork.");
260 0 : ErrorsFound = true;
261 : }
262 :
263 0 : if (ErrorsFound) {
264 0 : ShowFatalError(state, "Errors found conducting CalcExhasutAirSystem(). Preceding condition(s) causes termination.");
265 : }
266 :
267 0 : Real64 mixerFlow_Prior = 0.0;
268 0 : int outletNode_index = state.dataMixerComponent->MixerCond(thisExhSys.ZoneMixerIndex).OutletNode;
269 0 : mixerFlow_Prior = state.dataLoopNodes->Node(outletNode_index).MassFlowRate;
270 : if (mixerFlow_Prior == 0.0) {
271 : // No flow coming out from the exhaust controls;
272 : // fan should be cut off now;
273 : }
274 :
275 0 : int outletNode_Num = 0;
276 0 : Real64 RhoAirCurrent = state.dataEnvrn->StdRhoAir;
277 :
278 0 : if (thisExhSys.centralFanType == HVAC::FanType::SystemModel) {
279 0 : state.dataHVACGlobal->OnOffFanPartLoadFraction = 1.0;
280 0 : state.dataFans->fans(thisExhSys.CentralFanIndex)->simulate(state, false, _, _);
281 :
282 : // Update report variables
283 0 : outletNode_Num = state.dataFans->fans(thisExhSys.CentralFanIndex)->outletNodeNum;
284 :
285 0 : thisExhSys.centralFan_MassFlowRate = state.dataLoopNodes->Node(outletNode_Num).MassFlowRate;
286 :
287 0 : thisExhSys.centralFan_VolumeFlowRate_Std = state.dataLoopNodes->Node(outletNode_Num).MassFlowRate / state.dataEnvrn->StdRhoAir;
288 :
289 0 : RhoAirCurrent = EnergyPlus::Psychrometrics::PsyRhoAirFnPbTdbW(state,
290 0 : state.dataEnvrn->OutBaroPress,
291 0 : state.dataLoopNodes->Node(outletNode_Num).Temp,
292 0 : state.dataLoopNodes->Node(outletNode_Num).HumRat);
293 0 : if (RhoAirCurrent <= 0.0) {
294 0 : RhoAirCurrent = state.dataEnvrn->StdRhoAir;
295 : }
296 0 : thisExhSys.centralFan_VolumeFlowRate_Cur = state.dataLoopNodes->Node(outletNode_Num).MassFlowRate / RhoAirCurrent;
297 :
298 0 : thisExhSys.centralFan_Power = state.dataFans->fans(thisExhSys.CentralFanIndex)->totalPower;
299 :
300 0 : thisExhSys.centralFan_Energy = thisExhSys.centralFan_Power * state.dataHVACGlobal->TimeStepSysSec;
301 :
302 0 : } else if (thisExhSys.centralFanType == HVAC::FanType::ComponentModel) {
303 0 : auto *fan = state.dataFans->fans(thisExhSys.CentralFanIndex);
304 0 : fan->simulate(state, FirstHVACIteration);
305 :
306 : // Update output variables
307 :
308 0 : outletNode_Num = fan->outletNodeNum;
309 :
310 0 : thisExhSys.centralFan_MassFlowRate = fan->outletAirMassFlowRate;
311 :
312 0 : thisExhSys.centralFan_VolumeFlowRate_Std = fan->outletAirMassFlowRate / state.dataEnvrn->StdRhoAir;
313 :
314 0 : RhoAirCurrent = EnergyPlus::Psychrometrics::PsyRhoAirFnPbTdbW(state,
315 0 : state.dataEnvrn->OutBaroPress,
316 0 : state.dataLoopNodes->Node(outletNode_Num).Temp,
317 0 : state.dataLoopNodes->Node(outletNode_Num).HumRat);
318 0 : if (RhoAirCurrent <= 0.0) {
319 0 : RhoAirCurrent = state.dataEnvrn->StdRhoAir;
320 : }
321 0 : thisExhSys.centralFan_VolumeFlowRate_Cur = fan->outletAirMassFlowRate / RhoAirCurrent;
322 :
323 0 : thisExhSys.centralFan_Power = fan->totalPower * 1000.0;
324 :
325 0 : thisExhSys.centralFan_Energy = fan->totalEnergy * 1000.0;
326 : }
327 0 : thisExhSys.exhTotalHVACReliefHeatLoss = state.dataLoopNodes->Node(outletNode_Num).MassFlowRate *
328 0 : (state.dataLoopNodes->Node(outletNode_Num).Enthalpy - state.dataEnvrn->OutEnthalpy);
329 :
330 0 : Real64 mixerFlow_Posterior = 0.0;
331 0 : mixerFlow_Posterior = state.dataLoopNodes->Node(outletNode_index).MassFlowRate;
332 : if (mixerFlow_Posterior < HVAC::SmallMassFlow) {
333 : // fan flow is nearly zero and should be considered off
334 : // but this still can use the ratio
335 : }
336 : if (mixerFlow_Prior < HVAC::SmallMassFlow) {
337 : // this is the case where the fan flow should be resetted to zeros and not run the ratio
338 : }
339 0 : if ((mixerFlow_Prior - mixerFlow_Posterior > HVAC::SmallMassFlow) || (mixerFlow_Prior - mixerFlow_Posterior < -HVAC::SmallMassFlow)) {
340 : // calculate a ratio
341 0 : Real64 flowRatio = mixerFlow_Posterior / mixerFlow_Prior;
342 0 : if (flowRatio > 1.0) {
343 0 : ShowWarningError(state, format("{}{}={}", RoutineName, cCurrentModuleObject, thisExhSys.Name));
344 0 : ShowContinueError(state, "Requested flow rate is lower than the exhasut fan flow rate.");
345 0 : ShowContinueError(state, "Will scale up the requested flow rate to meet fan flow rate.");
346 : }
347 :
348 : // get the mixer inlet node index
349 0 : int zoneMixerIndex = thisExhSys.ZoneMixerIndex;
350 0 : for (int i = 1; i <= state.dataMixerComponent->MixerCond(zoneMixerIndex).NumInletNodes; ++i) {
351 0 : int exhLegIndex = state.dataExhAirSystemMrg->mixerIndexMap[state.dataMixerComponent->MixerCond(zoneMixerIndex).InletNode(i)];
352 0 : CalcZoneHVACExhaustControl(state, exhLegIndex, flowRatio);
353 : }
354 :
355 : // Simulate the mixer again to update the flow
356 0 : MixerComponent::SimAirMixer(state, thisExhSys.ZoneMixerName, thisExhSys.ZoneMixerIndex);
357 :
358 : // if the adjustment matches, then no need to run fan simulation again.
359 : }
360 0 : }
361 :
362 112 : void GetZoneExhaustControlInput(EnergyPlusData &state)
363 : {
364 : // Process ZoneExhaust Control inputs
365 :
366 : // Locals
367 112 : bool ErrorsFound = false;
368 :
369 : // Use the json helper to process input
370 112 : constexpr std::string_view RoutineName("GetZoneExhaustControlInput: ");
371 112 : constexpr std::string_view routineName = "GetZoneExhaustControlInput";
372 :
373 112 : std::string const cCurrentModuleObject = "ZoneHVAC:ExhaustControl";
374 112 : auto &ip = state.dataInputProcessing->inputProcessor;
375 112 : auto const instances = ip->epJSON.find(cCurrentModuleObject);
376 112 : if (instances != ip->epJSON.end()) {
377 2 : auto const &objectSchemaProps = ip->getObjectSchemaProps(state, cCurrentModuleObject);
378 2 : auto &instancesValue = instances.value();
379 2 : int numZoneExhaustControls = instancesValue.size();
380 2 : int exhCtrlNum = 0;
381 : int NumAlphas;
382 : int NumNums;
383 :
384 2 : if (numZoneExhaustControls > 0) {
385 2 : state.dataZoneEquip->ZoneExhaustControlSystem.allocate(numZoneExhaustControls);
386 : }
387 :
388 10 : for (auto instance = instancesValue.begin(); instance != instancesValue.end(); ++instance) {
389 8 : ++exhCtrlNum;
390 8 : auto const &objectFields = instance.value();
391 8 : auto &thisExhCtrl = state.dataZoneEquip->ZoneExhaustControlSystem(exhCtrlNum);
392 :
393 8 : ErrorObjectHeader eoh{routineName, cCurrentModuleObject, instance.key()};
394 :
395 8 : thisExhCtrl.Name = Util::makeUPPER(instance.key());
396 8 : ip->markObjectAsUsed(cCurrentModuleObject, instance.key());
397 :
398 16 : std::string availSchName = ip->getAlphaFieldValue(objectFields, objectSchemaProps, "availability_schedule_name");
399 8 : if (availSchName.empty()) {
400 : // blank
401 0 : thisExhCtrl.availSched = Sched::GetScheduleAlwaysOn(state);
402 8 : } else if ((thisExhCtrl.availSched = Sched::GetSchedule(state, availSchName)) == nullptr) {
403 : // mismatch, reset to always on
404 0 : thisExhCtrl.availSched = Sched::GetScheduleAlwaysOn(state);
405 0 : ShowWarningItemNotFound(state, eoh, "Avaiability Schedule Name", availSchName, "Availability Schedule is reset to Always ON.");
406 : }
407 :
408 16 : std::string zoneName = ip->getAlphaFieldValue(objectFields, objectSchemaProps, "zone_name");
409 8 : thisExhCtrl.ZoneName = zoneName;
410 8 : int zoneNum = Util::FindItemInList(zoneName, state.dataHeatBal->Zone);
411 8 : thisExhCtrl.ZoneNum = zoneNum;
412 :
413 8 : thisExhCtrl.ControlledZoneNum = Util::FindItemInList(zoneName, state.dataHeatBal->Zone);
414 :
415 : // These two nodes are required inputs:
416 16 : std::string inletNodeName = ip->getAlphaFieldValue(objectFields, objectSchemaProps, "inlet_node_name");
417 8 : int inletNodeNum = NodeInputManager::GetOnlySingleNode(state,
418 : inletNodeName,
419 : ErrorsFound,
420 : DataLoopNode::ConnectionObjectType::ZoneHVACExhaustControl,
421 8 : thisExhCtrl.Name,
422 : DataLoopNode::NodeFluidType::Air,
423 : DataLoopNode::ConnectionType::Inlet,
424 : NodeInputManager::CompFluidStream::Primary,
425 : DataLoopNode::ObjectIsParent);
426 8 : thisExhCtrl.InletNodeNum = inletNodeNum;
427 :
428 16 : std::string outletNodeName = ip->getAlphaFieldValue(objectFields, objectSchemaProps, "outlet_node_name");
429 :
430 8 : int outletNodeNum = NodeInputManager::GetOnlySingleNode(state,
431 : outletNodeName,
432 : ErrorsFound,
433 : DataLoopNode::ConnectionObjectType::ZoneHVACExhaustControl,
434 8 : thisExhCtrl.Name,
435 : DataLoopNode::NodeFluidType::Air,
436 : DataLoopNode::ConnectionType::Outlet,
437 : NodeInputManager::CompFluidStream::Primary,
438 8 : DataLoopNode::ObjectIsParent);
439 8 : thisExhCtrl.OutletNodeNum = outletNodeNum;
440 :
441 8 : if (!state.dataExhAirSystemMrg->mappingDone) {
442 8 : state.dataExhAirSystemMrg->mixerIndexMap.emplace(outletNodeNum, exhCtrlNum);
443 : }
444 :
445 16 : Real64 designExhaustFlowRate = ip->getRealFieldValue(objectFields, objectSchemaProps, "design_exhaust_flow_rate");
446 8 : thisExhCtrl.DesignExhaustFlowRate = designExhaustFlowRate;
447 :
448 16 : std::string flowControlTypeName = Util::makeUPPER(ip->getAlphaFieldValue(objectFields, objectSchemaProps, "flow_control_type"));
449 : // std::string flowControlTypeName = Util::makeUPPER(fields.at("flow_control_type").get<std::string>());
450 8 : thisExhCtrl.FlowControlOption =
451 8 : static_cast<ZoneExhaustControl::FlowControlType>(getEnumValue(flowControlTypeNamesUC, flowControlTypeName));
452 :
453 : std::string exhaustFlowFractionSchedName =
454 16 : ip->getAlphaFieldValue(objectFields, objectSchemaProps, "exhaust_flow_fraction_schedule_name");
455 :
456 8 : if (exhaustFlowFractionSchedName.empty()) {
457 2 : thisExhCtrl.exhaustFlowFractionSched =
458 2 : Sched::GetScheduleAlwaysOn(state); // Not an availability schedule, but defaults to constant-1.0
459 6 : } else if ((thisExhCtrl.exhaustFlowFractionSched = Sched::GetSchedule(state, exhaustFlowFractionSchedName)) == nullptr) {
460 0 : ShowSevereItemNotFound(state, eoh, "Exhaust Flow Fraction Schedule Name", exhaustFlowFractionSchedName);
461 : }
462 :
463 16 : thisExhCtrl.SupplyNodeOrNodelistName = ip->getAlphaFieldValue(objectFields, objectSchemaProps, "supply_node_or_nodelist_name");
464 :
465 8 : bool NodeListError = false;
466 8 : int NumParams = 0;
467 8 : int NumNodes = 0;
468 :
469 8 : ip->getObjectDefMaxArgs(state, "NodeList", NumParams, NumAlphas, NumNums);
470 8 : thisExhCtrl.SuppNodeNums.dimension(NumParams, 0);
471 8 : NodeInputManager::GetNodeNums(state,
472 8 : thisExhCtrl.SupplyNodeOrNodelistName,
473 : NumNodes,
474 8 : thisExhCtrl.SuppNodeNums,
475 : NodeListError,
476 : DataLoopNode::NodeFluidType::Air,
477 : DataLoopNode::ConnectionObjectType::ZoneHVACExhaustControl, // maybe zone inlets?
478 8 : thisExhCtrl.Name,
479 : DataLoopNode::ConnectionType::Sensor,
480 : NodeInputManager::CompFluidStream::Primary,
481 : DataLoopNode::ObjectIsNotParent); // , // _, // supplyNodeOrNodelistName);
482 :
483 : // Verify these nodes are indeed supply nodes:
484 8 : if (thisExhCtrl.FlowControlOption == ZoneExhaustControl::FlowControlType::FollowSupply) { // FollowSupply
485 0 : bool nodeNotFound = false;
486 0 : for (size_t i = 1; i <= thisExhCtrl.SuppNodeNums.size(); ++i) {
487 0 : CheckForSupplyNode(state, exhCtrlNum, nodeNotFound);
488 0 : if (nodeNotFound) {
489 0 : ShowSevereError(state, format("{}{}={}", RoutineName, cCurrentModuleObject, thisExhCtrl.Name));
490 0 : ShowContinueError(state,
491 0 : format("Node or NodeList Name ={}. Must all be supply nodes.", thisExhCtrl.SupplyNodeOrNodelistName));
492 0 : ErrorsFound = true;
493 : }
494 : }
495 : }
496 :
497 : // Deal with design exhaust autosize here;
498 8 : if (thisExhCtrl.DesignExhaustFlowRate == DataSizing::AutoSize) {
499 2 : SizeExhaustControlFlow(state, exhCtrlNum, thisExhCtrl.SuppNodeNums);
500 : }
501 :
502 : std::string minZoneTempLimitSchedName =
503 16 : ip->getAlphaFieldValue(objectFields, objectSchemaProps, "minimum_zone_temperature_limit_schedule_name");
504 8 : if (minZoneTempLimitSchedName.empty()) {
505 2 : } else if ((thisExhCtrl.minZoneTempLimitSched = Sched::GetSchedule(state, minZoneTempLimitSchedName)) == nullptr) {
506 0 : ShowSevereItemNotFound(state, eoh, "Minimum Zone Temperature Limit Schedule Name", minZoneTempLimitSchedName);
507 : }
508 :
509 : std::string minExhFlowFracSchedName =
510 16 : ip->getAlphaFieldValue(objectFields, objectSchemaProps, "minimum_exhaust_flow_fraction_schedule_name");
511 : // to do so schedule matching
512 8 : if (minExhFlowFracSchedName.empty()) {
513 8 : } else if ((thisExhCtrl.minExhFlowFracSched = Sched::GetSchedule(state, minExhFlowFracSchedName)) == nullptr) {
514 0 : ShowSevereItemNotFound(state, eoh, "Minimum Exhaust Flow Fraction Schedule Name", minExhFlowFracSchedName);
515 : }
516 :
517 : std::string balancedExhFracSchedName =
518 16 : ip->getAlphaFieldValue(objectFields, objectSchemaProps, "balanced_exhaust_fraction_schedule_name");
519 : // to do so schedule matching
520 8 : if (balancedExhFracSchedName.empty()) {
521 8 : } else if ((thisExhCtrl.balancedExhFracSched = Sched::GetSchedule(state, balancedExhFracSchedName)) == nullptr) {
522 8 : ShowSevereItemNotFound(state, eoh, "Balanced Exhaust Fraction Schedule Name", balancedExhFracSchedName);
523 : }
524 :
525 : // Maybe an additional check per IORef:
526 : // This input field must be blank when the zone air flow balance is enforced. If user specifies a schedule and zone air flow balance
527 : // is enforced, then EnergyPlus throws a warning error message, ignores the schedule and simulation continues.
528 10 : }
529 :
530 2 : state.dataZoneEquip->NumZoneExhaustControls = numZoneExhaustControls; // or exhCtrlNum
531 :
532 : // Done with creating a map that contains a table of for each zone to exhasut controls
533 2 : state.dataExhAirSystemMrg->mappingDone = true;
534 : }
535 :
536 112 : if (ErrorsFound) {
537 0 : ShowFatalError(state, "Errors found getting ZoneHVAC:ExhaustControl. Preceding condition(s) causes termination.");
538 : }
539 112 : }
540 :
541 424465 : void SimZoneHVACExhaustControls(EnergyPlusData &state)
542 : {
543 424465 : if (state.dataExhCtrlSystemMrg->GetInputFlag) { // First time subroutine has been entered
544 110 : GetZoneExhaustControlInput(state);
545 110 : state.dataExhCtrlSystemMrg->GetInputFlag = false;
546 : }
547 :
548 424465 : for (int ExhaustControlNum = 1; ExhaustControlNum <= state.dataZoneEquip->NumZoneExhaustControls; ++ExhaustControlNum) {
549 0 : CalcZoneHVACExhaustControl(state, ExhaustControlNum);
550 : }
551 :
552 : // report results if needed
553 424465 : }
554 :
555 1 : void CalcZoneHVACExhaustControl(EnergyPlusData &state, int const ZoneHVACExhaustControlNum, Real64 const FlowRatio)
556 : {
557 : // Calculate a zonehvac exhaust control system
558 :
559 1 : auto &thisExhCtrl = state.dataZoneEquip->ZoneExhaustControlSystem(ZoneHVACExhaustControlNum);
560 :
561 1 : int InletNode = thisExhCtrl.InletNodeNum;
562 1 : int OutletNode = thisExhCtrl.OutletNodeNum;
563 1 : auto &thisExhInlet = state.dataLoopNodes->Node(InletNode);
564 1 : auto &thisExhOutlet = state.dataLoopNodes->Node(OutletNode);
565 : Real64 MassFlow;
566 1 : Real64 Tin = state.dataZoneTempPredictorCorrector->zoneHeatBalance(thisExhCtrl.ZoneNum).ZT;
567 1 : Real64 thisExhCtrlAvailScheVal = thisExhCtrl.availSched->getCurrentVal();
568 :
569 1 : if (FlowRatio >= 0.0) {
570 0 : thisExhCtrl.BalancedFlow *= FlowRatio;
571 0 : thisExhCtrl.UnbalancedFlow *= FlowRatio;
572 :
573 0 : thisExhInlet.MassFlowRate *= FlowRatio;
574 : } else {
575 : // Availability schedule:
576 1 : if (thisExhCtrlAvailScheVal <= 0.0) {
577 1 : MassFlow = 0.0;
578 1 : thisExhInlet.MassFlowRate = 0.0;
579 : } else {
580 : //
581 : }
582 :
583 1 : Real64 DesignFlowRate = thisExhCtrl.DesignExhaustFlowRate;
584 1 : Real64 FlowFrac = 0.0;
585 1 : if (thisExhCtrl.minExhFlowFracSched != nullptr) {
586 1 : FlowFrac = thisExhCtrl.exhaustFlowFractionSched->getCurrentVal();
587 1 : if (FlowFrac < 0.0) {
588 0 : ShowWarningError(
589 0 : state, format("Exhaust Flow Fraction Schedule value is negative for Zone Exhaust Control Named: {};", thisExhCtrl.Name));
590 0 : ShowContinueError(state, "Reset value to zero and continue the simulation.");
591 0 : FlowFrac = 0.0;
592 : }
593 : }
594 :
595 1 : Real64 MinFlowFrac = 0.0;
596 1 : if (thisExhCtrl.minExhFlowFracSched != nullptr) {
597 1 : MinFlowFrac = thisExhCtrl.minExhFlowFracSched->getCurrentVal();
598 1 : if (MinFlowFrac < 0.0) {
599 0 : ShowWarningError(
600 : state,
601 0 : format("Minimum Exhaust Flow Fraction Schedule value is negative for Zone Exhaust Control Named: {};", thisExhCtrl.Name));
602 0 : ShowContinueError(state, "Reset value to zero and continue the simulation.");
603 0 : MinFlowFrac = 0.0;
604 : }
605 : }
606 :
607 1 : if (FlowFrac < MinFlowFrac) {
608 0 : FlowFrac = MinFlowFrac;
609 : }
610 :
611 1 : if (thisExhCtrlAvailScheVal > 0.0) { // available
612 0 : if (thisExhCtrl.minZoneTempLimitSched != nullptr) {
613 0 : if (Tin >= thisExhCtrl.minZoneTempLimitSched->getCurrentVal()) {
614 : } else {
615 0 : FlowFrac = MinFlowFrac;
616 : }
617 : } else {
618 : // flow not changed
619 : }
620 : } else {
621 1 : FlowFrac = 0.0; // directly set flow rate to zero.
622 : }
623 :
624 1 : if (thisExhCtrl.FlowControlOption == ZoneExhaustControl::FlowControlType::FollowSupply) { // follow-supply
625 0 : Real64 supplyFlowRate = 0.0;
626 0 : int numOfSuppNodes = thisExhCtrl.SuppNodeNums.size();
627 0 : for (int i = 1; i <= numOfSuppNodes; ++i) {
628 0 : supplyFlowRate += state.dataLoopNodes->Node(thisExhCtrl.SuppNodeNums(i)).MassFlowRate;
629 : }
630 0 : MassFlow = supplyFlowRate * FlowFrac;
631 : } else { // Scheduled or Invalid
632 1 : MassFlow = DesignFlowRate * FlowFrac;
633 : }
634 :
635 1 : if (thisExhCtrl.balancedExhFracSched != nullptr) {
636 0 : thisExhCtrl.BalancedFlow = // state.dataHVACGlobal->BalancedExhMassFlow =
637 0 : MassFlow * // state.dataHVACGlobal->UnbalExhMassFlow *
638 0 : thisExhCtrl.balancedExhFracSched->getCurrentVal();
639 0 : thisExhCtrl.UnbalancedFlow = // state.dataHVACGlobal->UnbalExhMassFlow =
640 0 : MassFlow - // = state.dataHVACGlobal->UnbalExhMassFlow -
641 0 : thisExhCtrl.BalancedFlow; // state.dataHVACGlobal->BalancedExhMassFlow;
642 : } else {
643 1 : thisExhCtrl.BalancedFlow = 0.0;
644 1 : thisExhCtrl.UnbalancedFlow = MassFlow;
645 : }
646 :
647 1 : thisExhInlet.MassFlowRate = MassFlow;
648 : }
649 :
650 1 : thisExhOutlet.MassFlowRate = thisExhInlet.MassFlowRate;
651 :
652 1 : thisExhOutlet.Temp = thisExhInlet.Temp;
653 1 : thisExhOutlet.HumRat = thisExhInlet.HumRat;
654 1 : thisExhOutlet.Enthalpy = thisExhInlet.Enthalpy;
655 : // Set the outlet nodes for properties that just pass through & not used
656 1 : thisExhOutlet.Quality = thisExhInlet.Quality;
657 1 : thisExhOutlet.Press = thisExhInlet.Press;
658 :
659 : // Set the Node Flow Control Variables from the Fan Control Variables
660 1 : thisExhOutlet.MassFlowRateMax = thisExhInlet.MassFlowRateMax;
661 1 : thisExhOutlet.MassFlowRateMaxAvail = thisExhInlet.MassFlowRateMaxAvail;
662 1 : thisExhOutlet.MassFlowRateMinAvail = thisExhInlet.MassFlowRateMinAvail;
663 :
664 : // these might also be useful to pass through
665 1 : if (state.dataContaminantBalance->Contaminant.CO2Simulation) {
666 0 : thisExhOutlet.CO2 = thisExhInlet.CO2;
667 : }
668 :
669 1 : if (state.dataContaminantBalance->Contaminant.GenericContamSimulation) {
670 0 : thisExhOutlet.GenContam = thisExhInlet.GenContam;
671 : }
672 1 : }
673 :
674 4 : void SizeExhaustSystem(EnergyPlusData &state, int const exhSysNum)
675 : {
676 4 : auto &thisExhSys = state.dataZoneEquip->ExhaustAirSystem(exhSysNum);
677 :
678 4 : if (!thisExhSys.SizingFlag) {
679 0 : return;
680 : }
681 :
682 : // mixer outlet sizing:
683 4 : Real64 outletFlowMaxAvail = 0.0;
684 12 : for (int i = 1; i <= state.dataMixerComponent->MixerCond(thisExhSys.ZoneMixerIndex).NumInletNodes; ++i) {
685 8 : int inletNode_index = state.dataMixerComponent->MixerCond(thisExhSys.ZoneMixerIndex).InletNode(i);
686 8 : outletFlowMaxAvail += state.dataLoopNodes->Node(inletNode_index).MassFlowRateMaxAvail;
687 : }
688 :
689 : // mixer outlet considered OutletMassFlowRateMaxAvail?
690 4 : int outletNode_index = state.dataMixerComponent->MixerCond(thisExhSys.ZoneMixerIndex).OutletNode;
691 4 : state.dataLoopNodes->Node(outletNode_index).MassFlowRateMaxAvail = outletFlowMaxAvail;
692 :
693 4 : auto *fan = state.dataFans->fans(thisExhSys.CentralFanIndex);
694 : // then central exhasut fan sizing here:
695 4 : if (thisExhSys.centralFanType == HVAC::FanType::SystemModel) {
696 4 : if (fan->maxAirFlowRate == DataSizing::AutoSize) {
697 0 : fan->maxAirFlowRate = outletFlowMaxAvail / state.dataEnvrn->StdRhoAir;
698 : }
699 4 : BaseSizer::reportSizerOutput(state, "FAN:SYSTEMMODEL", fan->Name, "Design Fan Airflow [m3/s]", fan->maxAirFlowRate);
700 0 : } else if (thisExhSys.centralFanType == HVAC::FanType::ComponentModel) {
701 0 : if (fan->maxAirMassFlowRate == DataSizing::AutoSize) {
702 0 : fan->maxAirMassFlowRate = outletFlowMaxAvail * dynamic_cast<Fans::FanComponent *>(fan)->sizingFactor;
703 : }
704 0 : BaseSizer::reportSizerOutput(state,
705 0 : HVAC::fanTypeNames[(int)fan->type],
706 : fan->Name,
707 : "Design Fan Airflow [m3/s]",
708 0 : fan->maxAirMassFlowRate / state.dataEnvrn->StdRhoAir);
709 : } else {
710 : //
711 : }
712 :
713 : // after evertyhing sized, set the sizing flag to be false
714 4 : thisExhSys.SizingFlag = false;
715 : }
716 :
717 2 : void SizeExhaustControlFlow(EnergyPlusData &state, int const zoneExhCtrlNum, Array1D_int &NodeNums)
718 : {
719 2 : auto &thisExhCtrl = state.dataZoneEquip->ZoneExhaustControlSystem(zoneExhCtrlNum);
720 :
721 2 : Real64 designFlow = 0.0;
722 :
723 2 : if (thisExhCtrl.FlowControlOption == ZoneExhaustControl::FlowControlType::FollowSupply) { // FollowSupply
724 : // size based on supply nodelist flow
725 0 : for (size_t i = 1; i <= NodeNums.size(); ++i) {
726 0 : designFlow += state.dataLoopNodes->Node(NodeNums(i)).MassFlowRateMax;
727 : }
728 : } else { // scheduled etc.
729 : // based on zone OA.
730 2 : designFlow = state.dataSize->FinalZoneSizing(thisExhCtrl.ZoneNum).MinOA;
731 : }
732 :
733 2 : thisExhCtrl.DesignExhaustFlowRate = designFlow;
734 2 : }
735 :
736 424465 : void UpdateZoneExhaustControl(EnergyPlusData &state)
737 : {
738 424465 : for (int i = 1; i <= state.dataZoneEquip->NumZoneExhaustControls; ++i) {
739 0 : int controlledZoneNum = state.dataZoneEquip->ZoneExhaustControlSystem(i).ControlledZoneNum;
740 0 : state.dataZoneEquip->ZoneEquipConfig(controlledZoneNum).ZoneExh +=
741 0 : state.dataZoneEquip->ZoneExhaustControlSystem(i).BalancedFlow + state.dataZoneEquip->ZoneExhaustControlSystem(i).UnbalancedFlow;
742 0 : state.dataZoneEquip->ZoneEquipConfig(controlledZoneNum).ZoneExhBalanced += state.dataZoneEquip->ZoneExhaustControlSystem(i).BalancedFlow;
743 : }
744 424465 : }
745 :
746 2 : void CheckForSupplyNode(EnergyPlusData &state, int const ExhCtrlNum, bool &NodeNotFound)
747 : {
748 : // Trying to check a node to see if it is truely a supply node
749 : // for a nodelist, need a call loop to check each node in the list
750 :
751 2 : auto &thisExhCtrl = state.dataZoneEquip->ZoneExhaustControlSystem(ExhCtrlNum);
752 :
753 : // check a node is a zone inlet node.
754 2 : std::string_view constexpr RoutineName = "GetExhaustControlInput: ";
755 2 : std::string_view constexpr CurrentModuleObject = "ZoneHVAC:ExhaustControl";
756 :
757 2 : bool ZoneNodeNotFound = true;
758 2 : bool ErrorsFound = false;
759 4 : for (size_t i = 1; i <= thisExhCtrl.SuppNodeNums.size(); ++i) {
760 2 : int supplyNodeNum = thisExhCtrl.SuppNodeNums(i);
761 3 : for (int NodeNum = 1; NodeNum <= state.dataZoneEquip->ZoneEquipConfig(thisExhCtrl.ZoneNum).NumInletNodes; ++NodeNum) {
762 2 : if (supplyNodeNum == state.dataZoneEquip->ZoneEquipConfig(thisExhCtrl.ZoneNum).InletNode(NodeNum)) {
763 1 : ZoneNodeNotFound = false;
764 1 : break;
765 : }
766 : }
767 2 : if (ZoneNodeNotFound) {
768 1 : ShowSevereError(state, format("{}{}={}", RoutineName, CurrentModuleObject, thisExhCtrl.Name));
769 2 : ShowContinueError(
770 : state,
771 2 : format("Supply or supply list = \"{}\" contains at least one node that is not a zone inlet node for Zone Name = \"{}\"",
772 1 : thisExhCtrl.SupplyNodeOrNodelistName,
773 1 : thisExhCtrl.ZoneName));
774 2 : ShowContinueError(state, "..Nodes in the supply node or nodelist must be a zone inlet node.");
775 1 : ErrorsFound = true;
776 : }
777 : }
778 :
779 2 : NodeNotFound = ErrorsFound;
780 2 : }
781 :
782 0 : bool ExhaustSystemHasMixer(EnergyPlusData &state, std::string_view CompName) // component (mixer) name
783 : {
784 : // Given a mixer name, this routine determines if that mixer is found on Exhaust Systems.
785 :
786 0 : if (state.dataExhAirSystemMrg->GetInputFlag) {
787 0 : GetExhaustAirSystemInput(state);
788 0 : state.dataExhAirSystemMrg->GetInputFlag = false;
789 : }
790 :
791 : return // ( state.dataZoneEquip->NumExhaustAirSystems > 0) &&
792 0 : (Util::FindItemInList(CompName, state.dataZoneEquip->ExhaustAirSystem, &ExhaustAir::ZoneMixerName) > 0);
793 : }
794 :
795 : } // namespace ExhaustAirSystemManager
796 :
797 : } // namespace EnergyPlus
|