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/Data/EnergyPlusData.hh>
60 : #include <EnergyPlus/DataContaminantBalance.hh>
61 : #include <EnergyPlus/DataConvergParams.hh>
62 : #include <EnergyPlus/DataDefineEquip.hh>
63 : #include <EnergyPlus/DataEnvironment.hh>
64 : #include <EnergyPlus/DataHVACGlobals.hh>
65 : #include <EnergyPlus/DataHeatBalFanSys.hh>
66 : #include <EnergyPlus/DataHeatBalance.hh>
67 : #include <EnergyPlus/DataLoopNode.hh>
68 : #include <EnergyPlus/DataSizing.hh>
69 : #include <EnergyPlus/DataZoneEnergyDemands.hh>
70 : #include <EnergyPlus/DataZoneEquipment.hh>
71 : #include <EnergyPlus/DualDuct.hh>
72 : #include <EnergyPlus/GeneralRoutines.hh>
73 : #include <EnergyPlus/GlobalNames.hh>
74 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
75 : #include <EnergyPlus/NodeInputManager.hh>
76 : #include <EnergyPlus/OutputProcessor.hh>
77 : #include <EnergyPlus/OutputReportPredefined.hh>
78 : #include <EnergyPlus/Psychrometrics.hh>
79 : #include <EnergyPlus/ScheduleManager.hh>
80 : #include <EnergyPlus/UtilityRoutines.hh>
81 :
82 : namespace EnergyPlus {
83 :
84 : namespace DualDuct {
85 : // Module containing the DualDuct simulation routines
86 :
87 : // MODULE INFORMATION:
88 : // AUTHOR Richard J. Liesen
89 : // DATE WRITTEN February 2000
90 : // MODIFIED Clayton Miller, Brent Griffith Aug. 2010 - Added DualDuctOA Terminal Unit to Simulate Decoupled OA/RA
91 : // RE-ENGINEERED na
92 :
93 : // PURPOSE OF THIS MODULE:
94 : // To encapsulate the data and algorithms required to
95 : // manage the DualDuct Systems Simulation
96 :
97 : constexpr std::string_view cCMO_DDConstantVolume = "AirTerminal:DualDuct:ConstantVolume";
98 : constexpr std::string_view cCMO_DDVariableVolume = "AirTerminal:DualDuct:VAV";
99 : constexpr std::string_view cCMO_DDVarVolOA = "AirTerminal:DualDuct:VAV:OutdoorAir";
100 : constexpr Real64 DualDuctMassFlowSetToler = DataConvergParams::HVACFlowRateToler * 0.00001;
101 : constexpr std::array<std::string_view, static_cast<int>(PerPersonMode::Num)> modeStrings = {"NOTSET", "CURRENTOCCUPANCY", "DESIGNOCCUPANCY"};
102 : constexpr std::array<std::string_view, static_cast<int>(DualDuctDamper::Num)> damperTypeStrings = {"ConstantVolume", "VAV", "VAV:OutdoorAir"};
103 : constexpr std::array<std::string_view, static_cast<int>(DualDuctDamper::Num)> cmoNameArray = {
104 : cCMO_DDConstantVolume, cCMO_DDVariableVolume, cCMO_DDVarVolOA};
105 :
106 0 : void SimulateDualDuct(
107 : EnergyPlusData &state, std::string_view CompName, bool const FirstHVACIteration, int const ZoneNum, int const ZoneNodeNum, int &CompIndex)
108 : {
109 :
110 : // SUBROUTINE INFORMATION:
111 : // AUTHOR Richard Liesen
112 : // DATE WRITTEN February 2000
113 : // MODIFIED na
114 : // RE-ENGINEERED na
115 :
116 : // PURPOSE OF THIS SUBROUTINE:
117 : // This subroutine manages Damper component simulation.
118 : // It is called from the SimAirLoopComponent
119 : // at the system time step.
120 :
121 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
122 : int DDNum; // The Damper that you are currently loading input into
123 :
124 : // Obtains and Allocates Damper related parameters from input file
125 0 : if (state.dataDualDuct->GetDualDuctInputFlag) { // First time subroutine has been entered
126 0 : GetDualDuctInput(state);
127 0 : state.dataDualDuct->GetDualDuctInputFlag = false;
128 : }
129 :
130 : // Find the correct DDNumber with the AirLoop & CompNum from AirLoop Derived Type
131 0 : if (CompIndex == 0) {
132 0 : DDNum = Util::FindItemInList(CompName, state.dataDualDuct->dd_airterminal, &DualDuctAirTerminal::Name);
133 0 : if (DDNum == 0) {
134 0 : ShowFatalError(state, format("SimulateDualDuct: Damper not found={}", CompName));
135 : }
136 0 : CompIndex = DDNum;
137 : } else {
138 0 : DDNum = CompIndex;
139 0 : if (DDNum > state.dataDualDuct->NumDDAirTerminal || DDNum < 1) {
140 0 : ShowFatalError(state,
141 0 : format("SimulateDualDuct: Invalid CompIndex passed={}, Number of Dampers={}, Damper name={}",
142 : CompIndex,
143 0 : state.dataDualDuct->NumDDAirTerminal,
144 : CompName));
145 : }
146 0 : if (state.dataDualDuct->dd_airterminal(DDNum).CheckEquipName) {
147 0 : if (CompName != state.dataDualDuct->dd_airterminal(DDNum).Name) {
148 0 : ShowFatalError(state,
149 0 : format("SimulateDualDuct: Invalid CompIndex passed={}, Damper name={}, stored Damper Name for that index={}",
150 : CompIndex,
151 : CompName,
152 0 : state.dataDualDuct->dd_airterminal(DDNum).Name));
153 : }
154 0 : state.dataDualDuct->dd_airterminal(DDNum).CheckEquipName = false;
155 : }
156 : }
157 :
158 0 : auto &thisDualDuct(state.dataDualDuct->dd_airterminal(DDNum));
159 :
160 0 : if (CompIndex > 0) {
161 0 : state.dataSize->CurTermUnitSizingNum = state.dataDefineEquipment->AirDistUnit(thisDualDuct.ADUNum).TermUnitSizingNum;
162 : // With the correct DDNum Initialize
163 0 : thisDualDuct.InitDualDuct(state, FirstHVACIteration); // Initialize all Damper related parameters
164 :
165 : // Calculate the Correct Damper Model with the current DDNum
166 0 : switch (thisDualDuct.DamperType) {
167 0 : case DualDuctDamper::ConstantVolume: { // 'AirTerminal:DualDuct:ConstantVolume'
168 0 : thisDualDuct.SimDualDuctConstVol(state, ZoneNum, ZoneNodeNum);
169 0 : } break;
170 0 : case DualDuctDamper::VariableVolume: { // 'AirTerminal:DualDuct:VAV'
171 0 : thisDualDuct.SimDualDuctVarVol(state, ZoneNum, ZoneNodeNum);
172 0 : } break;
173 0 : case DualDuctDamper::OutdoorAir: {
174 0 : thisDualDuct.SimDualDuctVAVOutdoorAir(state, ZoneNum, ZoneNodeNum); // 'AirTerminal:DualDuct:VAV:OutdoorAir'
175 0 : } break;
176 0 : default:
177 0 : break;
178 : }
179 :
180 : // Update the current Damper to the outlet nodes
181 0 : thisDualDuct.UpdateDualDuct(state);
182 : } else {
183 0 : ShowFatalError(state, format("SimulateDualDuct: Damper not found={}", CompName));
184 : }
185 0 : }
186 :
187 2 : void GetDualDuctInput(EnergyPlusData &state)
188 : {
189 :
190 : // SUBROUTINE INFORMATION:
191 : // AUTHOR Richard Liesen
192 : // DATE WRITTEN April 1998
193 : // MODIFIED Julien Marrec of EffiBEM, 2017-12-18
194 : // RE-ENGINEERED na
195 :
196 : // PURPOSE OF THIS SUBROUTINE:
197 : // This subroutine is the main routine to call other input routines and Get routines
198 :
199 : // METHODOLOGY EMPLOYED:
200 : // Uses the status flags to trigger events.
201 :
202 : // SUBROUTINE PARAMETER DEFINITIONS:
203 : static constexpr std::string_view RoutineName("GetDualDuctInput: "); // include trailing bla
204 : static constexpr std::string_view routineName = "GetDualDuctInput";
205 :
206 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
207 : int NumAlphas;
208 : int NumNums;
209 : int IOStat;
210 2 : Array1D<Real64> NumArray(2, 0.0);
211 2 : Array1D_string AlphArray(7);
212 2 : Array1D_string cAlphaFields(7); // Alpha field names
213 2 : Array1D_string cNumericFields(2); // Numeric field names
214 2 : Array1D_bool lAlphaBlanks(7, true); // Logical array, alpha field input BLANK = .TRUE.
215 2 : Array1D_bool lNumericBlanks(2, true); // Logical array, numeric field input BLANK = .TRUE.
216 2 : std::string CurrentModuleObject; // for ease in getting objects
217 2 : bool ErrorsFound(false); // If errors detected in input
218 : int SupAirIn; // controlled zone supply air inlet index
219 : int ADUNum; // loop control to search Air Distribution Units
220 2 : Real64 DummyOAFlow(0.0);
221 :
222 2 : int NumDualDuctConstVolDampers = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, cCMO_DDConstantVolume);
223 2 : int NumDualDuctVarVolDampers = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, cCMO_DDVariableVolume);
224 2 : state.dataDualDuct->NumDualDuctVarVolOA = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, cCMO_DDVarVolOA);
225 2 : state.dataDualDuct->NumDDAirTerminal = NumDualDuctConstVolDampers + NumDualDuctVarVolDampers + state.dataDualDuct->NumDualDuctVarVolOA;
226 2 : state.dataDualDuct->dd_airterminal.allocate(state.dataDualDuct->NumDDAirTerminal);
227 2 : state.dataDualDuct->UniqueDualDuctAirTerminalNames.reserve(state.dataDualDuct->NumDDAirTerminal);
228 :
229 2 : if (NumDualDuctConstVolDampers > 0) {
230 0 : CurrentModuleObject = cCMO_DDConstantVolume;
231 0 : for (int DamperIndex = 1; DamperIndex <= NumDualDuctConstVolDampers; ++DamperIndex) {
232 :
233 : // Load the info from the damper
234 :
235 0 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
236 : CurrentModuleObject,
237 : DamperIndex,
238 : AlphArray,
239 : NumAlphas,
240 : NumArray,
241 : NumNums,
242 : IOStat,
243 : lNumericBlanks,
244 : lAlphaBlanks,
245 : cAlphaFields,
246 : cNumericFields);
247 :
248 0 : ErrorObjectHeader eoh{routineName, CurrentModuleObject, AlphArray(1)};
249 :
250 : // Anything below this line in this control block should use DDNum
251 0 : int DDNum = DamperIndex;
252 0 : auto &thisDD = state.dataDualDuct->dd_airterminal(DDNum);
253 0 : GlobalNames::VerifyUniqueInterObjectName(
254 0 : state, state.dataDualDuct->UniqueDualDuctAirTerminalNames, AlphArray(1), CurrentModuleObject, cAlphaFields(1), ErrorsFound);
255 0 : thisDD.Name = AlphArray(1);
256 0 : thisDD.DamperType = DualDuctDamper::ConstantVolume;
257 0 : if (lAlphaBlanks(2)) {
258 0 : thisDD.availSched = Sched::GetScheduleAlwaysOn(state);
259 0 : } else if ((thisDD.availSched = Sched::GetSchedule(state, AlphArray(2))) == nullptr) {
260 0 : ShowSevereItemNotFound(state, eoh, cAlphaFields(2), AlphArray(2));
261 0 : ErrorsFound = true;
262 : }
263 0 : thisDD.OutletNodeNum = GetOnlySingleNode(state,
264 0 : AlphArray(3),
265 : ErrorsFound,
266 : DataLoopNode::ConnectionObjectType::AirTerminalDualDuctConstantVolume,
267 0 : thisDD.Name,
268 : DataLoopNode::NodeFluidType::Air,
269 : DataLoopNode::ConnectionType::Outlet,
270 : NodeInputManager::CompFluidStream::Primary,
271 : DataLoopNode::ObjectIsNotParent,
272 0 : cAlphaFields(3));
273 0 : thisDD.HotAirInletNodeNum = GetOnlySingleNode(state,
274 0 : AlphArray(4),
275 : ErrorsFound,
276 : DataLoopNode::ConnectionObjectType::AirTerminalDualDuctConstantVolume,
277 0 : thisDD.Name,
278 : DataLoopNode::NodeFluidType::Air,
279 : DataLoopNode::ConnectionType::Inlet,
280 : NodeInputManager::CompFluidStream::Primary,
281 : DataLoopNode::ObjectIsNotParent,
282 0 : cAlphaFields(4));
283 0 : thisDD.ColdAirInletNodeNum = GetOnlySingleNode(state,
284 0 : AlphArray(5),
285 : ErrorsFound,
286 : DataLoopNode::ConnectionObjectType::AirTerminalDualDuctConstantVolume,
287 0 : thisDD.Name,
288 : DataLoopNode::NodeFluidType::Air,
289 : DataLoopNode::ConnectionType::Inlet,
290 : NodeInputManager::CompFluidStream::Primary,
291 : DataLoopNode::ObjectIsNotParent,
292 0 : cAlphaFields(5));
293 :
294 0 : thisDD.MaxAirVolFlowRate = NumArray(1);
295 0 : thisDD.ZoneMinAirFracDes = 0.0;
296 :
297 : // Register component set data - one for heat and one for cool
298 0 : BranchNodeConnections::TestCompSet(state, CurrentModuleObject + ":HEAT", thisDD.Name, AlphArray(4), AlphArray(3), "Air Nodes");
299 0 : BranchNodeConnections::TestCompSet(state, CurrentModuleObject + ":COOL", thisDD.Name, AlphArray(5), AlphArray(3), "Air Nodes");
300 :
301 0 : for (ADUNum = 1; ADUNum <= (int)state.dataDefineEquipment->AirDistUnit.size(); ++ADUNum) {
302 0 : if (thisDD.OutletNodeNum == state.dataDefineEquipment->AirDistUnit(ADUNum).OutletNodeNum) {
303 0 : state.dataDefineEquipment->AirDistUnit(ADUNum).InletNodeNum = thisDD.ColdAirInletNodeNum;
304 0 : state.dataDefineEquipment->AirDistUnit(ADUNum).InletNodeNum2 = thisDD.HotAirInletNodeNum;
305 0 : thisDD.ADUNum = ADUNum;
306 : }
307 : }
308 : // one assumes if there isn't one assigned, it's an error?
309 0 : if (thisDD.ADUNum == 0) {
310 0 : auto &thisObjType = damperTypeStrings[static_cast<int>(thisDD.DamperType)];
311 0 : ShowSevereError(
312 : state,
313 0 : format("{}No matching List:Zone:AirTerminal for AirTerminal:DualDuct = [{},{}].", RoutineName, thisObjType, thisDD.Name));
314 0 : ShowContinueError(state, format("...should have outlet node={}", state.dataLoopNodes->NodeID(thisDD.OutletNodeNum)));
315 0 : ErrorsFound = true;
316 : } else {
317 :
318 : // Fill the Zone Equipment data with the inlet node numbers of this unit.
319 0 : for (int CtrlZone = 1; CtrlZone <= state.dataGlobal->NumOfZones; ++CtrlZone) {
320 0 : auto &thisZoneEquipConfig = state.dataZoneEquip->ZoneEquipConfig(CtrlZone);
321 0 : if (!state.dataZoneEquip->ZoneEquipConfig(CtrlZone).IsControlled) continue;
322 0 : for (SupAirIn = 1; SupAirIn <= thisZoneEquipConfig.NumInletNodes; ++SupAirIn) {
323 0 : if (thisDD.OutletNodeNum == thisZoneEquipConfig.InletNode(SupAirIn)) {
324 0 : if (state.dataZoneEquip->ZoneEquipConfig(CtrlZone).AirDistUnitCool(SupAirIn).OutNode > 0) {
325 0 : ShowSevereError(state, "Error in connecting a terminal unit to a zone");
326 0 : ShowContinueError(
327 0 : state, format("{} already connects to another zone", state.dataLoopNodes->NodeID(thisDD.OutletNodeNum)));
328 0 : ShowContinueError(state, format("Occurs for terminal unit {} = {}", CurrentModuleObject, thisDD.Name));
329 0 : ShowContinueError(state, "Check terminal unit node names for errors");
330 0 : ErrorsFound = true;
331 : } else {
332 0 : thisZoneEquipConfig.AirDistUnitCool(SupAirIn).InNode = thisDD.ColdAirInletNodeNum;
333 0 : thisZoneEquipConfig.AirDistUnitHeat(SupAirIn).InNode = thisDD.HotAirInletNodeNum;
334 0 : thisZoneEquipConfig.AirDistUnitCool(SupAirIn).OutNode = thisDD.OutletNodeNum;
335 0 : thisZoneEquipConfig.AirDistUnitHeat(SupAirIn).OutNode = thisDD.OutletNodeNum;
336 0 : state.dataDefineEquipment->AirDistUnit(thisDD.ADUNum).TermUnitSizingNum =
337 0 : thisZoneEquipConfig.AirDistUnitCool(SupAirIn).TermUnitSizingIndex;
338 0 : state.dataDefineEquipment->AirDistUnit(thisDD.ADUNum).ZoneEqNum = CtrlZone;
339 : }
340 0 : thisDD.CtrlZoneNum = CtrlZone;
341 0 : thisDD.CtrlZoneInNodeIndex = SupAirIn;
342 : }
343 : }
344 : }
345 : }
346 : // Setup the Average damper Position output variable
347 : // CurrentModuleObject='AirTerminal:DualDuct:ConstantVolume'
348 0 : SetupOutputVariable(state,
349 : "Zone Air Terminal Cold Supply Duct Damper Position",
350 : Constant::Units::None,
351 0 : thisDD.ColdAirDamperPosition,
352 : OutputProcessor::TimeStepType::System,
353 : OutputProcessor::StoreType::Average,
354 0 : thisDD.Name);
355 0 : SetupOutputVariable(state,
356 : "Zone Air Terminal Hot Supply Duct Damper Position",
357 : Constant::Units::None,
358 0 : thisDD.HotAirDamperPosition,
359 : OutputProcessor::TimeStepType::System,
360 : OutputProcessor::StoreType::Average,
361 0 : thisDD.Name);
362 :
363 : } // end Number of Damper Loop
364 : }
365 :
366 2 : if (NumDualDuctVarVolDampers > 0) {
367 2 : CurrentModuleObject = cCMO_DDVariableVolume;
368 4 : for (int DamperIndex = 1; DamperIndex <= NumDualDuctVarVolDampers; ++DamperIndex) {
369 :
370 : // Load the info from the damper
371 :
372 2 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
373 : CurrentModuleObject,
374 : DamperIndex,
375 : AlphArray,
376 : NumAlphas,
377 : NumArray,
378 : NumNums,
379 : IOStat,
380 : lNumericBlanks,
381 : lAlphaBlanks,
382 : cAlphaFields,
383 : cNumericFields);
384 :
385 2 : ErrorObjectHeader eoh{routineName, CurrentModuleObject, AlphArray(1)};
386 :
387 : // Anything below this line in this control block should use DDNum
388 2 : int DDNum = DamperIndex + NumDualDuctConstVolDampers;
389 2 : auto &thisDD = state.dataDualDuct->dd_airterminal(DDNum);
390 2 : GlobalNames::VerifyUniqueInterObjectName(
391 4 : state, state.dataDualDuct->UniqueDualDuctAirTerminalNames, AlphArray(1), CurrentModuleObject, cAlphaFields(1), ErrorsFound);
392 2 : thisDD.Name = AlphArray(1);
393 2 : thisDD.DamperType = DualDuctDamper::VariableVolume;
394 2 : if (lAlphaBlanks(2)) {
395 2 : thisDD.availSched = Sched::GetScheduleAlwaysOn(state);
396 0 : } else if ((thisDD.availSched = Sched::GetSchedule(state, AlphArray(2))) == nullptr) {
397 0 : ShowSevereItemNotFound(state, eoh, cAlphaFields(2), AlphArray(2));
398 0 : ErrorsFound = true;
399 : }
400 6 : thisDD.OutletNodeNum = GetOnlySingleNode(state,
401 2 : AlphArray(3),
402 : ErrorsFound,
403 : DataLoopNode::ConnectionObjectType::AirTerminalDualDuctVAV,
404 2 : thisDD.Name,
405 : DataLoopNode::NodeFluidType::Air,
406 : DataLoopNode::ConnectionType::Outlet,
407 : NodeInputManager::CompFluidStream::Primary,
408 : DataLoopNode::ObjectIsNotParent,
409 2 : cAlphaFields(3));
410 6 : thisDD.HotAirInletNodeNum = GetOnlySingleNode(state,
411 2 : AlphArray(4),
412 : ErrorsFound,
413 : DataLoopNode::ConnectionObjectType::AirTerminalDualDuctVAV,
414 2 : thisDD.Name,
415 : DataLoopNode::NodeFluidType::Air,
416 : DataLoopNode::ConnectionType::Inlet,
417 : NodeInputManager::CompFluidStream::Primary,
418 : DataLoopNode::ObjectIsNotParent,
419 2 : cAlphaFields(4));
420 6 : thisDD.ColdAirInletNodeNum = GetOnlySingleNode(state,
421 2 : AlphArray(5),
422 : ErrorsFound,
423 : DataLoopNode::ConnectionObjectType::AirTerminalDualDuctVAV,
424 2 : thisDD.Name,
425 : DataLoopNode::NodeFluidType::Air,
426 : DataLoopNode::ConnectionType::Inlet,
427 : NodeInputManager::CompFluidStream::Primary,
428 : DataLoopNode::ObjectIsNotParent,
429 2 : cAlphaFields(5));
430 :
431 2 : thisDD.MaxAirVolFlowRate = NumArray(1);
432 2 : thisDD.ZoneMinAirFracDes = NumArray(2);
433 :
434 : // Register component set data - one for heat and one for cool
435 4 : BranchNodeConnections::TestCompSet(state, CurrentModuleObject + ":HEAT", thisDD.Name, AlphArray(4), AlphArray(3), "Air Nodes");
436 2 : BranchNodeConnections::TestCompSet(state, CurrentModuleObject + ":COOL", thisDD.Name, AlphArray(5), AlphArray(3), "Air Nodes");
437 :
438 4 : for (ADUNum = 1; ADUNum <= (int)state.dataDefineEquipment->AirDistUnit.size(); ++ADUNum) {
439 2 : if (thisDD.OutletNodeNum == state.dataDefineEquipment->AirDistUnit(ADUNum).OutletNodeNum) {
440 2 : state.dataDefineEquipment->AirDistUnit(ADUNum).InletNodeNum = thisDD.ColdAirInletNodeNum;
441 2 : state.dataDefineEquipment->AirDistUnit(ADUNum).InletNodeNum2 = thisDD.HotAirInletNodeNum;
442 2 : thisDD.ADUNum = ADUNum;
443 : }
444 : }
445 : // one assumes if there isn't one assigned, it's an error?
446 2 : if (thisDD.ADUNum == 0) {
447 0 : auto &thisObjType = damperTypeStrings[static_cast<int>(thisDD.DamperType)];
448 0 : ShowSevereError(
449 : state,
450 0 : format("{}No matching List:Zone:AirTerminal for AirTerminal:DualDuct = [{},{}].", RoutineName, thisObjType, thisDD.Name));
451 0 : ShowContinueError(state, format("...should have outlet node={}", state.dataLoopNodes->NodeID(thisDD.OutletNodeNum)));
452 0 : ErrorsFound = true;
453 : } else {
454 :
455 : // Fill the Zone Equipment data with the inlet node numbers of this unit.
456 3 : for (int CtrlZone = 1; CtrlZone <= state.dataGlobal->NumOfZones; ++CtrlZone) {
457 1 : auto &thisZoneEquipConfig = state.dataZoneEquip->ZoneEquipConfig(CtrlZone);
458 1 : if (!state.dataZoneEquip->ZoneEquipConfig(CtrlZone).IsControlled) continue;
459 2 : for (SupAirIn = 1; SupAirIn <= thisZoneEquipConfig.NumInletNodes; ++SupAirIn) {
460 1 : if (thisDD.OutletNodeNum == thisZoneEquipConfig.InletNode(SupAirIn)) {
461 1 : thisZoneEquipConfig.AirDistUnitCool(SupAirIn).InNode = thisDD.ColdAirInletNodeNum;
462 1 : thisZoneEquipConfig.AirDistUnitHeat(SupAirIn).InNode = thisDD.HotAirInletNodeNum;
463 1 : thisZoneEquipConfig.AirDistUnitCool(SupAirIn).OutNode = thisDD.OutletNodeNum;
464 1 : thisZoneEquipConfig.AirDistUnitHeat(SupAirIn).OutNode = thisDD.OutletNodeNum;
465 1 : state.dataDefineEquipment->AirDistUnit(thisDD.ADUNum).TermUnitSizingNum =
466 1 : thisZoneEquipConfig.AirDistUnitCool(SupAirIn).TermUnitSizingIndex;
467 1 : state.dataDefineEquipment->AirDistUnit(thisDD.ADUNum).ZoneEqNum = CtrlZone;
468 :
469 1 : thisDD.CtrlZoneNum = CtrlZone;
470 1 : thisDD.CtrlZoneInNodeIndex = SupAirIn;
471 : }
472 : }
473 : }
474 : }
475 2 : if (!lAlphaBlanks(6)) {
476 0 : thisDD.OARequirementsPtr = Util::FindItemInList(AlphArray(6), state.dataSize->OARequirements);
477 0 : if (thisDD.OARequirementsPtr == 0) {
478 0 : ShowSevereError(state, format("{} = {} not found.", cAlphaFields(6), AlphArray(6)));
479 0 : ShowContinueError(state, format("Occurs in {} = {}", cCMO_DDVariableVolume, thisDD.Name));
480 0 : ErrorsFound = true;
481 : } else {
482 0 : thisDD.NoOAFlowInputFromUser = false;
483 : }
484 : }
485 :
486 2 : if (lAlphaBlanks(7)) {
487 0 : thisDD.ZoneTurndownMinAirFrac = 1.0;
488 2 : } else if ((thisDD.zoneTurndownMinAirFracSched = Sched::GetSchedule(state, AlphArray(7))) == nullptr) {
489 0 : ShowSevereItemNotFound(state, eoh, cAlphaFields(7), AlphArray(7));
490 0 : ErrorsFound = true;
491 : }
492 :
493 : // Setup the Average damper Position output variable
494 : // CurrentModuleObject='AirTerminal:DualDuct:VAV'
495 4 : SetupOutputVariable(state,
496 : "Zone Air Terminal Cold Supply Duct Damper Position",
497 : Constant::Units::None,
498 2 : thisDD.ColdAirDamperPosition,
499 : OutputProcessor::TimeStepType::System,
500 : OutputProcessor::StoreType::Average,
501 2 : thisDD.Name);
502 4 : SetupOutputVariable(state,
503 : "Zone Air Terminal Hot Supply Duct Damper Position",
504 : Constant::Units::None,
505 2 : thisDD.HotAirDamperPosition,
506 : OutputProcessor::TimeStepType::System,
507 : OutputProcessor::StoreType::Average,
508 2 : thisDD.Name);
509 4 : SetupOutputVariable(state,
510 : "Zone Air Terminal Outdoor Air Volume Flow Rate",
511 : Constant::Units::m3_s,
512 2 : thisDD.OutdoorAirFlowRate,
513 : OutputProcessor::TimeStepType::System,
514 : OutputProcessor::StoreType::Average,
515 2 : thisDD.Name);
516 : } // end Number of Damper Loop
517 : }
518 :
519 2 : if (state.dataDualDuct->NumDualDuctVarVolOA > 0) {
520 0 : CurrentModuleObject = cCMO_DDVarVolOA;
521 0 : for (int DamperIndex = 1; DamperIndex <= state.dataDualDuct->NumDualDuctVarVolOA; ++DamperIndex) {
522 :
523 : // Load the info from the damper
524 0 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
525 : CurrentModuleObject,
526 : DamperIndex,
527 : AlphArray,
528 : NumAlphas,
529 : NumArray,
530 : NumNums,
531 : IOStat,
532 : lNumericBlanks,
533 : lAlphaBlanks,
534 : cAlphaFields,
535 : cNumericFields);
536 :
537 0 : ErrorObjectHeader eoh{routineName, CurrentModuleObject, AlphArray(1)};
538 :
539 : // Anything below this line in this control block should use DDNum
540 0 : int DDNum = DamperIndex + NumDualDuctConstVolDampers + NumDualDuctVarVolDampers;
541 0 : auto &thisDD = state.dataDualDuct->dd_airterminal(DDNum);
542 0 : GlobalNames::VerifyUniqueInterObjectName(
543 0 : state, state.dataDualDuct->UniqueDualDuctAirTerminalNames, AlphArray(1), CurrentModuleObject, cAlphaFields(1), ErrorsFound);
544 0 : thisDD.Name = AlphArray(1);
545 0 : thisDD.DamperType = DualDuctDamper::OutdoorAir;
546 0 : if (lAlphaBlanks(2)) {
547 0 : thisDD.availSched = Sched::GetScheduleAlwaysOn(state);
548 0 : } else if ((thisDD.availSched = Sched::GetSchedule(state, AlphArray(2))) == nullptr) {
549 0 : ShowSevereItemNotFound(state, eoh, cAlphaFields(2), AlphArray(2));
550 0 : ErrorsFound = true;
551 : }
552 0 : thisDD.OutletNodeNum = GetOnlySingleNode(state,
553 0 : AlphArray(3),
554 : ErrorsFound,
555 : DataLoopNode::ConnectionObjectType::AirTerminalDualDuctVAVOutdoorAir,
556 0 : thisDD.Name,
557 : DataLoopNode::NodeFluidType::Air,
558 : DataLoopNode::ConnectionType::Outlet,
559 : NodeInputManager::CompFluidStream::Primary,
560 : DataLoopNode::ObjectIsNotParent,
561 0 : cAlphaFields(3));
562 0 : thisDD.OAInletNodeNum = GetOnlySingleNode(state,
563 0 : AlphArray(4),
564 : ErrorsFound,
565 : DataLoopNode::ConnectionObjectType::AirTerminalDualDuctVAVOutdoorAir,
566 0 : thisDD.Name,
567 : DataLoopNode::NodeFluidType::Air,
568 : DataLoopNode::ConnectionType::Inlet,
569 : NodeInputManager::CompFluidStream::Primary,
570 : DataLoopNode::ObjectIsNotParent,
571 0 : cAlphaFields(4));
572 :
573 0 : if (!lAlphaBlanks(5)) {
574 0 : thisDD.RecircAirInletNodeNum = GetOnlySingleNode(state,
575 0 : AlphArray(5),
576 : ErrorsFound,
577 : DataLoopNode::ConnectionObjectType::AirTerminalDualDuctVAVOutdoorAir,
578 0 : thisDD.Name,
579 : DataLoopNode::NodeFluidType::Air,
580 : DataLoopNode::ConnectionType::Inlet,
581 : NodeInputManager::CompFluidStream::Primary,
582 : DataLoopNode::ObjectIsNotParent,
583 0 : cAlphaFields(5));
584 : } else {
585 : // for this model, we intentionally allow not using the recirc side
586 0 : thisDD.RecircIsUsed = false;
587 : }
588 :
589 0 : thisDD.MaxAirVolFlowRate = NumArray(1);
590 0 : thisDD.MaxAirMassFlowRate = thisDD.MaxAirVolFlowRate * state.dataEnvrn->StdRhoAir;
591 :
592 : // Register component set data - one for OA and one for RA
593 0 : BranchNodeConnections::TestCompSet(state, CurrentModuleObject + ":OutdoorAir", thisDD.Name, AlphArray(4), AlphArray(3), "Air Nodes");
594 0 : if (thisDD.RecircIsUsed) {
595 0 : BranchNodeConnections::TestCompSet(
596 0 : state, CurrentModuleObject + ":RecirculatedAir", thisDD.Name, AlphArray(5), AlphArray(3), "Air Nodes");
597 : }
598 :
599 0 : thisDD.OAPerPersonMode = static_cast<PerPersonMode>(getEnumValue(modeStrings, AlphArray(7)));
600 0 : if (thisDD.OAPerPersonMode == PerPersonMode::Invalid) {
601 0 : thisDD.OAPerPersonMode = PerPersonMode::ModeNotSet;
602 : }
603 : // checks on this are done later
604 :
605 0 : for (ADUNum = 1; ADUNum <= (int)state.dataDefineEquipment->AirDistUnit.size(); ++ADUNum) {
606 0 : if (thisDD.OutletNodeNum == state.dataDefineEquipment->AirDistUnit(ADUNum).OutletNodeNum) {
607 0 : state.dataDefineEquipment->AirDistUnit(ADUNum).InletNodeNum = thisDD.OAInletNodeNum;
608 0 : state.dataDefineEquipment->AirDistUnit(ADUNum).InletNodeNum2 = thisDD.RecircAirInletNodeNum;
609 0 : thisDD.ADUNum = ADUNum;
610 : }
611 : }
612 : // one assumes if there isn't one assigned, it's an error?
613 0 : if (thisDD.ADUNum == 0) {
614 0 : auto &thisObjType = damperTypeStrings[static_cast<int>(thisDD.DamperType)];
615 0 : ShowSevereError(
616 : state,
617 0 : format("{}No matching List:Zone:AirTerminal for AirTerminal:DualDuct = [{},{}].", RoutineName, thisObjType, thisDD.Name));
618 0 : ShowContinueError(state, format("...should have outlet node={}", state.dataLoopNodes->NodeID(thisDD.OutletNodeNum)));
619 0 : ErrorsFound = true;
620 : } else {
621 :
622 : // Fill the Zone Equipment data with the inlet node numbers of this unit.
623 0 : for (int CtrlZone = 1; CtrlZone <= state.dataGlobal->NumOfZones; ++CtrlZone) {
624 0 : auto &thisZoneEquipConfig = state.dataZoneEquip->ZoneEquipConfig(CtrlZone);
625 0 : if (!state.dataZoneEquip->ZoneEquipConfig(CtrlZone).IsControlled) continue;
626 0 : for (SupAirIn = 1; SupAirIn <= thisZoneEquipConfig.NumInletNodes; ++SupAirIn) {
627 0 : if (thisDD.OutletNodeNum == thisZoneEquipConfig.InletNode(SupAirIn)) {
628 0 : if (thisDD.RecircIsUsed) {
629 0 : thisZoneEquipConfig.AirDistUnitCool(SupAirIn).InNode = thisDD.RecircAirInletNodeNum;
630 : } else {
631 0 : thisZoneEquipConfig.AirDistUnitCool(SupAirIn).InNode = thisDD.OAInletNodeNum;
632 : }
633 0 : thisZoneEquipConfig.AirDistUnitHeat(SupAirIn).InNode = thisDD.OAInletNodeNum;
634 0 : thisZoneEquipConfig.AirDistUnitCool(SupAirIn).OutNode = thisDD.OutletNodeNum;
635 0 : thisZoneEquipConfig.AirDistUnitHeat(SupAirIn).OutNode = thisDD.OutletNodeNum;
636 0 : state.dataDefineEquipment->AirDistUnit(thisDD.ADUNum).TermUnitSizingNum =
637 0 : thisZoneEquipConfig.AirDistUnitCool(SupAirIn).TermUnitSizingIndex;
638 0 : state.dataDefineEquipment->AirDistUnit(thisDD.ADUNum).ZoneEqNum = CtrlZone;
639 :
640 0 : thisDD.CtrlZoneNum = CtrlZone;
641 0 : thisDD.CtrlZoneInNodeIndex = SupAirIn;
642 : }
643 : }
644 : }
645 : }
646 0 : thisDD.OARequirementsPtr = Util::FindItemInList(AlphArray(6), state.dataSize->OARequirements);
647 0 : if (thisDD.OARequirementsPtr == 0) {
648 0 : ShowSevereError(state, format("{} = {} not found.", cAlphaFields(6), AlphArray(6)));
649 0 : ShowContinueError(state, format("Occurs in {} = {}", cCMO_DDVarVolOA, thisDD.Name));
650 0 : ErrorsFound = true;
651 : } else {
652 0 : thisDD.NoOAFlowInputFromUser = false;
653 :
654 : // now fill design OA rate
655 0 : thisDD.CalcOAOnlyMassFlow(state, DummyOAFlow, thisDD.DesignOAFlowRate);
656 :
657 0 : if (thisDD.MaxAirVolFlowRate != DataSizing::AutoSize) {
658 0 : BaseSizer::reportSizerOutput(
659 : state, CurrentModuleObject, thisDD.Name, "Maximum Outdoor Air Flow Rate [m3/s]", thisDD.DesignOAFlowRate);
660 :
661 0 : if (thisDD.RecircIsUsed) {
662 0 : thisDD.DesignRecircFlowRate = thisDD.MaxAirVolFlowRate - thisDD.DesignOAFlowRate;
663 0 : thisDD.DesignRecircFlowRate = max(0.0, thisDD.DesignRecircFlowRate);
664 0 : BaseSizer::reportSizerOutput(
665 : state, CurrentModuleObject, thisDD.Name, "Maximum Recirculated Air Flow Rate [m3/s]", thisDD.DesignRecircFlowRate);
666 : } else {
667 0 : if (thisDD.MaxAirVolFlowRate < thisDD.DesignOAFlowRate) {
668 0 : ShowSevereError(state,
669 0 : format("The value {:.5R} in {}is lower than the outdoor air requirement.",
670 0 : thisDD.MaxAirVolFlowRate,
671 : cNumericFields(1)));
672 0 : ShowContinueError(state, format("Occurs in {} = {}", cCMO_DDVarVolOA, thisDD.Name));
673 0 : ShowContinueError(state, format("The design outdoor air requirement is {:.5R}", thisDD.DesignOAFlowRate));
674 0 : ErrorsFound = true;
675 : }
676 : }
677 : }
678 : }
679 :
680 0 : if (thisDD.OAPerPersonMode == PerPersonMode::ModeNotSet) {
681 0 : DummyOAFlow = state.dataSize->OARequirements(thisDD.OARequirementsPtr).OAFlowPerPerson;
682 0 : if ((DummyOAFlow == 0.0) && (lAlphaBlanks(7))) { // no worries
683 : // do nothing, okay since no per person requirement involved
684 0 : } else if ((DummyOAFlow > 0.0) && (lAlphaBlanks(7))) { // missing input
685 0 : ShowSevereError(state, format("{} was blank.", cAlphaFields(7)));
686 0 : ShowContinueError(state, format("Occurs in {} = {}", cCMO_DDVarVolOA, thisDD.Name));
687 0 : ShowContinueError(state, R"(Valid choices are "CurrentOccupancy" or "DesignOccupancy")");
688 0 : ErrorsFound = true;
689 0 : } else if ((DummyOAFlow > 0.0) && !(lAlphaBlanks(7))) { // incorrect input
690 0 : ShowSevereError(state, format("{} = {} not a valid key choice.", cAlphaFields(7), AlphArray(7)));
691 0 : ShowContinueError(state, format("Occurs in {} = {}", cCMO_DDVarVolOA, thisDD.Name));
692 0 : ShowContinueError(state, R"(Valid choices are "CurrentOccupancy" or "DesignOccupancy")");
693 0 : ErrorsFound = true;
694 : }
695 : }
696 :
697 : // Setup the Average damper Position output variable
698 0 : SetupOutputVariable(state,
699 : "Zone Air Terminal Outdoor Air Duct Damper Position",
700 : Constant::Units::None,
701 0 : thisDD.OADamperPosition,
702 : OutputProcessor::TimeStepType::System,
703 : OutputProcessor::StoreType::Average,
704 0 : thisDD.Name);
705 0 : SetupOutputVariable(state,
706 : "Zone Air Terminal Recirculated Air Duct Damper Position",
707 : Constant::Units::None,
708 0 : thisDD.RecircAirDamperPosition,
709 : OutputProcessor::TimeStepType::System,
710 : OutputProcessor::StoreType::Average,
711 0 : thisDD.Name);
712 0 : SetupOutputVariable(state,
713 : "Zone Air Terminal Outdoor Air Fraction",
714 : Constant::Units::None,
715 0 : thisDD.OAFraction,
716 : OutputProcessor::TimeStepType::System,
717 : OutputProcessor::StoreType::Average,
718 0 : thisDD.Name);
719 :
720 : } // end Number of Damper Loop
721 : }
722 :
723 2 : if (ErrorsFound) {
724 0 : ShowFatalError(state, format("{}Errors found in input. Preceding condition(s) cause termination.", RoutineName));
725 : }
726 2 : }
727 :
728 4 : void DualDuctAirTerminal::InitDualDuct(EnergyPlusData &state, bool const FirstHVACIteration)
729 : {
730 :
731 : // SUBROUTINE INFORMATION:
732 : // AUTHOR Richard J. Liesen
733 : // DATE WRITTEN February 1998
734 : // MODIFIED na
735 : // RE-ENGINEERED na
736 :
737 : // PURPOSE OF THIS SUBROUTINE:
738 : // This subroutine is for initializations of the Damper Components.
739 :
740 : // METHODOLOGY EMPLOYED:
741 : // Uses the status flags to trigger events.
742 :
743 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
744 : int HotInNode;
745 : int ColdInNode;
746 : int OAInNode; // Outdoor Air Inlet Node for VAV:OutdoorAir units
747 : int RAInNode; // Reciruclated Air Inlet Node for VAV:OutdoorAir units
748 : int OutNode;
749 : int Loop; // Loop checking control variable
750 : Real64 PeopleFlow; // local sum variable, m3/s
751 :
752 4 : if (!state.dataDualDuct->ZoneEquipmentListChecked && state.dataZoneEquip->ZoneEquipInputsFilled) {
753 0 : state.dataDualDuct->ZoneEquipmentListChecked = true;
754 : // Check to see if there is a Air Distribution Unit on the Zone Equipment List
755 0 : for (Loop = 1; Loop <= state.dataDualDuct->NumDDAirTerminal; ++Loop) {
756 0 : if (this->ADUNum == 0) continue;
757 0 : if (DataZoneEquipment::CheckZoneEquipmentList(
758 0 : state, "ZONEHVAC:AIRDISTRIBUTIONUNIT", state.dataDefineEquipment->AirDistUnit(this->ADUNum).Name))
759 0 : continue;
760 0 : ShowSevereError(state,
761 0 : format("InitDualDuct: ADU=[Air Distribution Unit,{}] is not on any ZoneHVAC:EquipmentList.",
762 0 : state.dataDefineEquipment->AirDistUnit(this->ADUNum).Name));
763 0 : if (this->DamperType == DualDuctDamper::ConstantVolume) {
764 0 : ShowContinueError(state, format("...Dual Duct Damper=[{},{}] will not be simulated.", cCMO_DDConstantVolume, this->Name));
765 0 : } else if (this->DamperType == DualDuctDamper::VariableVolume) {
766 0 : ShowContinueError(state, format("...Dual Duct Damper=[{},{}] will not be simulated.", cCMO_DDVariableVolume, this->Name));
767 0 : } else if (this->DamperType == DualDuctDamper::OutdoorAir) {
768 0 : ShowContinueError(state, format("...Dual Duct Damper=[{},{}] will not be simulated.", cCMO_DDVarVolOA, this->Name));
769 : } else {
770 0 : ShowContinueError(state, format("...Dual Duct Damper=[unknown/invalid,{}] will not be simulated.", this->Name));
771 : }
772 : }
773 : }
774 :
775 4 : if (!state.dataGlobal->SysSizingCalc && this->MySizeFlag) {
776 1 : this->SizeDualDuct(state);
777 1 : this->MySizeFlag = false;
778 : }
779 :
780 : // Do the Begin Environment initializations
781 4 : if (state.dataGlobal->BeginEnvrnFlag && this->MyEnvrnFlag) {
782 :
783 2 : if (this->DamperType == DualDuctDamper::ConstantVolume || this->DamperType == DualDuctDamper::VariableVolume) {
784 2 : OutNode = this->OutletNodeNum;
785 2 : HotInNode = this->HotAirInletNodeNum;
786 2 : ColdInNode = this->ColdAirInletNodeNum;
787 2 : state.dataLoopNodes->Node(OutNode).MassFlowRateMax = this->MaxAirVolFlowRate * state.dataEnvrn->StdRhoAir;
788 2 : if (this->DamperType == DualDuctDamper::ConstantVolume) {
789 0 : state.dataLoopNodes->Node(OutNode).MassFlowRateMin = 0.0;
790 2 : } else if (this->DamperType == DualDuctDamper::VariableVolume) {
791 : // get dual duct air terminal box minimum flow fraction value
792 2 : if (this->zoneTurndownMinAirFracSched != nullptr) {
793 2 : this->ZoneTurndownMinAirFrac = this->zoneTurndownMinAirFracSched->getMinVal(state);
794 : } else {
795 0 : this->ZoneTurndownMinAirFrac = 1.0;
796 : }
797 2 : state.dataLoopNodes->Node(OutNode).MassFlowRateMin =
798 2 : state.dataLoopNodes->Node(OutNode).MassFlowRateMax * this->ZoneMinAirFracDes * this->ZoneTurndownMinAirFrac;
799 : } else {
800 0 : state.dataLoopNodes->Node(OutNode).MassFlowRateMin = 0.0;
801 : }
802 2 : this->dd_airterminalHotAirInlet.AirMassFlowRateMax = state.dataLoopNodes->Node(OutNode).MassFlowRateMax;
803 2 : this->dd_airterminalColdAirInlet.AirMassFlowRateMax = state.dataLoopNodes->Node(OutNode).MassFlowRateMax;
804 2 : state.dataLoopNodes->Node(HotInNode).MassFlowRateMax = state.dataLoopNodes->Node(OutNode).MassFlowRateMax;
805 2 : state.dataLoopNodes->Node(ColdInNode).MassFlowRateMax = state.dataLoopNodes->Node(OutNode).MassFlowRateMax;
806 2 : state.dataLoopNodes->Node(HotInNode).MassFlowRateMin = 0.0;
807 2 : state.dataLoopNodes->Node(ColdInNode).MassFlowRateMin = 0.0;
808 2 : this->MyEnvrnFlag = false;
809 :
810 0 : } else if (this->DamperType == DualDuctDamper::OutdoorAir) {
811 : // Initialize for DualDuct:VAV:OutdoorAir
812 0 : OutNode = this->OutletNodeNum;
813 0 : OAInNode = this->OAInletNodeNum;
814 0 : if (this->RecircIsUsed) RAInNode = this->RecircAirInletNodeNum;
815 0 : state.dataLoopNodes->Node(OutNode).MassFlowRateMax = this->MaxAirMassFlowRate;
816 0 : state.dataLoopNodes->Node(OutNode).MassFlowRateMin = 0.0;
817 0 : this->dd_airterminalOAInlet.AirMassFlowRateMax = this->DesignOAFlowRate * state.dataEnvrn->StdRhoAir;
818 0 : if (this->RecircIsUsed) {
819 0 : this->dd_airterminalRecircAirInlet.AirMassFlowRateMax = this->MaxAirMassFlowRate - this->dd_airterminalOAInlet.AirMassFlowRateMax;
820 0 : state.dataLoopNodes->Node(RAInNode).MassFlowRateMax = this->dd_airterminalRecircAirInlet.AirMassFlowRateMax;
821 0 : state.dataLoopNodes->Node(RAInNode).MassFlowRateMin = 0.0;
822 0 : this->dd_airterminalRecircAirInlet.AirMassFlowDiffMag = 1.0e-10 * this->dd_airterminalRecircAirInlet.AirMassFlowRateMax;
823 : }
824 0 : state.dataLoopNodes->Node(OAInNode).MassFlowRateMax = this->dd_airterminalOAInlet.AirMassFlowRateMax;
825 0 : state.dataLoopNodes->Node(OAInNode).MassFlowRateMin = 0.0;
826 : // figure per person by design level for the OA duct.
827 0 : PeopleFlow = 0.0;
828 0 : for (Loop = 1; Loop <= state.dataHeatBal->TotPeople; ++Loop) {
829 0 : if (state.dataHeatBal->People(Loop).ZonePtr != this->CtrlZoneNum) continue;
830 0 : DataSizing::OAFlowCalcMethod damperOAFlowMethod = state.dataSize->OARequirements(this->OARequirementsPtr).OAFlowMethod;
831 0 : if (damperOAFlowMethod == DataSizing::OAFlowCalcMethod::PerPerson || damperOAFlowMethod == DataSizing::OAFlowCalcMethod::Sum ||
832 : damperOAFlowMethod == DataSizing::OAFlowCalcMethod::Max) {
833 0 : PeopleFlow +=
834 0 : state.dataHeatBal->People(Loop).NumberOfPeople * state.dataSize->OARequirements(this->OARequirementsPtr).OAFlowPerPerson;
835 : }
836 : }
837 0 : this->MyEnvrnFlag = false;
838 : }
839 : }
840 :
841 4 : if (!state.dataGlobal->BeginEnvrnFlag) {
842 2 : this->MyEnvrnFlag = true;
843 : }
844 :
845 : // Find air loop associated with this terminal unit
846 4 : if (this->MyAirLoopFlag) {
847 4 : if (this->AirLoopNum == 0) {
848 4 : if ((this->CtrlZoneNum > 0) && (this->CtrlZoneInNodeIndex > 0)) {
849 4 : this->AirLoopNum = state.dataZoneEquip->ZoneEquipConfig(this->CtrlZoneNum).InletNodeAirLoopNum(this->CtrlZoneInNodeIndex);
850 4 : state.dataDefineEquipment->AirDistUnit(this->ADUNum).AirLoopNum = this->AirLoopNum;
851 : // Don't set MyAirLoopFlag to false yet because airloopnums might not be populated yet
852 : }
853 : } else {
854 0 : this->MyAirLoopFlag = false;
855 : }
856 : }
857 :
858 : // Initialize the Inlet Nodes of the Sys
859 4 : if (this->DamperType == DualDuctDamper::ConstantVolume || this->DamperType == DualDuctDamper::VariableVolume) {
860 4 : HotInNode = this->HotAirInletNodeNum;
861 4 : ColdInNode = this->ColdAirInletNodeNum;
862 4 : OutNode = this->OutletNodeNum;
863 0 : } else if (this->DamperType == DualDuctDamper::OutdoorAir) {
864 0 : OAInNode = this->OAInletNodeNum;
865 0 : if (this->RecircIsUsed) RAInNode = this->RecircAirInletNodeNum;
866 0 : OutNode = this->OutletNodeNum;
867 : }
868 :
869 4 : if (FirstHVACIteration) {
870 : // CALL DisplayString('Init First HVAC Iteration {'//TRIM( dd_airterminal(DDNum)%DamperName)//'}') !-For debugging - REMOVE
871 : // The first time through set the mass flow rate to the Max
872 : // Take care of the flow rates first. For Const Vol and VAV.
873 2 : if (this->DamperType == DualDuctDamper::ConstantVolume || this->DamperType == DualDuctDamper::VariableVolume) {
874 2 : auto &thisHotInNode = state.dataLoopNodes->Node(HotInNode);
875 2 : auto &thisColdInNode = state.dataLoopNodes->Node(ColdInNode);
876 2 : Real64 schedValue = this->availSched->getCurrentVal();
877 2 : if ((thisHotInNode.MassFlowRate > 0.0) && (schedValue > 0.0)) {
878 2 : thisHotInNode.MassFlowRate = this->dd_airterminalHotAirInlet.AirMassFlowRateMax;
879 : } else {
880 0 : thisHotInNode.MassFlowRate = 0.0;
881 : }
882 2 : if ((thisColdInNode.MassFlowRate > 0.0) && (schedValue > 0.0)) {
883 0 : thisColdInNode.MassFlowRate = this->dd_airterminalColdAirInlet.AirMassFlowRateMax;
884 : } else {
885 2 : thisColdInNode.MassFlowRate = 0.0;
886 : }
887 : // Next take care of the Max Avail Flow Rates
888 2 : if ((thisHotInNode.MassFlowRateMaxAvail > 0.0) && (schedValue > 0.0)) {
889 2 : thisHotInNode.MassFlowRateMaxAvail = this->dd_airterminalHotAirInlet.AirMassFlowRateMax;
890 : } else {
891 0 : thisHotInNode.MassFlowRateMaxAvail = 0.0;
892 : }
893 2 : if ((thisColdInNode.MassFlowRateMaxAvail > 0.0) && (schedValue > 0.0)) {
894 0 : thisColdInNode.MassFlowRateMaxAvail = this->dd_airterminalColdAirInlet.AirMassFlowRateMax;
895 : } else {
896 2 : thisColdInNode.MassFlowRateMaxAvail = 0.0;
897 : }
898 : // get current time step air terminal box turndown minimum flow fraction
899 2 : if (this->zoneTurndownMinAirFracSched != nullptr) {
900 2 : this->ZoneTurndownMinAirFrac = this->zoneTurndownMinAirFracSched->getCurrentVal();
901 : } else {
902 0 : this->ZoneTurndownMinAirFrac = 1.0;
903 : }
904 : // update to the current dual duct minimum air flow fraction
905 2 : this->ZoneMinAirFrac = this->ZoneMinAirFracDes * this->ZoneTurndownMinAirFrac;
906 : // The last item is to take care of the Min Avail Flow Rates
907 2 : if ((thisHotInNode.MassFlowRate > 0.0) && (schedValue > 0.0)) {
908 2 : thisHotInNode.MassFlowRateMinAvail = this->dd_airterminalHotAirInlet.AirMassFlowRateMax * this->ZoneMinAirFrac;
909 : } else {
910 0 : thisHotInNode.MassFlowRateMinAvail = 0.0;
911 : }
912 2 : if ((thisColdInNode.MassFlowRate > 0.0) && (schedValue > 0.0)) {
913 0 : thisColdInNode.MassFlowRateMinAvail = this->dd_airterminalColdAirInlet.AirMassFlowRateMax * this->ZoneMinAirFrac;
914 : } else {
915 2 : thisColdInNode.MassFlowRateMinAvail = 0.0;
916 : }
917 :
918 2 : } else if (this->DamperType == DualDuctDamper::OutdoorAir) {
919 0 : auto &thisOAInNode = state.dataLoopNodes->Node(OAInNode);
920 0 : Real64 schedValue = this->availSched->getCurrentVal();
921 : // The first time through set the mass flow rate to the Max for VAV:OutdoorAir
922 0 : if ((thisOAInNode.MassFlowRate > 0.0) && (schedValue > 0.0)) {
923 0 : thisOAInNode.MassFlowRate = this->dd_airterminalOAInlet.AirMassFlowRateMax;
924 : } else {
925 0 : thisOAInNode.MassFlowRate = 0.0;
926 : }
927 0 : if (this->RecircIsUsed) {
928 0 : auto &thisRAInNode = state.dataLoopNodes->Node(RAInNode);
929 0 : if ((thisRAInNode.MassFlowRate > 0.0) && (schedValue > 0.0)) {
930 0 : thisRAInNode.MassFlowRate = this->dd_airterminalRecircAirInlet.AirMassFlowRateMax;
931 : } else {
932 0 : thisRAInNode.MassFlowRate = 0.0;
933 : }
934 : // clear flow history
935 0 : this->dd_airterminalRecircAirInlet.AirMassFlowRateHist1 = 0.0;
936 0 : this->dd_airterminalRecircAirInlet.AirMassFlowRateHist2 = 0.0;
937 0 : this->dd_airterminalRecircAirInlet.AirMassFlowRateHist3 = 0.0;
938 : }
939 : // Next take care of the Max Avail Flow Rates
940 0 : if ((thisOAInNode.MassFlowRateMaxAvail > 0.0) && (schedValue > 0.0)) {
941 0 : thisOAInNode.MassFlowRateMaxAvail = this->dd_airterminalOAInlet.AirMassFlowRateMax;
942 : } else {
943 0 : thisOAInNode.MassFlowRateMaxAvail = 0.0;
944 : }
945 0 : if (this->RecircIsUsed) {
946 0 : auto &thisRAInNode = state.dataLoopNodes->Node(RAInNode);
947 0 : if ((thisRAInNode.MassFlowRateMaxAvail > 0.0) && (schedValue > 0.0)) {
948 0 : thisRAInNode.MassFlowRateMaxAvail = this->dd_airterminalRecircAirInlet.AirMassFlowRateMax;
949 : } else {
950 0 : thisRAInNode.MassFlowRateMaxAvail = 0.0;
951 : }
952 : }
953 : // The last item is to take care of the Min Avail Flow Rates. VAV:OutdoorAir
954 0 : thisOAInNode.MassFlowRateMinAvail = 0.0;
955 0 : if (this->RecircIsUsed) {
956 0 : auto &thisRAInNode = state.dataLoopNodes->Node(RAInNode);
957 0 : thisRAInNode.MassFlowRateMinAvail = 0.0;
958 : }
959 : }
960 : }
961 :
962 : // Initialize the Inlet Nodes of the Dampers for Const. Vol and VAV
963 4 : if (this->DamperType == DualDuctDamper::ConstantVolume || this->DamperType == DualDuctDamper::VariableVolume) {
964 :
965 4 : this->dd_airterminalHotAirInlet.AirMassFlowRateMaxAvail =
966 4 : min(state.dataLoopNodes->Node(OutNode).MassFlowRateMax, state.dataLoopNodes->Node(HotInNode).MassFlowRateMaxAvail);
967 4 : this->dd_airterminalHotAirInlet.AirMassFlowRateMinAvail =
968 4 : min(max(state.dataLoopNodes->Node(OutNode).MassFlowRateMin, state.dataLoopNodes->Node(HotInNode).MassFlowRateMinAvail),
969 4 : state.dataLoopNodes->Node(HotInNode).MassFlowRateMaxAvail);
970 :
971 4 : this->dd_airterminalColdAirInlet.AirMassFlowRateMaxAvail =
972 4 : min(state.dataLoopNodes->Node(OutNode).MassFlowRateMax, state.dataLoopNodes->Node(ColdInNode).MassFlowRateMaxAvail);
973 4 : this->dd_airterminalColdAirInlet.AirMassFlowRateMinAvail =
974 4 : min(max(state.dataLoopNodes->Node(OutNode).MassFlowRateMin, state.dataLoopNodes->Node(ColdInNode).MassFlowRateMinAvail),
975 4 : state.dataLoopNodes->Node(ColdInNode).MassFlowRateMaxAvail);
976 :
977 : // Do the following initializations (every time step): This should be the info from
978 : // the previous components outlets or the node data in this section.
979 : // Load the node data in this section for the component simulation
980 4 : this->dd_airterminalHotAirInlet.AirMassFlowRate = state.dataLoopNodes->Node(HotInNode).MassFlowRate;
981 4 : this->dd_airterminalHotAirInlet.AirTemp = state.dataLoopNodes->Node(HotInNode).Temp;
982 4 : this->dd_airterminalHotAirInlet.AirHumRat = state.dataLoopNodes->Node(HotInNode).HumRat;
983 4 : this->dd_airterminalHotAirInlet.AirEnthalpy = state.dataLoopNodes->Node(HotInNode).Enthalpy;
984 4 : this->dd_airterminalColdAirInlet.AirMassFlowRate = state.dataLoopNodes->Node(ColdInNode).MassFlowRate;
985 4 : this->dd_airterminalColdAirInlet.AirTemp = state.dataLoopNodes->Node(ColdInNode).Temp;
986 4 : this->dd_airterminalColdAirInlet.AirHumRat = state.dataLoopNodes->Node(ColdInNode).HumRat;
987 4 : this->dd_airterminalColdAirInlet.AirEnthalpy = state.dataLoopNodes->Node(ColdInNode).Enthalpy;
988 :
989 : // Initialize the Inlet Nodes of the Dampers for VAV:OutdoorAir
990 0 : } else if (this->DamperType == DualDuctDamper::OutdoorAir) {
991 0 : this->dd_airterminalOAInlet.AirMassFlowRateMaxAvail = state.dataLoopNodes->Node(OAInNode).MassFlowRateMaxAvail;
992 0 : this->dd_airterminalOAInlet.AirMassFlowRateMinAvail = state.dataLoopNodes->Node(OAInNode).MassFlowRateMinAvail;
993 :
994 : // Do the following initializations (every time step): This should be the info from
995 : // the previous components outlets or the node data in this section.
996 : // Load the node data in this section for the component simulation
997 0 : this->dd_airterminalOAInlet.AirMassFlowRate = state.dataLoopNodes->Node(OAInNode).MassFlowRate;
998 0 : this->dd_airterminalOAInlet.AirTemp = state.dataLoopNodes->Node(OAInNode).Temp;
999 0 : this->dd_airterminalOAInlet.AirHumRat = state.dataLoopNodes->Node(OAInNode).HumRat;
1000 0 : this->dd_airterminalOAInlet.AirEnthalpy = state.dataLoopNodes->Node(OAInNode).Enthalpy;
1001 0 : if (this->RecircIsUsed) {
1002 0 : this->dd_airterminalRecircAirInlet.AirMassFlowRateMaxAvail = state.dataLoopNodes->Node(RAInNode).MassFlowRateMaxAvail;
1003 0 : this->dd_airterminalRecircAirInlet.AirMassFlowRateMinAvail = state.dataLoopNodes->Node(RAInNode).MassFlowRateMinAvail;
1004 0 : this->dd_airterminalRecircAirInlet.AirMassFlowRate = state.dataLoopNodes->Node(RAInNode).MassFlowRate;
1005 0 : this->dd_airterminalRecircAirInlet.AirTemp = state.dataLoopNodes->Node(RAInNode).Temp;
1006 0 : this->dd_airterminalRecircAirInlet.AirHumRat = state.dataLoopNodes->Node(RAInNode).HumRat;
1007 0 : this->dd_airterminalRecircAirInlet.AirEnthalpy = state.dataLoopNodes->Node(RAInNode).Enthalpy;
1008 : }
1009 : }
1010 4 : }
1011 :
1012 1 : void DualDuctAirTerminal::SizeDualDuct(EnergyPlusData &state)
1013 : {
1014 :
1015 : // SUBROUTINE INFORMATION:
1016 : // AUTHOR Fred Buhl
1017 : // DATE WRITTEN January 2002
1018 : // MODIFIED na
1019 : // RE-ENGINEERED na
1020 :
1021 : // PURPOSE OF THIS SUBROUTINE:
1022 : // This subroutine is for sizing Dual Duct air terminal units for which flow rates have not been
1023 : // specified in the input.
1024 :
1025 : // METHODOLOGY EMPLOYED:
1026 : // Obtains flow rates from the zone or system sizing arrays.
1027 :
1028 1 : if (this->MaxAirVolFlowRate == DataSizing::AutoSize) {
1029 :
1030 0 : if ((state.dataSize->CurZoneEqNum > 0) && (state.dataSize->CurTermUnitSizingNum > 0)) {
1031 0 : std::string_view damperType = cmoNameArray[static_cast<int>(this->DamperType)];
1032 : // ideally we'd just use a string_view, but there are multiple calls that are not yet set up for string_view, and they pass a
1033 : // reference, so we just create a string version for now. When we do more string_view cleanup, we'll end up searching on
1034 : // std::string() to find usages of it, so this should show up and get cleaned up then. Regardless, this is only called at
1035 : // program initialization, so it is not a runtime issue.
1036 0 : std::string damperTypeAsString = std::string(damperType);
1037 0 : CheckZoneSizing(state, damperTypeAsString, this->Name);
1038 0 : this->MaxAirVolFlowRate = max(state.dataSize->TermUnitFinalZoneSizing(state.dataSize->CurTermUnitSizingNum).DesCoolVolFlow,
1039 0 : state.dataSize->TermUnitFinalZoneSizing(state.dataSize->CurTermUnitSizingNum).DesHeatVolFlow);
1040 0 : if (this->DamperType == DualDuctDamper::OutdoorAir) {
1041 0 : if (this->RecircIsUsed) {
1042 0 : this->DesignRecircFlowRate =
1043 0 : max(state.dataSize->TermUnitFinalZoneSizing(state.dataSize->CurTermUnitSizingNum).DesCoolVolFlow,
1044 0 : state.dataSize->TermUnitFinalZoneSizing(state.dataSize->CurTermUnitSizingNum).DesHeatVolFlow);
1045 0 : this->MaxAirVolFlowRate = this->DesignRecircFlowRate + this->DesignOAFlowRate;
1046 : } else {
1047 0 : this->MaxAirVolFlowRate = this->DesignOAFlowRate;
1048 0 : this->DesignRecircFlowRate = 0.0;
1049 : }
1050 0 : this->MaxAirMassFlowRate = this->MaxAirVolFlowRate * state.dataEnvrn->StdRhoAir;
1051 : }
1052 :
1053 0 : if (this->MaxAirVolFlowRate < HVAC::SmallAirVolFlow) {
1054 0 : this->MaxAirVolFlowRate = 0.0;
1055 0 : this->MaxAirMassFlowRate = 0.0;
1056 0 : this->DesignOAFlowRate = 0.0;
1057 0 : this->DesignRecircFlowRate = 0.0;
1058 : }
1059 0 : BaseSizer::reportSizerOutput(state, damperTypeAsString, this->Name, "Maximum Air Flow Rate [m3/s]", this->MaxAirVolFlowRate);
1060 0 : if (this->DamperType == DualDuctDamper::OutdoorAir) {
1061 0 : BaseSizer::reportSizerOutput(
1062 : state, damperTypeAsString, this->Name, "Maximum Outdoor Air Flow Rate [m3/s]", this->DesignOAFlowRate);
1063 0 : if (this->RecircIsUsed) {
1064 0 : BaseSizer::reportSizerOutput(
1065 : state, damperTypeAsString, this->Name, "Maximum Recirculated Air Flow Rate [m3/s]", this->DesignRecircFlowRate);
1066 : }
1067 : }
1068 0 : }
1069 : }
1070 1 : }
1071 :
1072 0 : void DualDuctAirTerminal::SimDualDuctConstVol(EnergyPlusData &state, int const ZoneNum, int const ZoneNodeNum)
1073 : {
1074 :
1075 : // SUBROUTINE INFORMATION:
1076 : // AUTHOR Richard J. Liesen
1077 : // DATE WRITTEN Jan 2000
1078 : // MODIFIED na
1079 : // RE-ENGINEERED na
1080 :
1081 : // PURPOSE OF THIS SUBROUTINE:
1082 : // This subroutine simulates the simple mixing damper.
1083 :
1084 : // METHODOLOGY EMPLOYED:
1085 : // There is method to this madness.
1086 :
1087 : // Using/Aliasing
1088 : using namespace DataZoneEnergyDemands;
1089 : using HVAC::SmallTempDiff;
1090 : using Psychrometrics::PsyCpAirFnW;
1091 : using Psychrometrics::PsyTdbFnHW;
1092 :
1093 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1094 : Real64 MassFlow; // [kg/sec] Total Mass Flow Rate from Hot & Cold Inlets
1095 : Real64 HumRat; // [Kg Moisture / Kg dry air]
1096 : Real64 Enthalpy; // [Watts]
1097 : Real64 Temperature; // [C]
1098 : Real64 QTotLoad; // [W]
1099 : Real64 QZnReq; // [W]
1100 : Real64 CpAirZn;
1101 : Real64 CpAirSysHot;
1102 : Real64 CpAirSysCold;
1103 :
1104 : // Get the calculated load from the Heat Balance from ZoneSysEnergyDemand
1105 0 : QTotLoad = state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneNum).RemainingOutputRequired;
1106 : // Need the design MassFlowRate for calculations
1107 0 : if (this->availSched->getCurrentVal() > 0.0) {
1108 0 : MassFlow = this->dd_airterminalHotAirInlet.AirMassFlowRateMaxAvail / 2.0 + this->dd_airterminalColdAirInlet.AirMassFlowRateMaxAvail / 2.0;
1109 : } else {
1110 0 : MassFlow = 0.0;
1111 : }
1112 : // If there is massflow then need to provide the correct amount of total
1113 : // required zone energy
1114 0 : if (MassFlow > HVAC::SmallMassFlow) {
1115 0 : CpAirZn = PsyCpAirFnW(state.dataLoopNodes->Node(ZoneNodeNum).HumRat);
1116 0 : QZnReq = QTotLoad + MassFlow * CpAirZn * state.dataLoopNodes->Node(ZoneNodeNum).Temp;
1117 : // If the enthalpy is the same for the hot and cold duct then there would be a
1118 : // divide by zero so for heating or cooling set the damper to one max flow
1119 : // or the other.
1120 0 : if (std::abs(this->dd_airterminalColdAirInlet.AirTemp - this->dd_airterminalHotAirInlet.AirTemp) > SmallTempDiff) {
1121 : // CpAirSysHot = PsyCpAirFnWTdb(dd_airterminalHotAirInlet(DDNum)%AirHumRat,dd_airterminalHotAirInlet(DDNum)%AirTemp)
1122 : // CpAirSysCold= PsyCpAirFnWTdb(dd_airterminalColdAirInlet(DDNum)%AirHumRat,dd_airterminalColdAirInlet(DDNum)%AirTemp)
1123 0 : CpAirSysHot = CpAirZn;
1124 0 : CpAirSysCold = CpAirZn;
1125 : // Determine the Cold Air Mass Flow Rate
1126 0 : this->dd_airterminalColdAirInlet.AirMassFlowRate =
1127 0 : (QZnReq - MassFlow * CpAirSysHot * this->dd_airterminalHotAirInlet.AirTemp) /
1128 0 : (CpAirSysCold * this->dd_airterminalColdAirInlet.AirTemp - CpAirSysHot * this->dd_airterminalHotAirInlet.AirTemp);
1129 0 : } else if ((QTotLoad > 0.0) && (this->dd_airterminalHotAirInlet.AirMassFlowRate > 0.0)) {
1130 0 : this->dd_airterminalColdAirInlet.AirMassFlowRate = 0.0;
1131 : } else {
1132 0 : this->dd_airterminalColdAirInlet.AirMassFlowRate = MassFlow;
1133 : }
1134 : // Check to make sure that the calculated flow is not greater than the available flows
1135 0 : if (this->dd_airterminalColdAirInlet.AirMassFlowRate > this->dd_airterminalColdAirInlet.AirMassFlowRateMaxAvail) {
1136 0 : this->dd_airterminalColdAirInlet.AirMassFlowRate = this->dd_airterminalColdAirInlet.AirMassFlowRateMaxAvail;
1137 0 : } else if (this->dd_airterminalColdAirInlet.AirMassFlowRate < this->dd_airterminalColdAirInlet.AirMassFlowRateMinAvail) {
1138 0 : this->dd_airterminalColdAirInlet.AirMassFlowRate = this->dd_airterminalColdAirInlet.AirMassFlowRateMinAvail;
1139 : }
1140 : // Using Mass Continuity to determine the other duct flow quantity
1141 0 : this->dd_airterminalHotAirInlet.AirMassFlowRate = MassFlow - this->dd_airterminalColdAirInlet.AirMassFlowRate;
1142 0 : if (this->dd_airterminalHotAirInlet.AirMassFlowRate > this->dd_airterminalHotAirInlet.AirMassFlowRateMaxAvail) {
1143 0 : this->dd_airterminalHotAirInlet.AirMassFlowRate = this->dd_airterminalHotAirInlet.AirMassFlowRateMaxAvail;
1144 0 : } else if (this->dd_airterminalHotAirInlet.AirMassFlowRate < this->dd_airterminalHotAirInlet.AirMassFlowRateMinAvail) {
1145 0 : this->dd_airterminalHotAirInlet.AirMassFlowRate = this->dd_airterminalHotAirInlet.AirMassFlowRateMinAvail;
1146 : }
1147 0 : MassFlow = this->dd_airterminalColdAirInlet.AirMassFlowRate + this->dd_airterminalHotAirInlet.AirMassFlowRate;
1148 : } else {
1149 : // System is Off set massflow to 0.0
1150 0 : MassFlow = 0.0;
1151 : }
1152 0 : if (MassFlow > HVAC::SmallMassFlow) {
1153 : // After flows are calculated then calculate the mixed air flow properties.
1154 0 : HumRat = (this->dd_airterminalHotAirInlet.AirHumRat * this->dd_airterminalHotAirInlet.AirMassFlowRate +
1155 0 : this->dd_airterminalColdAirInlet.AirHumRat * this->dd_airterminalColdAirInlet.AirMassFlowRate) /
1156 : MassFlow;
1157 0 : Enthalpy = (this->dd_airterminalHotAirInlet.AirEnthalpy * this->dd_airterminalHotAirInlet.AirMassFlowRate +
1158 0 : this->dd_airterminalColdAirInlet.AirEnthalpy * this->dd_airterminalColdAirInlet.AirMassFlowRate) /
1159 : MassFlow;
1160 :
1161 : // If there is no air flow than calculate the No Flow conditions
1162 : } else {
1163 0 : this->dd_airterminalColdAirInlet.AirMassFlowRate = 0.0;
1164 0 : this->dd_airterminalHotAirInlet.AirMassFlowRate = 0.0;
1165 0 : HumRat = (this->dd_airterminalHotAirInlet.AirHumRat + this->dd_airterminalColdAirInlet.AirHumRat) / 2.0;
1166 0 : Enthalpy = (this->dd_airterminalHotAirInlet.AirEnthalpy + this->dd_airterminalColdAirInlet.AirEnthalpy) / 2.0;
1167 : }
1168 0 : Temperature = PsyTdbFnHW(Enthalpy, HumRat);
1169 :
1170 : // Load all properties in the damper outlet
1171 0 : this->dd_airterminalOutlet.AirTemp = Temperature;
1172 0 : this->dd_airterminalOutlet.AirHumRat = HumRat;
1173 0 : this->dd_airterminalOutlet.AirMassFlowRate = MassFlow;
1174 0 : this->dd_airterminalOutlet.AirMassFlowRateMaxAvail = MassFlow;
1175 0 : this->dd_airterminalOutlet.AirMassFlowRateMinAvail =
1176 0 : min(this->dd_airterminalHotAirInlet.AirMassFlowRateMinAvail, this->dd_airterminalColdAirInlet.AirMassFlowRateMinAvail);
1177 0 : this->dd_airterminalOutlet.AirEnthalpy = Enthalpy;
1178 :
1179 : // Calculate the hot and cold damper position in %
1180 0 : if ((this->dd_airterminalHotAirInlet.AirMassFlowRateMax == 0.0) || (this->dd_airterminalColdAirInlet.AirMassFlowRateMax == 0.0)) {
1181 0 : this->ColdAirDamperPosition = 0.0;
1182 0 : this->HotAirDamperPosition = 0.0;
1183 : } else {
1184 0 : this->ColdAirDamperPosition = this->dd_airterminalColdAirInlet.AirMassFlowRate / this->dd_airterminalColdAirInlet.AirMassFlowRateMax;
1185 0 : this->HotAirDamperPosition = this->dd_airterminalHotAirInlet.AirMassFlowRate / this->dd_airterminalHotAirInlet.AirMassFlowRateMax;
1186 : }
1187 0 : }
1188 :
1189 2 : void DualDuctAirTerminal::SimDualDuctVarVol(EnergyPlusData &state, int const ZoneNum, int const ZoneNodeNum)
1190 : {
1191 :
1192 : // SUBROUTINE INFORMATION:
1193 : // AUTHOR Richard J. Liesen
1194 : // DATE WRITTEN Jan 2000
1195 : // MODIFIED na
1196 : // TH 3/2012: added supply air flow adjustment based on zone maximum outdoor
1197 : // air fraction - a TRACE feature
1198 : // RE-ENGINEERED na
1199 :
1200 : // PURPOSE OF THIS SUBROUTINE:
1201 : // This subroutine simulates the simple mixing damper.
1202 :
1203 : // METHODOLOGY EMPLOYED:
1204 : // There is method to this madness.
1205 :
1206 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1207 : Real64 MassFlow; // [kg/sec] Total Mass Flow Rate from Hot & Cold Inlets
1208 : Real64 HumRat; // [Kg Moisture / Kg dry air]
1209 : Real64 Enthalpy; // [Watts]
1210 : Real64 Temperature; // [C]
1211 : Real64 QTotLoad; // [W]
1212 : Real64 QZnReq; // [W]
1213 : Real64 CpAirZn; // specific heat of zone air
1214 : Real64 CpAirSysHot;
1215 : Real64 CpAirSysCold;
1216 : Real64 MassFlowBasedOnOA; // Supply air flow rate based on minimum OA requirement
1217 : Real64 AirLoopOAFrac; // fraction of outdoor air entering air loop outside air system
1218 :
1219 : // The calculated load from the Heat Balance
1220 2 : QTotLoad = state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneNum).RemainingOutputRequired;
1221 : // Calculate all of the required Cp's
1222 2 : CpAirZn = Psychrometrics::PsyCpAirFnW(state.dataLoopNodes->Node(ZoneNodeNum).HumRat);
1223 : // CpAirSysHot = PsyCpAirFnW(DamperHotAirInlet(DDNum)%AirHumRat,DamperHotAirInlet(DDNum)%AirTemp)
1224 : // CpAirSysCold= PsyCpAirFnW(DamperColdAirInlet(DDNum)%AirHumRat,DamperColdAirInlet(DDNum)%AirTemp)
1225 2 : CpAirSysHot = CpAirZn;
1226 2 : CpAirSysCold = CpAirZn;
1227 :
1228 : // calculate supply air flow rate based on user specified OA requirement
1229 2 : this->CalcOAMassFlow(state, MassFlowBasedOnOA, AirLoopOAFrac);
1230 :
1231 : // Then depending on if the Load is for heating or cooling it is handled differently. First
1232 : // the massflow rate of either heating or cooling is determined to meet the entire load. Then
1233 : // if the massflow is below the minimum or greater than the Max it is set to either the Min
1234 : // or the Max as specified for the VAV model.
1235 2 : if (this->availSched->getCurrentVal() == 0.0) {
1236 : // System is Off set massflow to 0.0
1237 0 : MassFlow = 0.0;
1238 :
1239 2 : } else if ((QTotLoad > 0.0) && (this->dd_airterminalHotAirInlet.AirMassFlowRateMaxAvail > 0.0)) {
1240 : // Then heating is needed
1241 : // Next check for the denominator equal to zero
1242 2 : if (std::abs((CpAirSysHot * this->dd_airterminalHotAirInlet.AirTemp) - (CpAirZn * state.dataLoopNodes->Node(ZoneNodeNum).Temp)) /
1243 2 : CpAirZn >
1244 : HVAC::SmallTempDiff) {
1245 2 : MassFlow = QTotLoad / (CpAirSysHot * this->dd_airterminalHotAirInlet.AirTemp - CpAirZn * state.dataLoopNodes->Node(ZoneNodeNum).Temp);
1246 : } else {
1247 : // If denominator tends to zero then mass flow would go to infinity thus set to the max for this iteration
1248 0 : MassFlow = this->dd_airterminalHotAirInlet.AirMassFlowRateMaxAvail;
1249 : }
1250 : // Check to see if the flow is < the Min or > the Max air Fraction to the zone; then set to min or max
1251 2 : if (MassFlow <= (this->dd_airterminalHotAirInlet.AirMassFlowRateMax * this->ZoneMinAirFrac)) {
1252 2 : MassFlow = this->dd_airterminalHotAirInlet.AirMassFlowRateMax * this->ZoneMinAirFrac;
1253 2 : MassFlow = max(MassFlow, this->dd_airterminalHotAirInlet.AirMassFlowRateMinAvail);
1254 0 : } else if (MassFlow >= this->dd_airterminalHotAirInlet.AirMassFlowRateMaxAvail) {
1255 0 : MassFlow = this->dd_airterminalHotAirInlet.AirMassFlowRateMaxAvail;
1256 : }
1257 :
1258 : // Apply the zone maximum outdoor air fraction for VAV boxes - a TRACE feature
1259 2 : if (state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneNum).SupplyAirAdjustFactor > 1.0) {
1260 0 : MassFlow *= state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneNum).SupplyAirAdjustFactor;
1261 : }
1262 :
1263 2 : MassFlow = max(MassFlow, MassFlowBasedOnOA);
1264 2 : MassFlow = min(MassFlow, this->dd_airterminalHotAirInlet.AirMassFlowRateMaxAvail);
1265 :
1266 0 : } else if ((QTotLoad < 0.0) && (this->dd_airterminalColdAirInlet.AirMassFlowRateMaxAvail > 0.0)) {
1267 : // Then cooling is required
1268 : // Next check for the denominator equal to zero
1269 0 : if (std::abs((CpAirSysCold * this->dd_airterminalColdAirInlet.AirTemp) - (CpAirZn * state.dataLoopNodes->Node(ZoneNodeNum).Temp)) /
1270 0 : CpAirZn >
1271 : HVAC::SmallTempDiff) {
1272 0 : MassFlow =
1273 0 : QTotLoad / (CpAirSysCold * this->dd_airterminalColdAirInlet.AirTemp - CpAirZn * state.dataLoopNodes->Node(ZoneNodeNum).Temp);
1274 : } else {
1275 : // If denominator tends to zero then mass flow would go to infinity thus set to the max for this iteration
1276 0 : MassFlow = this->dd_airterminalColdAirInlet.AirMassFlowRateMaxAvail;
1277 : }
1278 :
1279 : // Check to see if the flow is < the Min or > the Max air Fraction to the zone; then set to min or max
1280 0 : if ((MassFlow <= (this->dd_airterminalColdAirInlet.AirMassFlowRateMax * this->ZoneMinAirFrac)) && (MassFlow >= 0.0)) {
1281 0 : MassFlow = this->dd_airterminalColdAirInlet.AirMassFlowRateMax * this->ZoneMinAirFrac;
1282 0 : MassFlow = max(MassFlow, this->dd_airterminalColdAirInlet.AirMassFlowRateMinAvail);
1283 0 : } else if (MassFlow < 0.0) {
1284 0 : MassFlow = this->dd_airterminalColdAirInlet.AirMassFlowRateMaxAvail;
1285 0 : } else if (MassFlow >= this->dd_airterminalColdAirInlet.AirMassFlowRateMaxAvail) {
1286 0 : MassFlow = this->dd_airterminalColdAirInlet.AirMassFlowRateMaxAvail;
1287 : }
1288 :
1289 : // Apply the zone maximum outdoor air fraction for VAV boxes - a TRACE feature
1290 0 : if (state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneNum).SupplyAirAdjustFactor > 1.0) {
1291 0 : MassFlow *= state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneNum).SupplyAirAdjustFactor;
1292 : }
1293 :
1294 0 : MassFlow = max(MassFlow, MassFlowBasedOnOA);
1295 0 : MassFlow = min(MassFlow, this->dd_airterminalColdAirInlet.AirMassFlowRateMaxAvail);
1296 :
1297 0 : } else if ((this->dd_airterminalHotAirInlet.AirMassFlowRateMaxAvail > 0.0) ||
1298 0 : (this->dd_airterminalColdAirInlet.AirMassFlowRateMaxAvail > 0.0)) {
1299 : // No Load on Zone set to mixed condition
1300 0 : MassFlow = (this->dd_airterminalHotAirInlet.AirMassFlowRateMax / 2.0) * this->ZoneMinAirFrac +
1301 0 : this->dd_airterminalColdAirInlet.AirMassFlowRateMax / 2.0 * this->ZoneMinAirFrac;
1302 :
1303 : // Apply the zone maximum outdoor air fraction for VAV boxes - a TRACE feature
1304 0 : if (state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneNum).SupplyAirAdjustFactor > 1.0) {
1305 0 : MassFlow *= state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneNum).SupplyAirAdjustFactor;
1306 : }
1307 :
1308 0 : MassFlow = max(MassFlow, MassFlowBasedOnOA);
1309 0 : MassFlow =
1310 0 : min(MassFlow, (this->dd_airterminalHotAirInlet.AirMassFlowRateMaxAvail + this->dd_airterminalColdAirInlet.AirMassFlowRateMaxAvail));
1311 :
1312 : } else {
1313 : // System is Off set massflow to 0.0
1314 0 : MassFlow = 0.0;
1315 : }
1316 :
1317 : // Now the massflow for heating or cooling has been determined and if the massflow was reset to the
1318 : // Min or Max we will need to mix the hot and cold deck to meet the zone load. Knowing the enthalpy
1319 : // of the zone and the hot and cold air flows we can determine exactly by using the Energy and Continuity
1320 : // Eqns. Of course we have to make sure that we are within the Min and Max flow conditions.
1321 2 : if (MassFlow > HVAC::SmallMassFlow) {
1322 : // Determine the enthalpy required from Zone enthalpy and the zone load.
1323 2 : QZnReq = QTotLoad + MassFlow * CpAirZn * state.dataLoopNodes->Node(ZoneNodeNum).Temp;
1324 : // Using the known enthalpies the cold air inlet mass flow is determined. If the enthalpy of the hot and cold
1325 : // air streams are equal the IF-Then block handles that condition.
1326 2 : if (std::abs(this->dd_airterminalColdAirInlet.AirTemp - this->dd_airterminalHotAirInlet.AirTemp) > HVAC::SmallTempDiff) {
1327 : // Calculate the Cold air mass flow rate
1328 2 : this->dd_airterminalColdAirInlet.AirMassFlowRate =
1329 2 : (QZnReq - MassFlow * CpAirSysHot * this->dd_airterminalHotAirInlet.AirTemp) /
1330 2 : (CpAirSysCold * this->dd_airterminalColdAirInlet.AirTemp - CpAirSysHot * this->dd_airterminalHotAirInlet.AirTemp);
1331 0 : } else if ((QTotLoad > 0.0) && (this->dd_airterminalHotAirInlet.AirMassFlowRate > 0.0)) {
1332 0 : this->dd_airterminalColdAirInlet.AirMassFlowRate = 0.0;
1333 : } else {
1334 0 : this->dd_airterminalColdAirInlet.AirMassFlowRate = MassFlow;
1335 : }
1336 :
1337 : // Need to make sure that the flows are within limits
1338 2 : if (this->dd_airterminalColdAirInlet.AirMassFlowRate > this->dd_airterminalColdAirInlet.AirMassFlowRateMaxAvail) {
1339 2 : this->dd_airterminalColdAirInlet.AirMassFlowRate = this->dd_airterminalColdAirInlet.AirMassFlowRateMaxAvail;
1340 :
1341 : // These are shutoff boxes for either the hot or the cold, therfore one side or other can = 0.0
1342 0 : } else if (this->dd_airterminalColdAirInlet.AirMassFlowRate < 0.0) {
1343 0 : this->dd_airterminalColdAirInlet.AirMassFlowRate = 0.0;
1344 0 : } else if (this->dd_airterminalColdAirInlet.AirMassFlowRate > MassFlow) {
1345 0 : this->dd_airterminalColdAirInlet.AirMassFlowRate = MassFlow;
1346 : }
1347 : // Using Mass Continuity to determine the other duct flow quantity
1348 2 : this->dd_airterminalHotAirInlet.AirMassFlowRate = MassFlow - this->dd_airterminalColdAirInlet.AirMassFlowRate;
1349 :
1350 2 : if (this->dd_airterminalHotAirInlet.AirMassFlowRate < DualDuctMassFlowSetToler) {
1351 0 : this->dd_airterminalHotAirInlet.AirMassFlowRate = 0.0;
1352 0 : this->dd_airterminalColdAirInlet.AirMassFlowRate = MassFlow;
1353 2 : } else if (this->dd_airterminalColdAirInlet.AirMassFlowRate < DualDuctMassFlowSetToler) {
1354 2 : this->dd_airterminalColdAirInlet.AirMassFlowRate = 0.0;
1355 2 : this->dd_airterminalHotAirInlet.AirMassFlowRate = MassFlow;
1356 : }
1357 :
1358 : // After the flow rates are determined the properties are calculated.
1359 2 : HumRat = (this->dd_airterminalHotAirInlet.AirHumRat * this->dd_airterminalHotAirInlet.AirMassFlowRate +
1360 2 : this->dd_airterminalColdAirInlet.AirHumRat * this->dd_airterminalColdAirInlet.AirMassFlowRate) /
1361 : MassFlow;
1362 2 : Enthalpy = (this->dd_airterminalHotAirInlet.AirEnthalpy * this->dd_airterminalHotAirInlet.AirMassFlowRate +
1363 2 : this->dd_airterminalColdAirInlet.AirEnthalpy * this->dd_airterminalColdAirInlet.AirMassFlowRate) /
1364 : MassFlow;
1365 :
1366 : // IF the system is OFF the properties are calculated for this special case.
1367 : } else {
1368 0 : this->dd_airterminalColdAirInlet.AirMassFlowRate = 0.0;
1369 0 : this->dd_airterminalHotAirInlet.AirMassFlowRate = 0.0;
1370 0 : HumRat = (this->dd_airterminalHotAirInlet.AirHumRat + this->dd_airterminalColdAirInlet.AirHumRat) / 2.0;
1371 0 : Enthalpy = (this->dd_airterminalHotAirInlet.AirEnthalpy + this->dd_airterminalColdAirInlet.AirEnthalpy) / 2.0;
1372 : }
1373 2 : Temperature = Psychrometrics::PsyTdbFnHW(Enthalpy, HumRat);
1374 :
1375 2 : this->dd_airterminalOutlet.AirTemp = Temperature;
1376 2 : this->dd_airterminalOutlet.AirHumRat = HumRat;
1377 2 : this->dd_airterminalOutlet.AirMassFlowRate = MassFlow;
1378 2 : this->dd_airterminalOutlet.AirMassFlowRateMaxAvail = MassFlow;
1379 2 : this->dd_airterminalOutlet.AirMassFlowRateMinAvail = this->ZoneMinAirFrac * this->dd_airterminalHotAirInlet.AirMassFlowRateMax;
1380 2 : this->dd_airterminalOutlet.AirEnthalpy = Enthalpy;
1381 :
1382 : // Calculate the hot and cold damper position in %
1383 2 : if ((this->dd_airterminalHotAirInlet.AirMassFlowRateMax == 0.0) || (this->dd_airterminalColdAirInlet.AirMassFlowRateMax == 0.0)) {
1384 0 : this->ColdAirDamperPosition = 0.0;
1385 0 : this->HotAirDamperPosition = 0.0;
1386 : } else {
1387 2 : this->ColdAirDamperPosition = this->dd_airterminalColdAirInlet.AirMassFlowRate / this->dd_airterminalColdAirInlet.AirMassFlowRateMax;
1388 2 : this->HotAirDamperPosition = this->dd_airterminalHotAirInlet.AirMassFlowRate / this->dd_airterminalHotAirInlet.AirMassFlowRateMax;
1389 : }
1390 2 : }
1391 :
1392 0 : void DualDuctAirTerminal::SimDualDuctVAVOutdoorAir(EnergyPlusData &state, int const ZoneNum, int const ZoneNodeNum)
1393 : {
1394 :
1395 : // SUBROUTINE INFORMATION:
1396 : // AUTHOR Clayton Miller
1397 : // DATE WRITTEN Aug 2010
1398 : // MODIFIED B. Griffith, Dec 2010, major rework
1399 : // RE-ENGINEERED na
1400 :
1401 : // PURPOSE OF THIS SUBROUTINE:
1402 : // Designed to accommodate for systems with outdoor air (OA) and recirculated air (RA)
1403 : // as two separate air streams to controlled at the zone level in a dual duct system.
1404 :
1405 : // METHODOLOGY EMPLOYED:
1406 : // The terminal unit is be designed to set the airflow of the of the OA stream at the zone
1407 : // level based on the zonal ventilation requirements and the RA stream flowrate of recirculated
1408 : // cooling air stream in order to meet the remaining thermal load.
1409 : // If the zone calls for cooling but the inlet air temperature is too warm, recirc side set to zero
1410 : // if the zone calls for heating and the inlet air is warm enough, modulate damper to meet load
1411 : // if the zone calls for heating and the inlet air is too cold, zero flow (will not control sans reheat)
1412 :
1413 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1414 : Real64 MassFlowMax; // [kg/sec] Maximum Mass Flow Rate from OA and Recirc Inlets
1415 : Real64 HumRat; // [Kg Moisture / Kg dry air]
1416 : Real64 Enthalpy; // [Watts]
1417 : Real64 Temperature; // [C]
1418 : Real64 QTotLoadRemain; // [W]
1419 : Real64 QtoHeatSPRemain; // [W]
1420 : Real64 QtoCoolSPRemain; // [W]
1421 : // REAL(r64) :: QTotRemainAdjust ! [W]
1422 : Real64 QtoHeatSPRemainAdjust; // [W]
1423 : Real64 QtoCoolSPRemainAdjust; // [W]
1424 : Real64 QOALoadToHeatSP; // [W]
1425 : Real64 QOALoadToCoolSP; // [W]
1426 : Real64 QOALoad; // Amount of cooling load accounted for by OA Stream [W]
1427 : Real64 QRALoad; // Amount of cooling load accounted for by Recirc Stream [W]
1428 : Real64 CpAirZn; // specific heat of zone air
1429 : Real64 CpAirSysOA; // specific heat of outdoor air
1430 : Real64 CpAirSysRA; // specific heat of recirculated air
1431 : Real64 OAMassFlow; // Supply air flow rate based on minimum OA requirement - for printing
1432 : Real64 TotMassFlow; // [kg/sec] Total Mass Flow Rate from OA and Recirc Inlets
1433 : int OAInletNodeNum;
1434 : int RecircInletNodeNum;
1435 :
1436 0 : OAInletNodeNum = this->OAInletNodeNum;
1437 0 : if (this->RecircIsUsed) {
1438 0 : RecircInletNodeNum = this->RecircAirInletNodeNum;
1439 : }
1440 : // Calculate required ventilation air flow rate based on user specified OA requirement
1441 0 : this->CalcOAOnlyMassFlow(state, OAMassFlow);
1442 :
1443 : // The calculated load from the Heat Balance, adjusted for any equipment sequenced before terminal
1444 0 : QTotLoadRemain = state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneNum).RemainingOutputRequired;
1445 0 : QtoHeatSPRemain = state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneNum).RemainingOutputReqToHeatSP;
1446 0 : QtoCoolSPRemain = state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneNum).RemainingOutputReqToCoolSP;
1447 :
1448 : // Calculate all of the required Cp's
1449 0 : CpAirZn = Psychrometrics::PsyCpAirFnW(state.dataLoopNodes->Node(ZoneNodeNum).HumRat);
1450 0 : CpAirSysOA = Psychrometrics::PsyCpAirFnW(state.dataLoopNodes->Node(OAInletNodeNum).HumRat);
1451 0 : if (this->RecircIsUsed) CpAirSysRA = Psychrometrics::PsyCpAirFnW(state.dataLoopNodes->Node(RecircInletNodeNum).HumRat);
1452 :
1453 : // Set the OA Damper to the calculated ventilation flow rate
1454 0 : this->dd_airterminalOAInlet.AirMassFlowRate = OAMassFlow;
1455 : // Need to make sure that the OA flows are within limits
1456 0 : if (this->dd_airterminalOAInlet.AirMassFlowRate > this->dd_airterminalOAInlet.AirMassFlowRateMaxAvail) {
1457 0 : this->dd_airterminalOAInlet.AirMassFlowRate = this->dd_airterminalOAInlet.AirMassFlowRateMaxAvail;
1458 0 : } else if (this->dd_airterminalOAInlet.AirMassFlowRate < 0.0) {
1459 0 : this->dd_airterminalOAInlet.AirMassFlowRate = 0.0;
1460 : }
1461 :
1462 : //..Find the amount of load that the OAMassFlow accounted for
1463 0 : if (std::abs((CpAirSysOA * this->dd_airterminalOAInlet.AirTemp) - (CpAirZn * state.dataLoopNodes->Node(ZoneNodeNum).Temp)) / CpAirZn >
1464 : HVAC::SmallTempDiff) {
1465 0 : QOALoad = this->dd_airterminalOAInlet.AirMassFlowRate *
1466 0 : (CpAirSysOA * this->dd_airterminalOAInlet.AirTemp - CpAirZn * state.dataLoopNodes->Node(ZoneNodeNum).Temp);
1467 :
1468 0 : auto const &zoneTstatSetpt = state.dataHeatBalFanSys->zoneTstatSetpts(ZoneNum);
1469 0 : QOALoadToHeatSP =
1470 0 : this->dd_airterminalOAInlet.AirMassFlowRate * (CpAirSysOA * this->dd_airterminalOAInlet.AirTemp - CpAirZn * zoneTstatSetpt.setptLo);
1471 0 : QOALoadToCoolSP =
1472 0 : this->dd_airterminalOAInlet.AirMassFlowRate * (CpAirSysOA * this->dd_airterminalOAInlet.AirTemp - CpAirZn * zoneTstatSetpt.setptHi);
1473 :
1474 : } else {
1475 0 : QOALoad = 0.0;
1476 0 : QOALoadToHeatSP = 0.0;
1477 0 : QOALoadToCoolSP = 0.0;
1478 : }
1479 :
1480 0 : if (this->RecircIsUsed) {
1481 :
1482 : // correct load for recirc side to account for impact of OA side
1483 : // QTotRemainAdjust = QTotLoadRemain - QOALoad
1484 0 : QtoHeatSPRemainAdjust = QtoHeatSPRemain - QOALoadToHeatSP;
1485 0 : QtoCoolSPRemainAdjust = QtoCoolSPRemain - QOALoadToCoolSP;
1486 :
1487 0 : if (QtoCoolSPRemainAdjust < 0.0) {
1488 0 : QRALoad = QtoCoolSPRemainAdjust;
1489 0 : } else if (QtoHeatSPRemainAdjust > 0.0) {
1490 0 : QRALoad = QtoHeatSPRemainAdjust;
1491 : } else {
1492 0 : QRALoad = 0.0;
1493 : }
1494 :
1495 0 : if (QRALoad < 0.0) { // cooling
1496 0 : if ((this->dd_airterminalRecircAirInlet.AirTemp - state.dataLoopNodes->Node(ZoneNodeNum).Temp) < -0.5) { // can cool
1497 : // Find the Mass Flow Rate of the RA Stream needed to meet the zone cooling load
1498 0 : if (std::abs((CpAirSysRA * this->dd_airterminalRecircAirInlet.AirTemp) -
1499 0 : (CpAirZn * state.dataLoopNodes->Node(ZoneNodeNum).Temp)) /
1500 0 : CpAirZn >
1501 : HVAC::SmallTempDiff) {
1502 0 : this->dd_airterminalRecircAirInlet.AirMassFlowRate = QRALoad / (CpAirSysRA * this->dd_airterminalRecircAirInlet.AirTemp -
1503 0 : CpAirZn * state.dataLoopNodes->Node(ZoneNodeNum).Temp);
1504 : }
1505 : } else {
1506 0 : this->dd_airterminalRecircAirInlet.AirMassFlowRate = 0.0;
1507 : }
1508 :
1509 : } else { // heating or none needed.
1510 0 : this->dd_airterminalRecircAirInlet.AirMassFlowRate = 0.0;
1511 : }
1512 :
1513 : // Need to make sure that the RA flows are within limits
1514 0 : if (this->dd_airterminalRecircAirInlet.AirMassFlowRate > this->dd_airterminalRecircAirInlet.AirMassFlowRateMaxAvail) {
1515 0 : this->dd_airterminalRecircAirInlet.AirMassFlowRate = this->dd_airterminalRecircAirInlet.AirMassFlowRateMaxAvail;
1516 : // These are shutoff boxes for either the hot or the cold, therfore one side or other can = 0.0
1517 0 : } else if (this->dd_airterminalRecircAirInlet.AirMassFlowRate < 0.0) {
1518 0 : this->dd_airterminalRecircAirInlet.AirMassFlowRate = 0.0;
1519 : }
1520 :
1521 : } else {
1522 0 : this->dd_airterminalRecircAirInlet.AirMassFlowRate = 0.0;
1523 0 : this->dd_airterminalRecircAirInlet.AirMassFlowRateMaxAvail = 0.0;
1524 : } // recirc used
1525 :
1526 : // look for bang-bang condition: flow rate oscillating between 2 values during the air loop / zone
1527 : // equipment iteration. If detected, set flow rate to previous value.
1528 0 : if (((std::abs(this->dd_airterminalRecircAirInlet.AirMassFlowRate - this->dd_airterminalRecircAirInlet.AirMassFlowRateHist2) <
1529 0 : this->dd_airterminalRecircAirInlet.AirMassFlowDiffMag) ||
1530 0 : (std::abs(this->dd_airterminalRecircAirInlet.AirMassFlowRate - this->dd_airterminalRecircAirInlet.AirMassFlowRateHist3) <
1531 0 : this->dd_airterminalRecircAirInlet.AirMassFlowDiffMag)) &&
1532 0 : (std::abs(this->dd_airterminalRecircAirInlet.AirMassFlowRate - this->dd_airterminalRecircAirInlet.AirMassFlowRateHist1) >=
1533 0 : this->dd_airterminalRecircAirInlet.AirMassFlowDiffMag)) {
1534 0 : if (this->dd_airterminalRecircAirInlet.AirMassFlowRate > 0.0) {
1535 0 : this->dd_airterminalRecircAirInlet.AirMassFlowRate = this->dd_airterminalRecircAirInlet.AirMassFlowRateHist1;
1536 : }
1537 : }
1538 :
1539 : // Find the Max Box Flow Rate.
1540 0 : MassFlowMax = this->dd_airterminalOAInlet.AirMassFlowRateMaxAvail + this->dd_airterminalRecircAirInlet.AirMassFlowRateMaxAvail;
1541 0 : if (this->availSched->getCurrentVal() > 0.0) {
1542 0 : TotMassFlow = this->dd_airterminalOAInlet.AirMassFlowRate + this->dd_airterminalRecircAirInlet.AirMassFlowRate;
1543 : } else {
1544 0 : TotMassFlow = 0.0;
1545 : }
1546 :
1547 0 : if (TotMassFlow > HVAC::SmallMassFlow) {
1548 :
1549 : // If the sum of the two air streams' flow is greater than the Max Box Flow Rate then reset the RA Stream
1550 0 : if (TotMassFlow > MassFlowMax) {
1551 0 : this->dd_airterminalRecircAirInlet.AirMassFlowRate = MassFlowMax - this->dd_airterminalOAInlet.AirMassFlowRate;
1552 : }
1553 : // After the flow rates are determined the properties are calculated.
1554 0 : TotMassFlow = this->dd_airterminalOAInlet.AirMassFlowRate + this->dd_airterminalRecircAirInlet.AirMassFlowRate;
1555 0 : if (TotMassFlow > HVAC::SmallMassFlow) {
1556 0 : HumRat = (this->dd_airterminalOAInlet.AirHumRat * this->dd_airterminalOAInlet.AirMassFlowRate +
1557 0 : this->dd_airterminalRecircAirInlet.AirHumRat * this->dd_airterminalRecircAirInlet.AirMassFlowRate) /
1558 : TotMassFlow;
1559 0 : Enthalpy = (this->dd_airterminalOAInlet.AirEnthalpy * this->dd_airterminalOAInlet.AirMassFlowRate +
1560 0 : this->dd_airterminalRecircAirInlet.AirEnthalpy * this->dd_airterminalRecircAirInlet.AirMassFlowRate) /
1561 : TotMassFlow;
1562 : } else {
1563 0 : HumRat = (this->dd_airterminalRecircAirInlet.AirHumRat + this->dd_airterminalOAInlet.AirHumRat) / 2.0;
1564 0 : Enthalpy = (this->dd_airterminalRecircAirInlet.AirEnthalpy + this->dd_airterminalOAInlet.AirEnthalpy) / 2.0;
1565 : }
1566 : } else {
1567 :
1568 : // The Max Box Flow Rate is zero and the box is off.
1569 0 : this->dd_airterminalRecircAirInlet.AirMassFlowRate = 0.0;
1570 0 : this->dd_airterminalOAInlet.AirMassFlowRate = 0.0;
1571 0 : HumRat = (this->dd_airterminalRecircAirInlet.AirHumRat + this->dd_airterminalOAInlet.AirHumRat) / 2.0;
1572 0 : Enthalpy = (this->dd_airterminalRecircAirInlet.AirEnthalpy + this->dd_airterminalOAInlet.AirEnthalpy) / 2.0;
1573 : }
1574 :
1575 0 : Temperature = Psychrometrics::PsyTdbFnHW(Enthalpy, HumRat);
1576 :
1577 0 : this->dd_airterminalOutlet.AirTemp = Temperature;
1578 0 : this->dd_airterminalOutlet.AirHumRat = HumRat;
1579 0 : this->dd_airterminalOutlet.AirMassFlowRate = TotMassFlow;
1580 0 : this->dd_airterminalOutlet.AirMassFlowRateMaxAvail = MassFlowMax;
1581 0 : this->dd_airterminalOutlet.AirEnthalpy = Enthalpy;
1582 :
1583 : // Calculate the OA and RA damper position in %
1584 0 : if (this->RecircIsUsed) {
1585 0 : if (this->dd_airterminalRecircAirInlet.AirMassFlowRateMax == 0.0) { // protect div by zero
1586 0 : this->RecircAirDamperPosition = 0.0;
1587 : } else {
1588 0 : this->RecircAirDamperPosition =
1589 0 : this->dd_airterminalRecircAirInlet.AirMassFlowRate / this->dd_airterminalRecircAirInlet.AirMassFlowRateMax;
1590 : }
1591 : }
1592 :
1593 0 : if (this->dd_airterminalOAInlet.AirMassFlowRateMax == 0.0) { // protect div by zero
1594 0 : this->OADamperPosition = 0.0;
1595 : } else {
1596 0 : this->OADamperPosition = this->dd_airterminalOAInlet.AirMassFlowRate / this->dd_airterminalOAInlet.AirMassFlowRateMax;
1597 : }
1598 :
1599 : // Calculate OAFraction of mixed air after the box
1600 0 : if (TotMassFlow > 0) {
1601 0 : if (this->RecircIsUsed) {
1602 0 : if (this->dd_airterminalOAInlet.AirMassFlowRate == 0.0) {
1603 0 : this->OAFraction = 0.0;
1604 0 : } else if (this->dd_airterminalRecircAirInlet.AirMassFlowRate == 0.0) {
1605 0 : this->OAFraction = 1.0;
1606 : } else {
1607 0 : this->OAFraction = this->dd_airterminalOAInlet.AirMassFlowRate / TotMassFlow;
1608 : }
1609 : } else {
1610 0 : this->OAFraction = 1.0;
1611 : }
1612 : } else {
1613 0 : this->OAFraction = 0.0;
1614 : }
1615 :
1616 0 : this->dd_airterminalRecircAirInlet.AirMassFlowRateHist3 = this->dd_airterminalRecircAirInlet.AirMassFlowRateHist2;
1617 0 : this->dd_airterminalRecircAirInlet.AirMassFlowRateHist2 = this->dd_airterminalRecircAirInlet.AirMassFlowRateHist1;
1618 0 : this->dd_airterminalRecircAirInlet.AirMassFlowRateHist1 = this->dd_airterminalRecircAirInlet.AirMassFlowRate;
1619 0 : }
1620 :
1621 3 : void DualDuctAirTerminal::CalcOAMassFlow(EnergyPlusData &state, // NOLINT(readability-make-member-function-const)
1622 : Real64 &SAMassFlow, // outside air based on optional user input
1623 : Real64 &AirLoopOAFrac // outside air based on optional user input
1624 : )
1625 : {
1626 :
1627 : // FUNCTION INFORMATION:
1628 : // AUTHOR R. Raustad (FSEC)
1629 : // DATE WRITTEN Mar 2010
1630 : // MODIFIED Mangesh Basarkar, 06/2011: Modifying outside air based on airloop DCV flag
1631 : // RE-ENGINEERED na
1632 :
1633 : // PURPOSE OF THIS FUNCTION:
1634 : // Calculates the amount of outside air required based on optional user input.
1635 : // Zone multipliers are included and are applied in GetInput.
1636 :
1637 : // METHODOLOGY EMPLOYED:
1638 : // User input defines method used to calculate OA.
1639 :
1640 : // initialize OA flow rate and OA report variable
1641 3 : SAMassFlow = 0.0;
1642 3 : AirLoopOAFrac = 0.0;
1643 :
1644 : // Calculate the amount of OA based on optional user inputs
1645 3 : if (AirLoopNum > 0) {
1646 1 : AirLoopOAFrac = state.dataAirLoop->AirLoopFlow(AirLoopNum).OAFrac;
1647 : // If no additional input from user, RETURN from subroutine
1648 1 : if (this->NoOAFlowInputFromUser) return;
1649 : // Calculate outdoor air flow rate, zone multipliers are applied in GetInput
1650 1 : if (AirLoopOAFrac > 0.0) {
1651 1 : bool constexpr UseMinOASchFlag(true); // Always use min OA schedule in calculations.
1652 : Real64 const OAVolumeFlowRate =
1653 2 : DataSizing::calcDesignSpecificationOutdoorAir(state,
1654 : this->OARequirementsPtr,
1655 : this->CtrlZoneNum,
1656 1 : state.dataAirLoop->AirLoopControlInfo(AirLoopNum).AirLoopDCVFlag,
1657 : UseMinOASchFlag);
1658 1 : Real64 const OAMassFlow = OAVolumeFlowRate * state.dataEnvrn->StdRhoAir;
1659 :
1660 : // convert OA mass flow rate to supply air flow rate based on air loop OA fraction
1661 1 : SAMassFlow = OAMassFlow / AirLoopOAFrac;
1662 : }
1663 : }
1664 : }
1665 :
1666 1 : void DualDuctAirTerminal::CalcOAOnlyMassFlow(EnergyPlusData &state, // NOLINT(readability-make-member-function-const)
1667 : Real64 &OAMassFlow, // outside air flow from user input kg/s
1668 : ObjexxFCL::Optional<Real64> MaxOAVolFlow // design level for outside air m3/s
1669 : )
1670 : {
1671 :
1672 : // FUNCTION INFORMATION:
1673 : // AUTHOR C. Miller (Mod of CaclOAMassFlow by R. Raustad (FSEC))
1674 : // DATE WRITTEN Aug 2010
1675 : // MODIFIED B. Griffith, Dec 2010 clean up, sizing optional, scheduled OA
1676 : // RE-ENGINEERED na
1677 :
1678 : // PURPOSE OF THIS FUNCTION:
1679 : // Calculates the amount of outside air required based on optional user input. Returns
1680 : // ONLY calculated OAMassFlow without consideration of AirLoopOAFrac. Used for
1681 : // the DualDuct:VAV:OutdoorAir object which does not mix OA with RA
1682 :
1683 : // METHODOLOGY EMPLOYED:
1684 : // User input defines method used to calculate OA.
1685 :
1686 : // Calculate the amount of OA based on optional user inputs
1687 1 : OAMassFlow = 0.0;
1688 :
1689 : // If no additional input from user, RETURN from subroutine
1690 1 : if (this->NoOAFlowInputFromUser) {
1691 0 : ShowSevereError(
1692 : state,
1693 0 : format("CalcOAOnlyMassFlow: Problem in AirTerminal:DualDuct:VAV:OutdoorAir = {}, check outdoor air specification", this->Name));
1694 0 : if (present(MaxOAVolFlow)) MaxOAVolFlow = 0.0;
1695 0 : return;
1696 : }
1697 :
1698 1 : bool UseOccSchFlag = this->OAPerPersonMode == PerPersonMode::DCVByCurrentLevel; // TRUE = use actual occupancy, FALSE = use total zone people
1699 1 : bool PerPersonNotSet = this->OAPerPersonMode != PerPersonMode::DCVByCurrentLevel && this->OAPerPersonMode != PerPersonMode::ByDesignLevel;
1700 :
1701 1 : bool constexpr UseMinOASchFlag(true); // Always use min OA schedule in calculations.
1702 1 : Real64 OAVolumeFlowRate = DataSizing::calcDesignSpecificationOutdoorAir(state,
1703 : this->OARequirementsPtr,
1704 : this->CtrlZoneNum,
1705 : UseOccSchFlag,
1706 : UseMinOASchFlag,
1707 1 : PerPersonNotSet); // outside air volume flow rate (m3/s)
1708 :
1709 1 : OAMassFlow = OAVolumeFlowRate * state.dataEnvrn->StdRhoAir;
1710 :
1711 1 : if (present(MaxOAVolFlow)) {
1712 0 : OAVolumeFlowRate = DataSizing::calcDesignSpecificationOutdoorAir(
1713 : state, this->OARequirementsPtr, this->CtrlZoneNum, UseOccSchFlag, UseMinOASchFlag, false, true);
1714 0 : MaxOAVolFlow = OAVolumeFlowRate;
1715 : }
1716 : }
1717 :
1718 0 : void DualDuctAirTerminal::UpdateDualDuct(EnergyPlusData &state)
1719 : {
1720 :
1721 : // SUBROUTINE INFORMATION:
1722 : // AUTHOR Richard J. Liesen
1723 : // DATE WRITTEN February 2000
1724 : // MODIFIED Aug 2010 Clayton Miller - Added DualDuctVAVOutdoorAir
1725 : // RE-ENGINEERED na
1726 :
1727 : // PURPOSE OF THIS SUBROUTINE:
1728 : // This subroutine updates the dampers.
1729 :
1730 0 : if (this->DamperType == DualDuctDamper::ConstantVolume || this->DamperType == DualDuctDamper::VariableVolume) {
1731 :
1732 0 : int OutletNode = this->OutletNodeNum;
1733 0 : int HotInletNode = this->HotAirInletNodeNum;
1734 0 : int ColdInletNode = this->ColdAirInletNodeNum;
1735 :
1736 : // Set the outlet air nodes of the Damper
1737 0 : state.dataLoopNodes->Node(HotInletNode).MassFlowRate = this->dd_airterminalHotAirInlet.AirMassFlowRate;
1738 0 : state.dataLoopNodes->Node(ColdInletNode).MassFlowRate = this->dd_airterminalColdAirInlet.AirMassFlowRate;
1739 0 : state.dataLoopNodes->Node(OutletNode).MassFlowRate = this->dd_airterminalOutlet.AirMassFlowRate;
1740 0 : state.dataLoopNodes->Node(OutletNode).MassFlowRateMaxAvail = this->dd_airterminalOutlet.AirMassFlowRate;
1741 0 : state.dataLoopNodes->Node(OutletNode).MassFlowRateMinAvail = this->dd_airterminalOutlet.AirMassFlowRateMinAvail;
1742 0 : state.dataLoopNodes->Node(OutletNode).Temp = this->dd_airterminalOutlet.AirTemp;
1743 0 : state.dataLoopNodes->Node(OutletNode).HumRat = this->dd_airterminalOutlet.AirHumRat;
1744 0 : state.dataLoopNodes->Node(OutletNode).Enthalpy = this->dd_airterminalOutlet.AirEnthalpy;
1745 : // Set the outlet nodes for properties that just pass through & not used
1746 : // FIX THIS LATER!!!!
1747 0 : state.dataLoopNodes->Node(OutletNode).Quality = state.dataLoopNodes->Node(HotInletNode).Quality;
1748 0 : state.dataLoopNodes->Node(OutletNode).Press = state.dataLoopNodes->Node(HotInletNode).Press;
1749 :
1750 0 : if (state.dataContaminantBalance->Contaminant.CO2Simulation) {
1751 0 : if (state.dataLoopNodes->Node(OutletNode).MassFlowRate > 0.0) {
1752 0 : state.dataLoopNodes->Node(OutletNode).CO2 =
1753 0 : (state.dataLoopNodes->Node(HotInletNode).CO2 * state.dataLoopNodes->Node(HotInletNode).MassFlowRate +
1754 0 : state.dataLoopNodes->Node(ColdInletNode).CO2 * state.dataLoopNodes->Node(ColdInletNode).MassFlowRate) /
1755 0 : state.dataLoopNodes->Node(OutletNode).MassFlowRate;
1756 : } else {
1757 0 : state.dataLoopNodes->Node(OutletNode).CO2 =
1758 0 : max(state.dataLoopNodes->Node(HotInletNode).CO2, state.dataLoopNodes->Node(ColdInletNode).CO2);
1759 : }
1760 : }
1761 0 : if (state.dataContaminantBalance->Contaminant.GenericContamSimulation) {
1762 0 : if (state.dataLoopNodes->Node(OutletNode).MassFlowRate > 0.0) {
1763 0 : state.dataLoopNodes->Node(OutletNode).GenContam =
1764 0 : (state.dataLoopNodes->Node(HotInletNode).GenContam * state.dataLoopNodes->Node(HotInletNode).MassFlowRate +
1765 0 : state.dataLoopNodes->Node(ColdInletNode).GenContam * state.dataLoopNodes->Node(ColdInletNode).MassFlowRate) /
1766 0 : state.dataLoopNodes->Node(OutletNode).MassFlowRate;
1767 : } else {
1768 0 : state.dataLoopNodes->Node(OutletNode).GenContam =
1769 0 : max(state.dataLoopNodes->Node(HotInletNode).GenContam, state.dataLoopNodes->Node(ColdInletNode).GenContam);
1770 : }
1771 : }
1772 :
1773 0 : this->CalcOutdoorAirVolumeFlowRate(state);
1774 :
1775 0 : } else if (this->DamperType == DualDuctDamper::OutdoorAir) {
1776 :
1777 0 : int OutletNode = this->OutletNodeNum;
1778 0 : int OAInletNode = this->OAInletNodeNum;
1779 : // Set the outlet air nodes of the Damper
1780 0 : state.dataLoopNodes->Node(OAInletNode).MassFlowRate = this->dd_airterminalOAInlet.AirMassFlowRate;
1781 0 : state.dataLoopNodes->Node(OutletNode).MassFlowRate = this->dd_airterminalOutlet.AirMassFlowRate;
1782 0 : state.dataLoopNodes->Node(OutletNode).MassFlowRateMaxAvail = this->dd_airterminalOutlet.AirMassFlowRate;
1783 0 : state.dataLoopNodes->Node(OutletNode).MassFlowRateMinAvail = this->dd_airterminalOutlet.AirMassFlowRateMinAvail;
1784 0 : state.dataLoopNodes->Node(OutletNode).Temp = this->dd_airterminalOutlet.AirTemp;
1785 0 : state.dataLoopNodes->Node(OutletNode).HumRat = this->dd_airterminalOutlet.AirHumRat;
1786 0 : state.dataLoopNodes->Node(OutletNode).Enthalpy = this->dd_airterminalOutlet.AirEnthalpy;
1787 : // Set the outlet nodes for properties that just pass through & not used
1788 : // FIX THIS LATER!!!!
1789 0 : state.dataLoopNodes->Node(OutletNode).Quality = state.dataLoopNodes->Node(OAInletNode).Quality;
1790 0 : state.dataLoopNodes->Node(OutletNode).Press = state.dataLoopNodes->Node(OAInletNode).Press;
1791 :
1792 0 : if (this->RecircIsUsed) {
1793 0 : int RAInletNode = this->RecircAirInletNodeNum;
1794 0 : state.dataLoopNodes->Node(RAInletNode).MassFlowRate = this->dd_airterminalRecircAirInlet.AirMassFlowRate;
1795 0 : if (state.dataLoopNodes->Node(OutletNode).MassFlowRate > 0.0) {
1796 0 : if (state.dataContaminantBalance->Contaminant.CO2Simulation) {
1797 0 : state.dataLoopNodes->Node(OutletNode).CO2 =
1798 0 : (state.dataLoopNodes->Node(OAInletNode).CO2 * state.dataLoopNodes->Node(OAInletNode).MassFlowRate +
1799 0 : state.dataLoopNodes->Node(RAInletNode).CO2 * state.dataLoopNodes->Node(RAInletNode).MassFlowRate) /
1800 0 : state.dataLoopNodes->Node(OutletNode).MassFlowRate;
1801 : }
1802 0 : if (state.dataContaminantBalance->Contaminant.GenericContamSimulation) {
1803 0 : state.dataLoopNodes->Node(OutletNode).GenContam =
1804 0 : (state.dataLoopNodes->Node(OAInletNode).GenContam * state.dataLoopNodes->Node(OAInletNode).MassFlowRate +
1805 0 : state.dataLoopNodes->Node(RAInletNode).GenContam * state.dataLoopNodes->Node(RAInletNode).MassFlowRate) /
1806 0 : state.dataLoopNodes->Node(OutletNode).MassFlowRate;
1807 : }
1808 : } else {
1809 0 : if (state.dataContaminantBalance->Contaminant.CO2Simulation) {
1810 0 : state.dataLoopNodes->Node(OutletNode).CO2 =
1811 0 : max(state.dataLoopNodes->Node(OAInletNode).CO2, state.dataLoopNodes->Node(RAInletNode).CO2);
1812 : }
1813 0 : if (state.dataContaminantBalance->Contaminant.GenericContamSimulation) {
1814 0 : state.dataLoopNodes->Node(OutletNode).GenContam =
1815 0 : max(state.dataLoopNodes->Node(OAInletNode).GenContam, state.dataLoopNodes->Node(RAInletNode).GenContam);
1816 : }
1817 : }
1818 :
1819 : } else {
1820 0 : if (state.dataContaminantBalance->Contaminant.CO2Simulation) {
1821 0 : state.dataLoopNodes->Node(OutletNode).CO2 = state.dataLoopNodes->Node(OAInletNode).CO2;
1822 : }
1823 0 : if (state.dataContaminantBalance->Contaminant.GenericContamSimulation) {
1824 0 : state.dataLoopNodes->Node(OutletNode).GenContam = state.dataLoopNodes->Node(OAInletNode).GenContam;
1825 : }
1826 : }
1827 : }
1828 0 : }
1829 :
1830 73 : void ReportDualDuctConnections(EnergyPlusData &state)
1831 : {
1832 :
1833 : // SUBROUTINE INFORMATION:
1834 : // AUTHOR Michael J. Witte
1835 : // DATE WRITTEN February 2004
1836 : // MODIFIED B. Griffith, DOAS VAV dual duct
1837 : // RE-ENGINEERED na
1838 :
1839 : // PURPOSE OF THIS SUBROUTINE:
1840 : // Report dual duct damper connections to the BND file.
1841 :
1842 : // Using/Aliasing
1843 73 : int NumPrimaryAirSys = state.dataHVACGlobal->NumPrimaryAirSys;
1844 :
1845 : // Formats
1846 : static constexpr std::string_view Format_100("! <#Dual Duct Damper Connections>,<Number of Dual Duct Damper Connections>");
1847 : static constexpr std::string_view Format_102(
1848 : "! <Dual Duct Damper>,<Dual Duct Damper Count>,<Dual Duct Damper Name>,<Inlet Node>,<Outlet Node>,<Inlet "
1849 : "Node Type>,<AirLoopHVAC Name>");
1850 :
1851 73 : if (!allocated(state.dataDualDuct->dd_airterminal))
1852 73 : return; // Autodesk Bug: Can arrive here with Damper unallocated (SimulateDualDuct not yet called) with NumDDAirTerminal either set >0 or
1853 : // uninitialized
1854 :
1855 : // Report Dual Duct Dampers to BND File
1856 0 : print(state.files.bnd, "{}\n", "! ===============================================================");
1857 0 : print(state.files.bnd, "{}\n", Format_100);
1858 0 : print(state.files.bnd, " #Dual Duct Damper Connections,{}\n", state.dataDualDuct->NumDDAirTerminal * 2);
1859 0 : print(state.files.bnd, "{}\n", Format_102);
1860 :
1861 0 : for (int Count1 = 1; Count1 <= state.dataDualDuct->NumDDAirTerminal; ++Count1) {
1862 :
1863 : // Determine if this damper is connected to a supply air path
1864 0 : int Found = 0;
1865 0 : int SupplyAirPathNum = 0;
1866 0 : for (int Count2 = 1; Count2 <= state.dataZoneEquip->NumSupplyAirPaths; ++Count2) {
1867 0 : SupplyAirPathNum = Count2;
1868 0 : Found = 0;
1869 0 : for (int Count3 = 1; Count3 <= state.dataZoneEquip->SupplyAirPath(Count2).NumOutletNodes; ++Count3) {
1870 0 : if (state.dataDualDuct->dd_airterminal(Count1).HotAirInletNodeNum ==
1871 0 : state.dataZoneEquip->SupplyAirPath(Count2).OutletNode(Count3))
1872 0 : Found = Count3;
1873 0 : if (state.dataDualDuct->dd_airterminal(Count1).ColdAirInletNodeNum ==
1874 0 : state.dataZoneEquip->SupplyAirPath(Count2).OutletNode(Count3))
1875 0 : Found = Count3;
1876 0 : if (state.dataDualDuct->dd_airterminal(Count1).OAInletNodeNum == state.dataZoneEquip->SupplyAirPath(Count2).OutletNode(Count3))
1877 0 : Found = Count3;
1878 0 : if (state.dataDualDuct->dd_airterminal(Count1).RecircAirInletNodeNum ==
1879 0 : state.dataZoneEquip->SupplyAirPath(Count2).OutletNode(Count3))
1880 0 : Found = Count3;
1881 : }
1882 0 : if (Found != 0) break;
1883 : }
1884 0 : if (Found == 0) SupplyAirPathNum = 0;
1885 :
1886 : // Determine which air loop this dual duct damper is connected to
1887 0 : Found = 0;
1888 0 : std::string ChrName;
1889 0 : for (int Count2 = 1; Count2 <= NumPrimaryAirSys; ++Count2) {
1890 0 : ChrName = state.dataAirLoop->AirToZoneNodeInfo(Count2).AirLoopName;
1891 0 : Found = 0;
1892 0 : for (int Count3 = 1; Count3 <= state.dataAirLoop->AirToZoneNodeInfo(Count2).NumSupplyNodes; ++Count3) {
1893 0 : if (SupplyAirPathNum != 0) {
1894 0 : if (state.dataZoneEquip->SupplyAirPath(SupplyAirPathNum).InletNodeNum ==
1895 0 : state.dataAirLoop->AirToZoneNodeInfo(Count2).ZoneEquipSupplyNodeNum(Count3))
1896 0 : Found = Count3;
1897 : } else {
1898 0 : if (state.dataDualDuct->dd_airterminal(Count1).HotAirInletNodeNum ==
1899 0 : state.dataAirLoop->AirToZoneNodeInfo(Count2).ZoneEquipSupplyNodeNum(Count3))
1900 0 : Found = Count3;
1901 0 : if (state.dataDualDuct->dd_airterminal(Count1).ColdAirInletNodeNum ==
1902 0 : state.dataAirLoop->AirToZoneNodeInfo(Count2).ZoneEquipSupplyNodeNum(Count3))
1903 0 : Found = Count3;
1904 0 : if (state.dataDualDuct->dd_airterminal(Count1).OAInletNodeNum ==
1905 0 : state.dataAirLoop->AirToZoneNodeInfo(Count2).ZoneEquipSupplyNodeNum(Count3))
1906 0 : Found = Count3;
1907 0 : if (state.dataDualDuct->dd_airterminal(Count1).RecircAirInletNodeNum ==
1908 0 : state.dataAirLoop->AirToZoneNodeInfo(Count2).ZoneEquipSupplyNodeNum(Count3))
1909 0 : Found = Count3;
1910 : }
1911 : }
1912 0 : if (Found != 0) break;
1913 : }
1914 0 : if (Found == 0) ChrName = "**Unknown**";
1915 :
1916 0 : std::string_view damperType = cmoNameArray[static_cast<int>(state.dataDualDuct->dd_airterminal(Count1).DamperType)];
1917 0 : if ((state.dataDualDuct->dd_airterminal(Count1).DamperType == DualDuctDamper::ConstantVolume) ||
1918 0 : (state.dataDualDuct->dd_airterminal(Count1).DamperType == DualDuctDamper::VariableVolume)) {
1919 0 : print(state.files.bnd,
1920 : " Dual Duct Damper,{},{},{},{},{},Hot Air,{}\n",
1921 : Count1,
1922 : damperType,
1923 0 : state.dataDualDuct->dd_airterminal(Count1).Name,
1924 0 : state.dataLoopNodes->NodeID(state.dataDualDuct->dd_airterminal(Count1).HotAirInletNodeNum),
1925 0 : state.dataLoopNodes->NodeID(state.dataDualDuct->dd_airterminal(Count1).OutletNodeNum),
1926 : ChrName);
1927 :
1928 0 : print(state.files.bnd,
1929 : " Dual Duct Damper,{},{},{},{},{},Cold Air,{}\n",
1930 : Count1,
1931 : damperType,
1932 0 : state.dataDualDuct->dd_airterminal(Count1).Name,
1933 0 : state.dataLoopNodes->NodeID(state.dataDualDuct->dd_airterminal(Count1).ColdAirInletNodeNum),
1934 0 : state.dataLoopNodes->NodeID(state.dataDualDuct->dd_airterminal(Count1).OutletNodeNum),
1935 : ChrName);
1936 :
1937 0 : } else if (state.dataDualDuct->dd_airterminal(Count1).DamperType == DualDuctDamper::OutdoorAir) {
1938 0 : print(state.files.bnd,
1939 : "Dual Duct Damper, {},{},{},{},{},Outdoor Air,{}\n",
1940 : Count1,
1941 : damperType,
1942 0 : state.dataDualDuct->dd_airterminal(Count1).Name,
1943 0 : state.dataLoopNodes->NodeID(state.dataDualDuct->dd_airterminal(Count1).OAInletNodeNum),
1944 0 : state.dataLoopNodes->NodeID(state.dataDualDuct->dd_airterminal(Count1).OutletNodeNum),
1945 : ChrName);
1946 0 : print(state.files.bnd,
1947 : "Dual Duct Damper, {},{},{},{},{},Recirculated Air,{}\n",
1948 : Count1,
1949 : damperType,
1950 0 : state.dataDualDuct->dd_airterminal(Count1).Name,
1951 0 : state.dataLoopNodes->NodeID(state.dataDualDuct->dd_airterminal(Count1).RecircAirInletNodeNum),
1952 0 : state.dataLoopNodes->NodeID(state.dataDualDuct->dd_airterminal(Count1).OutletNodeNum),
1953 : ChrName);
1954 : }
1955 0 : }
1956 : }
1957 :
1958 0 : void GetDualDuctOutdoorAirRecircUse(EnergyPlusData &state,
1959 : [[maybe_unused]] std::string const &CompTypeName,
1960 : std::string_view CompName,
1961 : bool &RecircIsUsed)
1962 : {
1963 :
1964 : // SUBROUTINE INFORMATION:
1965 : // AUTHOR B. Griffith
1966 : // DATE WRITTEN Aug 2011
1967 : // MODIFIED na
1968 : // RE-ENGINEERED na
1969 :
1970 : // PURPOSE OF THIS SUBROUTINE:
1971 : // get routine to learn if a dual duct outdoor air unit is using its recirc deck
1972 :
1973 0 : RecircIsUsed = true;
1974 0 : if (state.dataDualDuct->GetDualDuctOutdoorAirRecircUseFirstTimeOnly) {
1975 0 : state.dataDualDuct->NumDualDuctVarVolOA = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, cCMO_DDVarVolOA);
1976 0 : state.dataDualDuct->RecircIsUsedARR.allocate(state.dataDualDuct->NumDualDuctVarVolOA);
1977 0 : state.dataDualDuct->DamperNamesARR.allocate(state.dataDualDuct->NumDualDuctVarVolOA);
1978 0 : if (state.dataDualDuct->NumDualDuctVarVolOA > 0) {
1979 0 : Array1D<Real64> NumArray(2, 0.0);
1980 0 : Array1D_string AlphArray(7);
1981 0 : Array1D_string cAlphaFields(7); // Alpha field names
1982 0 : Array1D_string cNumericFields(2); // Numeric field names
1983 0 : Array1D_bool lAlphaBlanks(7, true); // Logical array, alpha field input BLANK = .TRUE.
1984 0 : Array1D_bool lNumericBlanks(2, true); // Logical array, numeric field input BLANK = .TRUE.
1985 0 : for (int DamperIndex = 1; DamperIndex <= state.dataDualDuct->NumDualDuctVarVolOA; ++DamperIndex) {
1986 :
1987 : int NumAlphas;
1988 : int NumNums;
1989 : int IOStat;
1990 0 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
1991 : cCMO_DDVarVolOA,
1992 : DamperIndex,
1993 : AlphArray,
1994 : NumAlphas,
1995 : NumArray,
1996 : NumNums,
1997 : IOStat,
1998 : lNumericBlanks,
1999 : lAlphaBlanks,
2000 : cAlphaFields,
2001 : cNumericFields);
2002 0 : state.dataDualDuct->DamperNamesARR(DamperIndex) = AlphArray(1);
2003 0 : if (!lAlphaBlanks(5)) {
2004 0 : state.dataDualDuct->RecircIsUsedARR(DamperIndex) = true;
2005 : } else {
2006 0 : state.dataDualDuct->RecircIsUsedARR(DamperIndex) = false;
2007 : }
2008 : }
2009 0 : }
2010 0 : state.dataDualDuct->GetDualDuctOutdoorAirRecircUseFirstTimeOnly = false;
2011 : }
2012 :
2013 0 : int DamperIndex = Util::FindItemInList(CompName, state.dataDualDuct->DamperNamesARR, state.dataDualDuct->NumDualDuctVarVolOA);
2014 0 : if (DamperIndex > 0) {
2015 0 : RecircIsUsed = state.dataDualDuct->RecircIsUsedARR(DamperIndex);
2016 : }
2017 0 : }
2018 :
2019 0 : void DualDuctAirTerminal::CalcOutdoorAirVolumeFlowRate(EnergyPlusData &state)
2020 : {
2021 : // calculates zone outdoor air volume flow rate using the supply air flow rate and OA fraction, for AirLoopNum > 0 only for now
2022 0 : if (this->AirLoopNum > 0) {
2023 0 : this->OutdoorAirFlowRate =
2024 0 : (this->dd_airterminalOutlet.AirMassFlowRate / state.dataEnvrn->StdRhoAir) * state.dataAirLoop->AirLoopFlow(this->AirLoopNum).OAFrac;
2025 : }
2026 0 : }
2027 :
2028 2 : void DualDuctAirTerminal::reportTerminalUnit(EnergyPlusData &state)
2029 : {
2030 : // populate the predefined equipment summary report related to air terminals
2031 2 : auto &orp = state.dataOutRptPredefined;
2032 2 : auto &adu = state.dataDefineEquipment->AirDistUnit(this->ADUNum);
2033 2 : if (!state.dataSize->TermUnitFinalZoneSizing.empty()) {
2034 2 : auto &sizing = state.dataSize->TermUnitFinalZoneSizing(adu.TermUnitSizingNum);
2035 2 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermMinFlow, adu.Name, sizing.DesCoolVolFlowMin);
2036 2 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermMinOutdoorFlow, adu.Name, sizing.MinOA);
2037 2 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermSupCoolingSP, adu.Name, sizing.CoolDesTemp);
2038 2 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermSupHeatingSP, adu.Name, sizing.HeatDesTemp);
2039 2 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermHeatingCap, adu.Name, sizing.DesHeatLoad);
2040 2 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermCoolingCap, adu.Name, sizing.DesCoolLoad);
2041 : }
2042 :
2043 2 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermTypeInp, adu.Name, dualDuctDamperNames[(int)this->DamperType]);
2044 2 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermPrimFlow, adu.Name, this->MaxAirVolFlowRate);
2045 2 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermSecdFlow, adu.Name, "n/a");
2046 2 : if (this->zoneTurndownMinAirFracSched != nullptr) {
2047 1 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermMinFlowSch, adu.Name, this->zoneTurndownMinAirFracSched->Name);
2048 : } else {
2049 1 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermMinFlowSch, adu.Name, "n/a");
2050 : }
2051 2 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermMaxFlowReh, adu.Name, "n/a");
2052 2 : std::string schName = "n/a";
2053 2 : if (this->OARequirementsPtr > 0) {
2054 1 : schName = state.dataSize->OARequirements(this->OARequirementsPtr).oaFlowFracSched->Name;
2055 : }
2056 2 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermMinOAflowSch, adu.Name, schName);
2057 2 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermHeatCoilType, adu.Name, "n/a");
2058 2 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermCoolCoilType, adu.Name, "n/a");
2059 2 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermFanType, adu.Name, "n/a");
2060 2 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermFanName, adu.Name, "n/a");
2061 2 : }
2062 :
2063 : } // namespace DualDuct
2064 :
2065 : } // namespace EnergyPlus
|