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