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 :
51 : // ObjexxFCL Headers
52 : #include <ObjexxFCL/Array.functions.hh>
53 : #include <ObjexxFCL/Fmath.hh>
54 :
55 : // EnergyPlus Headers
56 : #include <EnergyPlus/Autosizing/Base.hh>
57 : #include <EnergyPlus/BranchNodeConnections.hh>
58 : #include <EnergyPlus/Data/EnergyPlusData.hh>
59 : #include <EnergyPlus/DataDefineEquip.hh>
60 : #include <EnergyPlus/DataEnvironment.hh>
61 : #include <EnergyPlus/DataHeatBalFanSys.hh>
62 : #include <EnergyPlus/DataIPShortCuts.hh>
63 : #include <EnergyPlus/DataLoopNode.hh>
64 : #include <EnergyPlus/DataPrecisionGlobals.hh>
65 : #include <EnergyPlus/DataSizing.hh>
66 : #include <EnergyPlus/DataZoneEnergyDemands.hh>
67 : #include <EnergyPlus/DataZoneEquipment.hh>
68 : #include <EnergyPlus/Fans.hh>
69 : #include <EnergyPlus/FluidProperties.hh>
70 : #include <EnergyPlus/General.hh>
71 : #include <EnergyPlus/GeneralRoutines.hh>
72 : #include <EnergyPlus/GlobalNames.hh>
73 : #include <EnergyPlus/HeatingCoils.hh>
74 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
75 : #include <EnergyPlus/MixerComponent.hh>
76 : #include <EnergyPlus/NodeInputManager.hh>
77 : #include <EnergyPlus/OutputProcessor.hh>
78 : #include <EnergyPlus/OutputReportPredefined.hh>
79 : #include <EnergyPlus/Plant/DataPlant.hh>
80 : #include <EnergyPlus/PlantUtilities.hh>
81 : #include <EnergyPlus/PoweredInductionUnits.hh>
82 : #include <EnergyPlus/Psychrometrics.hh>
83 : #include <EnergyPlus/ScheduleManager.hh>
84 : #include <EnergyPlus/SteamCoils.hh>
85 : #include <EnergyPlus/UtilityRoutines.hh>
86 : #include <EnergyPlus/WaterCoils.hh>
87 : #include <EnergyPlus/ZoneAirLoopEquipmentManager.hh>
88 :
89 : namespace EnergyPlus::PoweredInductionUnits {
90 :
91 : // Module containing routines dealing with Series and Parallel fan powered terminal boxes
92 :
93 : // MODULE INFORMATION:
94 : // AUTHOR Fred Buhl
95 : // DATE WRITTEN August 2000
96 : // MODIFIED Brent Griffith, Sept 2010, plant upgrades, fluid properties
97 : // RE-ENGINEERED na
98 :
99 : // PURPOSE OF THIS MODULE:
100 : // To encapsulate the data and algorithms needed to simulate Series and Parallel
101 : // fan powered induction terminal boxes.
102 :
103 : // METHODOLOGY EMPLOYED:
104 : // The terminal boxes are modeled as a collection of components: air mixer,
105 : // fan, and heating coil plus an integrated control
106 : // algorithm that adjusts the primary air flow and the heating coil output
107 : // to meet the zone load.
108 :
109 : // Using/Aliasing
110 : using namespace DataLoopNode;
111 : using HVAC::SmallAirVolFlow;
112 : using HVAC::SmallLoad;
113 : using HVAC::SmallMassFlow;
114 : using HVAC::SmallTempDiff;
115 : using Psychrometrics::PsyCpAirFnW;
116 : using Psychrometrics::PsyHFnTdbW;
117 : using SteamCoils::SimulateSteamCoilComponents;
118 :
119 15913 : void SimPIU(EnergyPlusData &state,
120 : std::string_view CompName, // name of the PIU
121 : bool const FirstHVACIteration, // TRUE if first HVAC iteration in time step
122 : int const ZoneNum, // index of zone served by PIU
123 : int const ZoneNodeNum, // zone node number of zone served by PIU
124 : int &CompIndex // PIU Index in PIU names
125 : )
126 : {
127 :
128 : // SUBROUTINE INFORMATION:
129 : // AUTHOR Fred Buhl
130 : // DATE WRITTEN March 2000
131 : // MODIFIED na
132 : // RE-ENGINEERED na
133 :
134 : // PURPOSE OF THIS SUBROUTINE:
135 : // Manages the simulation of a fan powered induction terminal unit.
136 : // Called from SimZoneAirLoopEquipmentin module ZoneAirLoopEquipmentManager.
137 :
138 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
139 15913 : int PIUNum = 0; // index of powered induction unit being simulated
140 :
141 : // First time SimPIU is called, get the input for all the fan coil units
142 15913 : if (state.dataPowerInductionUnits->GetPIUInputFlag) {
143 6 : GetPIUs(state);
144 6 : state.dataPowerInductionUnits->GetPIUInputFlag = false;
145 : }
146 :
147 : // Get the powered induction unit index
148 15913 : if (CompIndex == 0) {
149 7 : PIUNum = Util::FindItemInList(CompName, state.dataPowerInductionUnits->PIU);
150 7 : if (PIUNum == 0) {
151 0 : ShowFatalError(state, format("SimPIU: PIU Unit not found={}", CompName));
152 : }
153 7 : CompIndex = PIUNum;
154 : } else {
155 15906 : PIUNum = CompIndex;
156 15906 : if (PIUNum > state.dataPowerInductionUnits->NumPIUs || PIUNum < 1) {
157 0 : ShowFatalError(state,
158 0 : format("SimPIU: Invalid CompIndex passed={}, Number of PIU Units={}, PIU Unit name={}",
159 : CompIndex,
160 0 : state.dataPowerInductionUnits->NumPIUs,
161 : CompName));
162 : }
163 15906 : if (state.dataPowerInductionUnits->CheckEquipName(PIUNum)) {
164 6 : if (CompName != state.dataPowerInductionUnits->PIU(PIUNum).Name) {
165 0 : ShowFatalError(state,
166 0 : format("SimPIU: Invalid CompIndex passed={}, PIU Unit name={}, stored PIU Unit Name for that index={}",
167 : CompIndex,
168 : CompName,
169 0 : state.dataPowerInductionUnits->PIU(PIUNum).Name));
170 : }
171 6 : state.dataPowerInductionUnits->CheckEquipName(PIUNum) = false;
172 : }
173 : }
174 :
175 31826 : state.dataSize->CurTermUnitSizingNum =
176 15913 : state.dataDefineEquipment->AirDistUnit(state.dataPowerInductionUnits->PIU(PIUNum).ADUNum).TermUnitSizingNum;
177 : // initialize the unit
178 15913 : InitPIU(state, PIUNum, FirstHVACIteration);
179 :
180 15913 : state.dataSize->TermUnitPIU = true;
181 :
182 : // Select the correct unit type
183 15913 : switch (state.dataPowerInductionUnits->PIU(PIUNum).UnitType_Num) {
184 :
185 15913 : case DataDefineEquip::ZnAirLoopEquipType::SingleDuct_SeriesPIU_Reheat: { // 'AirTerminal:SingleDuct:SeriesPIU:Reheat'
186 :
187 15913 : CalcSeriesPIU(state, PIUNum, ZoneNum, ZoneNodeNum, FirstHVACIteration);
188 15913 : break;
189 : }
190 0 : case DataDefineEquip::ZnAirLoopEquipType::SingleDuct_ParallelPIU_Reheat: { // 'AirTerminal:SingleDuct:ParallelPIU:Reheat'
191 :
192 0 : CalcParallelPIU(state, PIUNum, ZoneNum, ZoneNodeNum, FirstHVACIteration);
193 0 : break;
194 : }
195 0 : default:
196 0 : ShowSevereError(state, format("Illegal PI Unit Type used={}", state.dataPowerInductionUnits->PIU(PIUNum).UnitType));
197 0 : ShowContinueError(state, format("Occurs in PI Unit={}", state.dataPowerInductionUnits->PIU(PIUNum).Name));
198 0 : ShowFatalError(state, "Preceding condition causes termination.");
199 0 : break;
200 : }
201 :
202 15913 : state.dataSize->TermUnitPIU = false;
203 :
204 : // Update the current unit's outlet nodes
205 : // no update needed: reheat coil updates outlet node; inlet nodes' mass flow rate set by Calc routine
206 :
207 : // Fill the report variables
208 15913 : ReportPIU(state, PIUNum);
209 15913 : }
210 :
211 19 : void GetPIUs(EnergyPlusData &state)
212 : {
213 :
214 : // SUBROUTINE INFORMATION:
215 : // AUTHOR Fred Buhl
216 : // DATE WRITTEN August 2000
217 : // MODIFIED na
218 : // RE-ENGINEERED na
219 :
220 : // PURPOSE OF THIS SUBROUTINE:
221 : // Obtains input data for powered induction unit terminal boxes and stores it
222 : // in PIU data structures
223 :
224 : // METHODOLOGY EMPLOYED:
225 : // Uses "Get" routines to read in data.
226 :
227 : // Using/Aliasing
228 : using BranchNodeConnections::SetUpCompSets;
229 : using BranchNodeConnections::TestCompSet;
230 :
231 : using NodeInputManager::GetOnlySingleNode;
232 : using SteamCoils::GetCoilSteamInletNode;
233 : using WaterCoils::GetCoilWaterInletNode;
234 :
235 : static constexpr std::string_view routineName = "GetPIUs";
236 :
237 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
238 19 : bool ErrorsFound(false); // Set to true if errors in input, fatal at end of routine
239 : static constexpr std::string_view RoutineName("GetPIUs: "); // include trailing blank space
240 19 : bool SteamMessageNeeded = true;
241 :
242 : // find the number of each type of fan coil unit
243 38 : state.dataPowerInductionUnits->NumSeriesPIUs =
244 19 : state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "AirTerminal:SingleDuct:SeriesPIU:Reheat");
245 38 : state.dataPowerInductionUnits->NumParallelPIUs =
246 19 : state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "AirTerminal:SingleDuct:ParallelPIU:Reheat");
247 19 : state.dataPowerInductionUnits->NumPIUs = state.dataPowerInductionUnits->NumSeriesPIUs + state.dataPowerInductionUnits->NumParallelPIUs;
248 :
249 19 : if (state.dataPowerInductionUnits->NumPIUs > 0) {
250 : // GetZonePlenumInput might call this routine before the AirDistUnit has been populated
251 17 : if (state.dataZoneAirLoopEquipmentManager->GetAirDistUnitsFlag) {
252 3 : ZoneAirLoopEquipmentManager::GetZoneAirLoopEquipment(state);
253 3 : state.dataZoneAirLoopEquipmentManager->GetAirDistUnitsFlag = false;
254 : }
255 : }
256 :
257 : // allocate the data structures
258 19 : state.dataPowerInductionUnits->PIU.allocate(state.dataPowerInductionUnits->NumPIUs);
259 19 : state.dataPowerInductionUnits->PiuUniqueNames.reserve(static_cast<unsigned>(state.dataPowerInductionUnits->NumPIUs));
260 19 : state.dataPowerInductionUnits->CheckEquipName.dimension(state.dataPowerInductionUnits->NumPIUs, true);
261 :
262 19 : int PIUNum = 0;
263 19 : auto &ip = state.dataInputProcessing->inputProcessor;
264 : // loop over Series PIUs; get and load the input data
265 95 : for (const std::string cCurrentModuleObject : {"AirTerminal:SingleDuct:SeriesPIU:Reheat", "AirTerminal:SingleDuct:ParallelPIU:Reheat"}) {
266 38 : auto const &objectSchemaProps = ip->getObjectSchemaProps(state, cCurrentModuleObject);
267 38 : auto const &PIUsInstances = ip->epJSON.find(cCurrentModuleObject);
268 38 : if (PIUsInstances != ip->epJSON.end()) {
269 17 : auto &PIUInstances = PIUsInstances.value();
270 34 : for (auto instance = PIUInstances.begin(); instance != PIUInstances.end(); ++instance) {
271 17 : ++PIUNum;
272 17 : auto const &fields = instance.value();
273 :
274 17 : ErrorObjectHeader eoh{routineName, cCurrentModuleObject, instance.key()};
275 :
276 17 : GlobalNames::VerifyUniqueInterObjectName(
277 17 : state, state.dataPowerInductionUnits->PiuUniqueNames, Util::makeUPPER(instance.key()), cCurrentModuleObject, "Name", ErrorsFound);
278 17 : auto &thisPIU = state.dataPowerInductionUnits->PIU(PIUNum);
279 17 : thisPIU.Name = Util::makeUPPER(instance.key());
280 17 : thisPIU.UnitType = cCurrentModuleObject;
281 17 : ip->markObjectAsUsed(cCurrentModuleObject, instance.key());
282 17 : if (cCurrentModuleObject == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
283 14 : thisPIU.UnitType_Num = DataDefineEquip::ZnAirLoopEquipType::SingleDuct_SeriesPIU_Reheat;
284 3 : } else if (cCurrentModuleObject == "AirTerminal:SingleDuct:ParallelPIU:Reheat") {
285 3 : thisPIU.UnitType_Num = DataDefineEquip::ZnAirLoopEquipType::SingleDuct_ParallelPIU_Reheat;
286 : }
287 :
288 34 : std::string schedName = ip->getAlphaFieldValue(fields, objectSchemaProps, "availability_schedule_name");
289 17 : if (schedName.empty()) {
290 3 : thisPIU.availSched = Sched::GetScheduleAlwaysOn(state);
291 14 : } else if ((thisPIU.availSched = Sched::GetSchedule(state, Util::makeUPPER(schedName))) == nullptr) {
292 0 : ShowWarningItemNotFound(
293 : state, eoh, "Availability Schedule Name", schedName, "Set the default as Always On. Simulation continues.");
294 0 : thisPIU.availSched = Sched::GetScheduleAlwaysOn(state);
295 : }
296 :
297 17 : if (cCurrentModuleObject == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
298 42 : thisPIU.MaxTotAirVolFlow = ip->getRealFieldValue(fields, objectSchemaProps, "maximum_air_flow_rate");
299 : }
300 17 : if (cCurrentModuleObject == "AirTerminal:SingleDuct:ParallelPIU:Reheat") {
301 9 : thisPIU.MaxSecAirVolFlow = ip->getRealFieldValue(fields, objectSchemaProps, "maximum_secondary_air_flow_rate");
302 : }
303 34 : thisPIU.MaxPriAirVolFlow = ip->getRealFieldValue(fields, objectSchemaProps, "maximum_primary_air_flow_rate");
304 34 : thisPIU.MinPriAirFlowFrac = ip->getRealFieldValue(fields, objectSchemaProps, "minimum_primary_air_flow_fraction");
305 17 : if (cCurrentModuleObject == "AirTerminal:SingleDuct:ParallelPIU:Reheat") {
306 9 : thisPIU.FanOnFlowFrac = ip->getRealFieldValue(fields, objectSchemaProps, "fan_on_flow_fraction");
307 : }
308 17 : thisPIU.HCoilType = static_cast<HtgCoilType>(
309 34 : getEnumValue(HCoilNamesUC, Util::makeUPPER(ip->getAlphaFieldValue(fields, objectSchemaProps, "reheat_coil_object_type"))));
310 17 : switch (thisPIU.HCoilType) {
311 1 : case HtgCoilType::SimpleHeating: {
312 1 : thisPIU.HCoil_PlantType = DataPlant::PlantEquipmentType::CoilWaterSimpleHeating;
313 1 : break;
314 : }
315 16 : case HtgCoilType::Electric:
316 : case HtgCoilType::Gas: {
317 16 : break;
318 : }
319 0 : case HtgCoilType::SteamAirHeating: {
320 0 : thisPIU.HCoil_PlantType = DataPlant::PlantEquipmentType::CoilSteamAirHeating;
321 0 : thisPIU.HCoil_fluid = Fluid::GetSteam(state);
322 0 : if (thisPIU.HCoil_fluid == nullptr) {
323 0 : ShowSevereError(state, format("{} Steam Properties for {} not found.", RoutineName, thisPIU.Name));
324 0 : if (SteamMessageNeeded) {
325 0 : ShowContinueError(state, "Steam Fluid Properties should have been included in the input file.");
326 : }
327 0 : ErrorsFound = true;
328 0 : SteamMessageNeeded = false;
329 : }
330 0 : break;
331 : }
332 0 : default: {
333 0 : ShowSevereError(state, format("Illegal Reheat Coil Type = {}", thisPIU.HCoilType));
334 0 : ShowContinueError(state, format("Occurs in {} = {}", cCurrentModuleObject, thisPIU.Name));
335 0 : ErrorsFound = true;
336 : }
337 : }
338 :
339 17 : auto connectionType = DataLoopNode::ConnectionObjectType::AirTerminalSingleDuctSeriesPIUReheat;
340 17 : if (cCurrentModuleObject == "AirTerminal:SingleDuct:ParallelPIU:Reheat") {
341 3 : connectionType = DataLoopNode::ConnectionObjectType::AirTerminalSingleDuctParallelPIUReheat;
342 : }
343 34 : thisPIU.PriAirInNode = GetOnlySingleNode(state,
344 34 : ip->getAlphaFieldValue(fields, objectSchemaProps, "supply_air_inlet_node_name"),
345 : ErrorsFound,
346 : connectionType,
347 17 : thisPIU.Name,
348 : DataLoopNode::NodeFluidType::Air,
349 : DataLoopNode::ConnectionType::Inlet,
350 : NodeInputManager::CompFluidStream::Primary,
351 : ObjectIsParent,
352 : "Supply Air Inlet Node Name");
353 :
354 34 : thisPIU.SecAirInNode = GetOnlySingleNode(state,
355 34 : ip->getAlphaFieldValue(fields, objectSchemaProps, "secondary_air_inlet_node_name"),
356 : ErrorsFound,
357 : connectionType,
358 17 : thisPIU.Name,
359 : DataLoopNode::NodeFluidType::Air,
360 : DataLoopNode::ConnectionType::Inlet,
361 : NodeInputManager::CompFluidStream::Primary,
362 : ObjectIsParent,
363 : "Secondary Air Inlet Node Name");
364 :
365 34 : thisPIU.OutAirNode = GetOnlySingleNode(state,
366 34 : ip->getAlphaFieldValue(fields, objectSchemaProps, "outlet_node_name"),
367 : ErrorsFound,
368 : connectionType,
369 17 : thisPIU.Name,
370 : DataLoopNode::NodeFluidType::Air,
371 : DataLoopNode::ConnectionType::Outlet,
372 : NodeInputManager::CompFluidStream::Primary,
373 : ObjectIsParent,
374 : "Outlet Node Name");
375 :
376 34 : thisPIU.HCoilInAirNode = GetOnlySingleNode(state,
377 34 : ip->getAlphaFieldValue(fields, objectSchemaProps, "reheat_coil_air_inlet_node_name"),
378 : ErrorsFound,
379 : connectionType,
380 17 : thisPIU.Name,
381 : DataLoopNode::NodeFluidType::Air,
382 : DataLoopNode::ConnectionType::Internal,
383 : NodeInputManager::CompFluidStream::Primary,
384 : ObjectIsParent,
385 : "Reheat Coil Air Inlet Node Name");
386 : // The reheat coil control node is necessary for hot water reheat, but not necessary for
387 : // electric or gas reheat.
388 17 : if (thisPIU.HCoilType == HtgCoilType::SimpleHeating) {
389 1 : thisPIU.HotControlNode = GetCoilWaterInletNode(state,
390 3 : ip->getAlphaFieldValue(fields, objectSchemaProps, "reheat_coil_object_type"),
391 4 : ip->getAlphaFieldValue(fields, objectSchemaProps, "reheat_coil_name"),
392 : ErrorsFound);
393 : }
394 17 : if (thisPIU.HCoilType == HtgCoilType::SteamAirHeating) {
395 0 : thisPIU.HotControlNode = GetCoilSteamInletNode(state,
396 0 : ip->getAlphaFieldValue(fields, objectSchemaProps, "reheat_coil_object_type"),
397 0 : ip->getAlphaFieldValue(fields, objectSchemaProps, "reheat_coil_name"),
398 : ErrorsFound);
399 : }
400 34 : thisPIU.MixerName = ip->getAlphaFieldValue(fields, objectSchemaProps, "zone_mixer_name");
401 34 : thisPIU.FanName = ip->getAlphaFieldValue(fields, objectSchemaProps, "fan_name");
402 :
403 : // find fan type
404 : // test if Fan:SystemModel fan of this name exists
405 17 : if ((thisPIU.Fan_Index = Fans::GetFanIndex(state, thisPIU.FanName)) == 0) {
406 0 : ShowSevereItemNotFound(state, eoh, state.dataIPShortCut->cAlphaFieldNames(8), thisPIU.FanName);
407 0 : ErrorsFound = true;
408 : } else {
409 : // Assert that this is a constant volume fan?
410 17 : auto *fan = state.dataFans->fans(thisPIU.Fan_Index);
411 17 : thisPIU.fanType = fan->type;
412 17 : thisPIU.fanAvailSched = fan->availSched;
413 : }
414 :
415 34 : thisPIU.HCoil = ip->getAlphaFieldValue(fields, objectSchemaProps, "reheat_coil_name");
416 17 : bool IsNotOK = false;
417 34 : ValidateComponent(
418 51 : state, HCoilNamesUC[static_cast<int>(thisPIU.HCoilType)], thisPIU.HCoil, IsNotOK, cCurrentModuleObject + " - Heating Coil");
419 17 : if (IsNotOK) {
420 0 : ShowContinueError(state, format("In {} = {}", cCurrentModuleObject, thisPIU.Name));
421 0 : ErrorsFound = true;
422 : }
423 34 : thisPIU.MaxVolHotWaterFlow = ip->getRealFieldValue(fields, objectSchemaProps, "maximum_hot_water_or_steam_flow_rate");
424 34 : thisPIU.MinVolHotWaterFlow = ip->getRealFieldValue(fields, objectSchemaProps, "minimum_hot_water_or_steam_flow_rate");
425 34 : thisPIU.HotControlOffset = ip->getRealFieldValue(fields, objectSchemaProps, "convergence_tolerance");
426 : // Set default convergence tolerance
427 17 : if (thisPIU.HotControlOffset <= 0.0) {
428 0 : thisPIU.HotControlOffset = 0.001;
429 : }
430 :
431 : // Variable speed fan inputs
432 34 : std::string fan_control_type = "ConstantSpeed";
433 34 : fan_control_type = ip->getAlphaFieldValue(fields, objectSchemaProps, "fan_control_type");
434 17 : thisPIU.fanControlType = FanCntrlType::ConstantSpeedFan;
435 17 : if (Util::SameString(fan_control_type, "VariableSpeed")) {
436 5 : thisPIU.fanControlType = FanCntrlType::VariableSpeedFan;
437 5 : if (thisPIU.fanType != HVAC::FanType::SystemModel) {
438 0 : ErrorsFound = true;
439 0 : ShowSevereError(state, format("Fan type must be Fan:SystemModel when Fan Control Type = {}", fan_control_type));
440 0 : ShowContinueError(state, format("Occurs in {} = {}", cCurrentModuleObject, thisPIU.Name));
441 : }
442 12 : } else if (Util::SameString(fan_control_type, "ConstantSpeed")) {
443 12 : thisPIU.fanControlType = FanCntrlType::ConstantSpeedFan;
444 : } else {
445 0 : ShowSevereError(state, format("Illegal Fan Control Type = {}", fan_control_type));
446 0 : ShowContinueError(state, format("Occurs in {} = {}", cCurrentModuleObject, thisPIU.Name));
447 0 : ErrorsFound = true;
448 : }
449 :
450 51 : std::string const heating_control_type = ip->getAlphaFieldValue(fields, objectSchemaProps, "heating_control_type");
451 17 : thisPIU.heatingControlType = HeatCntrlBehaviorType::Invalid;
452 17 : if (thisPIU.fanControlType == FanCntrlType::VariableSpeedFan) {
453 5 : if (Util::SameString(heating_control_type, "Staged")) {
454 2 : thisPIU.heatingControlType = HeatCntrlBehaviorType::StagedHeaterBehavior;
455 3 : } else if (Util::SameString(heating_control_type, "Modulated")) {
456 3 : thisPIU.heatingControlType = HeatCntrlBehaviorType::ModulatedHeaterBehavior;
457 : } else {
458 0 : ShowSevereError(state, "Heating Control Type should either be Staged or Modulated");
459 0 : ShowContinueError(state, format("Occurs in {} = {}", cCurrentModuleObject, thisPIU.Name));
460 0 : ErrorsFound = true;
461 : }
462 : }
463 :
464 34 : thisPIU.MinFanTurnDownRatio = ip->getRealFieldValue(fields, objectSchemaProps, "minimum_fan_turn_down_ratio");
465 34 : thisPIU.designHeatingDAT = ip->getRealFieldValue(fields, objectSchemaProps, "design_heating_discharge_air_temperature");
466 34 : thisPIU.highLimitDAT = ip->getRealFieldValue(fields, objectSchemaProps, "high_limit_heating_discharge_air_temperature");
467 :
468 : // Add fan to component sets array
469 17 : if (cCurrentModuleObject == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
470 28 : SetUpCompSets(state,
471 : thisPIU.UnitType,
472 : thisPIU.Name,
473 : "UNDEFINED",
474 : thisPIU.FanName,
475 : "UNDEFINED",
476 56 : ip->getAlphaFieldValue(fields, objectSchemaProps, "reheat_coil_air_inlet_node_name"));
477 3 : } else if (cCurrentModuleObject == "AirTerminal:SingleDuct:ParallelPIU:Reheat") {
478 6 : SetUpCompSets(state,
479 : thisPIU.UnitType,
480 : thisPIU.Name,
481 : "UNDEFINED",
482 : thisPIU.FanName,
483 12 : ip->getAlphaFieldValue(fields, objectSchemaProps, "secondary_air_inlet_node_name"),
484 : "UNDEFINED");
485 : }
486 :
487 : // Add reheat coil to component sets array
488 85 : SetUpCompSets(state,
489 : thisPIU.UnitType,
490 : thisPIU.Name,
491 51 : ip->getAlphaFieldValue(fields, objectSchemaProps, "reheat_coil_object_type"),
492 51 : ip->getAlphaFieldValue(fields, objectSchemaProps, "reheat_coil_name"),
493 51 : ip->getAlphaFieldValue(fields, objectSchemaProps, "reheat_coil_air_inlet_node_name"),
494 68 : ip->getAlphaFieldValue(fields, objectSchemaProps, "outlet_node_name"));
495 :
496 : // Register component set data
497 34 : TestCompSet(state,
498 : thisPIU.UnitType,
499 : thisPIU.Name,
500 17 : state.dataLoopNodes->NodeID(thisPIU.PriAirInNode),
501 17 : state.dataLoopNodes->NodeID(thisPIU.OutAirNode),
502 : "Air Nodes");
503 :
504 39 : for (int ADUNum = 1; ADUNum <= (int)state.dataDefineEquipment->AirDistUnit.size(); ++ADUNum) {
505 22 : if (thisPIU.OutAirNode == state.dataDefineEquipment->AirDistUnit(ADUNum).OutletNodeNum) {
506 17 : thisPIU.ADUNum = ADUNum;
507 17 : state.dataDefineEquipment->AirDistUnit(ADUNum).InletNodeNum = thisPIU.PriAirInNode;
508 : }
509 : }
510 : // one assumes if there isn't one assigned, it's an error?
511 17 : if (thisPIU.ADUNum == 0) {
512 0 : ShowSevereError(state,
513 0 : format("{}No matching Air Distribution Unit, for PIU = [{},{}].", RoutineName, thisPIU.UnitType, thisPIU.Name));
514 0 : ShowContinueError(state, format("...should have outlet node = {}", state.dataLoopNodes->NodeID(thisPIU.OutAirNode)));
515 0 : ErrorsFound = true;
516 : } else {
517 :
518 17 : bool AirNodeFound = false;
519 : // Fill the Zone Equipment data with the supply air inlet node number of this unit.
520 41 : for (int CtrlZone = 1; CtrlZone <= state.dataGlobal->NumOfZones; ++CtrlZone) {
521 24 : if (!state.dataZoneEquip->ZoneEquipConfig(CtrlZone).IsControlled) {
522 2 : continue;
523 : }
524 27 : for (int SupAirIn = 1; SupAirIn <= state.dataZoneEquip->ZoneEquipConfig(CtrlZone).NumInletNodes; ++SupAirIn) {
525 22 : if (thisPIU.OutAirNode == state.dataZoneEquip->ZoneEquipConfig(CtrlZone).InletNode(SupAirIn)) {
526 17 : state.dataZoneEquip->ZoneEquipConfig(CtrlZone).AirDistUnitCool(SupAirIn).InNode = thisPIU.PriAirInNode;
527 17 : state.dataZoneEquip->ZoneEquipConfig(CtrlZone).AirDistUnitCool(SupAirIn).OutNode = thisPIU.OutAirNode;
528 17 : state.dataDefineEquipment->AirDistUnit(thisPIU.ADUNum).TermUnitSizingNum =
529 17 : state.dataZoneEquip->ZoneEquipConfig(CtrlZone).AirDistUnitCool(SupAirIn).TermUnitSizingIndex;
530 17 : state.dataDefineEquipment->AirDistUnit(thisPIU.ADUNum).ZoneEqNum = CtrlZone;
531 17 : AirNodeFound = true;
532 17 : thisPIU.CtrlZoneNum = CtrlZone; // fill index for later use in finding air loop index
533 17 : thisPIU.ctrlZoneInNodeIndex = SupAirIn;
534 17 : break;
535 : }
536 : }
537 : }
538 17 : if (!AirNodeFound) {
539 0 : ShowSevereError(state, format("The outlet air node from the {} Unit = {}", cCurrentModuleObject, thisPIU.Name));
540 0 : ShowContinueError(
541 0 : state, format("did not have a matching Zone Equipment Inlet Node, Node = {}", state.dataIPShortCut->cAlphaArgs(5)));
542 0 : ErrorsFound = true;
543 : }
544 : }
545 17 : }
546 : }
547 38 : }
548 :
549 19 : if (ErrorsFound) {
550 0 : ShowFatalError(state, format("{} Errors found in getting input. Preceding conditions cause termination.", RoutineName));
551 : }
552 :
553 36 : for (int PIURpt = 1; PIURpt <= state.dataPowerInductionUnits->NumPIUs; ++PIURpt) {
554 17 : auto &thisPIU = state.dataPowerInductionUnits->PIU(PIURpt);
555 :
556 : // Setup Report variables for the PIUs
557 34 : SetupOutputVariable(state,
558 : "Zone Air Terminal Primary Damper Position",
559 : Constant::Units::None,
560 17 : thisPIU.PriDamperPosition,
561 : OutputProcessor::TimeStepType::System,
562 : OutputProcessor::StoreType::Average,
563 17 : thisPIU.Name);
564 34 : SetupOutputVariable(state,
565 : "Zone Air Terminal Heating Rate",
566 : Constant::Units::W,
567 17 : thisPIU.HeatingRate,
568 : OutputProcessor::TimeStepType::System,
569 : OutputProcessor::StoreType::Average,
570 17 : thisPIU.Name);
571 34 : SetupOutputVariable(state,
572 : "Zone Air Terminal Heating Energy",
573 : Constant::Units::J,
574 17 : thisPIU.HeatingEnergy,
575 : OutputProcessor::TimeStepType::System,
576 : OutputProcessor::StoreType::Sum,
577 17 : thisPIU.Name);
578 34 : SetupOutputVariable(state,
579 : "Zone Air Terminal Sensible Cooling Rate",
580 : Constant::Units::W,
581 17 : thisPIU.SensCoolRate,
582 : OutputProcessor::TimeStepType::System,
583 : OutputProcessor::StoreType::Average,
584 17 : thisPIU.Name);
585 34 : SetupOutputVariable(state,
586 : "Zone Air Terminal Sensible Cooling Energy",
587 : Constant::Units::J,
588 17 : thisPIU.SensCoolEnergy,
589 : OutputProcessor::TimeStepType::System,
590 : OutputProcessor::StoreType::Sum,
591 17 : thisPIU.Name);
592 34 : SetupOutputVariable(state,
593 : "Zone Air Terminal Outdoor Air Volume Flow Rate",
594 : Constant::Units::m3_s,
595 17 : thisPIU.OutdoorAirFlowRate,
596 : OutputProcessor::TimeStepType::System,
597 : OutputProcessor::StoreType::Average,
598 17 : thisPIU.Name);
599 34 : SetupOutputVariable(state,
600 : "Zone Air Terminal Total Air Mass Flow Rate",
601 : Constant::Units::kg_s,
602 17 : thisPIU.TotMassFlowRate,
603 : OutputProcessor::TimeStepType::System,
604 : OutputProcessor::StoreType::Average,
605 17 : state.dataPowerInductionUnits->PIU(PIURpt).Name);
606 34 : SetupOutputVariable(state,
607 : "Zone Air Terminal Primary Air Mass Flow Rate",
608 : Constant::Units::kg_s,
609 17 : thisPIU.PriMassFlowRate,
610 : OutputProcessor::TimeStepType::System,
611 : OutputProcessor::StoreType::Average,
612 17 : state.dataPowerInductionUnits->PIU(PIURpt).Name);
613 34 : SetupOutputVariable(state,
614 : "Zone Air Terminal Secondary Air Mass Flow Rate",
615 : Constant::Units::kg_s,
616 17 : thisPIU.SecMassFlowRate,
617 : OutputProcessor::TimeStepType::System,
618 : OutputProcessor::StoreType::Average,
619 17 : state.dataPowerInductionUnits->PIU(PIURpt).Name);
620 34 : SetupOutputVariable(state,
621 : "Zone Air Terminal Outlet Discharge Air Temperature",
622 : Constant::Units::C,
623 17 : thisPIU.DischargeAirTemp,
624 : OutputProcessor::TimeStepType::System,
625 : OutputProcessor::StoreType::Average,
626 17 : state.dataPowerInductionUnits->PIU(PIURpt).Name);
627 17 : SetupOutputVariable(state,
628 : "Zone Air Terminal Current Operation Control Stage",
629 : Constant::Units::unknown,
630 17 : thisPIU.CurOperationControlStage,
631 : OutputProcessor::TimeStepType::System,
632 : OutputProcessor::StoreType::Average,
633 17 : state.dataPowerInductionUnits->PIU(PIURpt).Name);
634 : }
635 19 : }
636 :
637 15921 : void InitPIU(EnergyPlusData &state,
638 : int const PIUNum, // number of the current fan coil unit being simulated
639 : bool const FirstHVACIteration // TRUE if first zone equip this HVAC step
640 : )
641 : {
642 :
643 : // SUBROUTINE INFORMATION:
644 : // AUTHOR Fred Buhl
645 : // DATE WRITTEN August 2000
646 : // MODIFIED na
647 : // RE-ENGINEERED na
648 :
649 : // PURPOSE OF THIS SUBROUTINE:
650 : // This subroutine is for initializations of the powered induction unit
651 : // terminal boxe.
652 :
653 : // METHODOLOGY EMPLOYED:
654 : // Uses the status flags to trigger initializations.
655 :
656 : // Using/Aliasing
657 :
658 : using DataZoneEquipment::CheckZoneEquipmentList;
659 : using PlantUtilities::InitComponentNodes;
660 : using PlantUtilities::ScanPlantLoopsForObject;
661 :
662 15921 : auto &thisPIU = state.dataPowerInductionUnits->PIU(PIUNum);
663 : // SUBROUTINE PARAMETER DEFINITIONS:
664 : static constexpr std::string_view RoutineName("InitPIU");
665 :
666 : // Do the one time initializations
667 15921 : if (state.dataPowerInductionUnits->MyOneTimeFlag) {
668 15 : state.dataPowerInductionUnits->MyEnvrnFlag.dimension(state.dataPowerInductionUnits->NumPIUs, true);
669 15 : state.dataPowerInductionUnits->MySizeFlag.dimension(state.dataPowerInductionUnits->NumPIUs, true);
670 15 : state.dataPowerInductionUnits->MyPlantScanFlag.dimension(state.dataPowerInductionUnits->NumPIUs, true);
671 15 : state.dataPowerInductionUnits->MyOneTimeFlag = false;
672 : }
673 :
674 15921 : if (state.dataPowerInductionUnits->MyPlantScanFlag(PIUNum) && allocated(state.dataPlnt->PlantLoop)) {
675 7 : if ((thisPIU.HCoil_PlantType == DataPlant::PlantEquipmentType::CoilWaterSimpleHeating) ||
676 7 : (thisPIU.HCoil_PlantType == DataPlant::PlantEquipmentType::CoilSteamAirHeating)) {
677 0 : bool errFlag = false;
678 0 : ScanPlantLoopsForObject(state, thisPIU.HCoil, thisPIU.HCoil_PlantType, thisPIU.HWplantLoc, errFlag, _, _, _, _, _);
679 0 : if (errFlag) {
680 0 : ShowFatalError(state, "InitPIU: Program terminated due to previous condition(s).");
681 : }
682 0 : thisPIU.HotCoilOutNodeNum = DataPlant::CompData::getPlantComponent(state, thisPIU.HWplantLoc).NodeNumOut;
683 : }
684 7 : state.dataPowerInductionUnits->MyPlantScanFlag(PIUNum) = false;
685 15914 : } else if (state.dataPowerInductionUnits->MyPlantScanFlag(PIUNum) && !state.dataGlobal->AnyPlantInModel) {
686 8 : state.dataPowerInductionUnits->MyPlantScanFlag(PIUNum) = false;
687 : }
688 :
689 15921 : if (!state.dataPowerInductionUnits->ZoneEquipmentListChecked && state.dataZoneEquip->ZoneEquipInputsFilled) {
690 7 : state.dataPowerInductionUnits->ZoneEquipmentListChecked = true;
691 : // Check to see if there is a Air Distribution Unit on the Zone Equipment List
692 14 : for (int Loop = 1; Loop <= state.dataPowerInductionUnits->NumPIUs; ++Loop) {
693 7 : if (state.dataPowerInductionUnits->PIU(Loop).ADUNum == 0) {
694 0 : continue;
695 : }
696 14 : if (CheckZoneEquipmentList(state,
697 : "ZoneHVAC:AirDistributionUnit",
698 7 : state.dataDefineEquipment->AirDistUnit(state.dataPowerInductionUnits->PIU(Loop).ADUNum).Name)) {
699 7 : continue;
700 : }
701 0 : ShowSevereError(state,
702 0 : format("InitPIU: ADU=[Air Distribution Unit,{}] is not on any ZoneHVAC:EquipmentList.",
703 0 : state.dataDefineEquipment->AirDistUnit(state.dataPowerInductionUnits->PIU(Loop).ADUNum).Name));
704 0 : ShowContinueError(state,
705 0 : format("...PIU=[{},{}] will not be simulated.",
706 0 : state.dataPowerInductionUnits->PIU(Loop).UnitType,
707 0 : state.dataPowerInductionUnits->PIU(Loop).Name));
708 : }
709 : }
710 :
711 15935 : if (!state.dataGlobal->SysSizingCalc && state.dataPowerInductionUnits->MySizeFlag(PIUNum) &&
712 14 : !state.dataPowerInductionUnits->MyPlantScanFlag(PIUNum)) {
713 :
714 14 : SizePIU(state, PIUNum);
715 :
716 : // If there's a hot water control node number defined in PIU
717 14 : if (thisPIU.HotControlNode > 0) {
718 : // plant upgrade note? why no separate handling of steam coil? add it ?
719 : // local plant fluid density
720 0 : Real64 const rho = state.dataPlnt->PlantLoop(thisPIU.HWplantLoc.loopNum).glycol->getDensity(state, Constant::HWInitConvTemp, RoutineName);
721 :
722 0 : thisPIU.MaxHotWaterFlow = rho * thisPIU.MaxVolHotWaterFlow;
723 0 : thisPIU.MinHotWaterFlow = rho * thisPIU.MinVolHotWaterFlow;
724 0 : InitComponentNodes(state, thisPIU.MinHotWaterFlow, thisPIU.MaxHotWaterFlow, thisPIU.HotControlNode, thisPIU.HotCoilOutNodeNum);
725 : }
726 :
727 14 : state.dataPowerInductionUnits->MySizeFlag(PIUNum) = false;
728 : }
729 :
730 15921 : int const PriNode = thisPIU.PriAirInNode;
731 15921 : int const SecNode = thisPIU.SecAirInNode;
732 :
733 : // Do the Begin Environment initializations
734 15921 : if (state.dataGlobal->BeginEnvrnFlag && state.dataPowerInductionUnits->MyEnvrnFlag(PIUNum)) {
735 32 : Real64 const RhoAir = state.dataEnvrn->StdRhoAir;
736 32 : int const OutletNode = thisPIU.OutAirNode;
737 : // set the mass flow rates from the input volume flow rates
738 32 : if (thisPIU.UnitType == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
739 29 : thisPIU.MaxTotAirMassFlow = RhoAir * thisPIU.MaxTotAirVolFlow;
740 29 : thisPIU.MaxPriAirMassFlow = RhoAir * thisPIU.MaxPriAirVolFlow;
741 29 : thisPIU.MinPriAirMassFlow = RhoAir * thisPIU.MinPriAirFlowFrac * thisPIU.MaxPriAirVolFlow;
742 29 : state.dataLoopNodes->Node(PriNode).MassFlowRateMax = thisPIU.MaxPriAirMassFlow;
743 29 : state.dataLoopNodes->Node(PriNode).MassFlowRateMin = thisPIU.MinPriAirMassFlow;
744 29 : state.dataLoopNodes->Node(OutletNode).MassFlowRateMax = thisPIU.MaxTotAirMassFlow;
745 : } else {
746 : // parallel
747 3 : thisPIU.MaxPriAirMassFlow = RhoAir * thisPIU.MaxPriAirVolFlow;
748 3 : thisPIU.MinPriAirMassFlow = RhoAir * thisPIU.MinPriAirFlowFrac * thisPIU.MaxPriAirVolFlow;
749 3 : thisPIU.MaxSecAirMassFlow = RhoAir * thisPIU.MaxSecAirVolFlow;
750 3 : thisPIU.FanOnAirMassFlow = RhoAir * thisPIU.FanOnFlowFrac * thisPIU.MaxPriAirVolFlow;
751 3 : state.dataLoopNodes->Node(PriNode).MassFlowRateMax = thisPIU.MaxPriAirMassFlow;
752 3 : state.dataLoopNodes->Node(PriNode).MassFlowRateMin = thisPIU.MinPriAirMassFlow;
753 3 : state.dataLoopNodes->Node(OutletNode).MassFlowRateMax = thisPIU.MaxPriAirMassFlow;
754 : }
755 32 : if (thisPIU.fanControlType == FanCntrlType::VariableSpeedFan) {
756 5 : if (thisPIU.UnitType == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
757 3 : thisPIU.MinTotAirMassFlow = thisPIU.MaxTotAirMassFlow * thisPIU.MinFanTurnDownRatio;
758 3 : thisPIU.MaxSecAirVolFlow = thisPIU.MaxTotAirMassFlow - thisPIU.MinPriAirMassFlow;
759 3 : thisPIU.MaxSecAirMassFlow = RhoAir * thisPIU.MaxSecAirVolFlow;
760 3 : thisPIU.MinSecAirMassFlow = max(0.0, thisPIU.MinTotAirMassFlow - thisPIU.MinPriAirMassFlow);
761 : } else {
762 2 : thisPIU.MaxSecAirMassFlow = RhoAir * thisPIU.MaxSecAirVolFlow;
763 2 : thisPIU.MinSecAirMassFlow = max(0.0, thisPIU.MaxSecAirMassFlow * thisPIU.MinFanTurnDownRatio);
764 2 : thisPIU.MinTotAirMassFlow = thisPIU.MinSecAirMassFlow + thisPIU.MinPriAirMassFlow;
765 : }
766 : }
767 :
768 32 : if (((thisPIU.HCoilType == HtgCoilType::SimpleHeating) || (thisPIU.HCoilType == HtgCoilType::SteamAirHeating)) &&
769 0 : !state.dataPowerInductionUnits->MyPlantScanFlag(PIUNum)) {
770 0 : InitComponentNodes(state, thisPIU.MinHotWaterFlow, thisPIU.MaxHotWaterFlow, thisPIU.HotControlNode, thisPIU.HotCoilOutNodeNum);
771 : }
772 :
773 32 : if (thisPIU.AirLoopNum == 0) { // fill air loop index
774 14 : if (thisPIU.CtrlZoneNum > 0 && thisPIU.ctrlZoneInNodeIndex > 0) {
775 14 : thisPIU.AirLoopNum = state.dataZoneEquip->ZoneEquipConfig(thisPIU.CtrlZoneNum).InletNodeAirLoopNum(thisPIU.ctrlZoneInNodeIndex);
776 14 : state.dataDefineEquipment->AirDistUnit(thisPIU.ADUNum).AirLoopNum = thisPIU.AirLoopNum;
777 : }
778 : }
779 :
780 32 : state.dataPowerInductionUnits->MyEnvrnFlag(PIUNum) = false;
781 : } // end one time inits
782 :
783 15921 : if (!state.dataGlobal->BeginEnvrnFlag) {
784 15711 : state.dataPowerInductionUnits->MyEnvrnFlag(PIUNum) = true;
785 : }
786 :
787 : // Do the start of HVAC time step initializations
788 15921 : if (FirstHVACIteration) {
789 : // check for upstream zero flow. If nonzero and schedule ON, set primary flow to max
790 7983 : if (thisPIU.availSched->getCurrentVal() > 0.0 && state.dataLoopNodes->Node(PriNode).MassFlowRate > 0.0) {
791 7944 : if (thisPIU.UnitType == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
792 7944 : state.dataLoopNodes->Node(PriNode).MassFlowRate = thisPIU.MaxPriAirMassFlow;
793 7944 : state.dataLoopNodes->Node(SecNode).MassFlowRate = max(0.0, thisPIU.MaxTotAirMassFlow - thisPIU.MaxPriAirMassFlow);
794 : } else {
795 0 : state.dataLoopNodes->Node(PriNode).MassFlowRate = thisPIU.MaxPriAirMassFlow;
796 0 : state.dataLoopNodes->Node(SecNode).MassFlowRate = thisPIU.MaxSecAirMassFlow;
797 : }
798 : } else {
799 39 : state.dataLoopNodes->Node(PriNode).MassFlowRate = 0.0;
800 39 : state.dataLoopNodes->Node(SecNode).MassFlowRate = 0.0;
801 : }
802 : // reset the max and min avail flows
803 7983 : if (thisPIU.availSched->getCurrentVal() > 0.0 && state.dataLoopNodes->Node(PriNode).MassFlowRateMaxAvail > 0.0) {
804 7944 : if (thisPIU.UnitType == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
805 7944 : state.dataLoopNodes->Node(PriNode).MassFlowRateMaxAvail = thisPIU.MaxPriAirMassFlow;
806 7944 : state.dataLoopNodes->Node(PriNode).MassFlowRateMinAvail = thisPIU.MinPriAirMassFlow;
807 7944 : state.dataLoopNodes->Node(SecNode).MassFlowRateMaxAvail = max(0.0, thisPIU.MaxTotAirMassFlow - thisPIU.MinPriAirMassFlow);
808 7944 : state.dataLoopNodes->Node(SecNode).MassFlowRateMinAvail = max(0.0, thisPIU.MaxTotAirMassFlow - thisPIU.MaxPriAirMassFlow);
809 : } else {
810 0 : state.dataLoopNodes->Node(PriNode).MassFlowRateMaxAvail = thisPIU.MaxPriAirMassFlow;
811 0 : state.dataLoopNodes->Node(PriNode).MassFlowRateMinAvail = thisPIU.MinPriAirMassFlow;
812 0 : state.dataLoopNodes->Node(SecNode).MassFlowRateMaxAvail = thisPIU.MaxSecAirMassFlow;
813 0 : state.dataLoopNodes->Node(SecNode).MassFlowRateMinAvail = 0.0;
814 : }
815 : } else {
816 39 : state.dataLoopNodes->Node(PriNode).MassFlowRateMaxAvail = 0.0;
817 39 : state.dataLoopNodes->Node(PriNode).MassFlowRateMinAvail = 0.0;
818 39 : state.dataLoopNodes->Node(SecNode).MassFlowRateMaxAvail = 0.0;
819 39 : state.dataLoopNodes->Node(SecNode).MassFlowRateMinAvail = 0.0;
820 : }
821 : }
822 :
823 : // Do the following initializations every time step
824 :
825 : // None needed
826 15921 : }
827 :
828 15 : void SizePIU(EnergyPlusData &state, int const PIUNum)
829 : {
830 :
831 : // SUBROUTINE INFORMATION:
832 : // AUTHOR Fred Buhl
833 : // DATE WRITTEN January 2002
834 : // MODIFIED August 2013 Daeho Kang, add component sizing table entries
835 : // RE-ENGINEERED na
836 :
837 : // PURPOSE OF THIS SUBROUTINE:
838 : // This subroutine is for sizing PIU terminal units for which flow rates have not been
839 : // specified in the input.
840 :
841 : // METHODOLOGY EMPLOYED:
842 : // Obtains flow rates from the zone or system sizing arrays.
843 :
844 : // Using/Aliasing
845 : using namespace DataSizing;
846 : using SteamCoils::GetCoilSteamInletNode;
847 : using SteamCoils::GetCoilSteamOutletNode;
848 : using WaterCoils::GetCoilWaterInletNode;
849 : using WaterCoils::GetCoilWaterOutletNode;
850 : using WaterCoils::SetCoilDesFlow;
851 :
852 : using PlantUtilities::MyPlantSizingIndex;
853 :
854 : // SUBROUTINE PARAMETER DEFINITIONS:
855 : static constexpr std::string_view RoutineName("SizePIU");
856 :
857 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
858 15 : bool IsAutoSize = false; // Indicator to autosize
859 15 : bool IsMaxPriFlowAutoSize = false; // Indicate if the maximum terminal flow is autosize
860 15 : int SysSizNum = 0; // System sizing number
861 15 : Real64 DesCoilLoad = 0.0;
862 15 : bool ErrorsFound = false;
863 :
864 15 : auto &TermUnitSizing(state.dataSize->TermUnitSizing);
865 15 : auto &CurTermUnitSizingNum(state.dataSize->CurTermUnitSizingNum);
866 15 : auto &thisPIU = state.dataPowerInductionUnits->PIU(PIUNum);
867 :
868 15 : if (thisPIU.MaxPriAirVolFlow == AutoSize) {
869 6 : IsAutoSize = true;
870 : }
871 15 : if ((state.dataSize->CurZoneEqNum > 0) && (CurTermUnitSizingNum > 0)) {
872 7 : if (!IsAutoSize && !state.dataSize->ZoneSizingRunDone) { // Simulation continue
873 0 : if (thisPIU.MaxPriAirVolFlow > 0.0) {
874 0 : BaseSizer::reportSizerOutput(
875 : state, thisPIU.UnitType, thisPIU.Name, "User-Specified Maximum Primary Air Flow Rate [m3/s]", thisPIU.MaxPriAirVolFlow);
876 : }
877 : } else {
878 7 : CheckZoneSizing(state, thisPIU.UnitType, thisPIU.Name);
879 : // Autosized maximum primary air flow for reporting
880 7 : Real64 MaxPriAirVolFlowDes = max(state.dataSize->TermUnitFinalZoneSizing(CurTermUnitSizingNum).DesCoolVolFlow,
881 7 : state.dataSize->TermUnitFinalZoneSizing(CurTermUnitSizingNum).DesHeatVolFlow);
882 7 : if (MaxPriAirVolFlowDes < SmallAirVolFlow) {
883 0 : MaxPriAirVolFlowDes = 0.0;
884 : }
885 :
886 7 : if (IsAutoSize) {
887 6 : thisPIU.MaxPriAirVolFlow = MaxPriAirVolFlowDes;
888 6 : IsMaxPriFlowAutoSize = true;
889 6 : BaseSizer::reportSizerOutput(
890 : state, thisPIU.UnitType, thisPIU.Name, "Design Size Maximum Primary Air Flow Rate [m3/s]", MaxPriAirVolFlowDes);
891 : } else {
892 1 : if (thisPIU.MaxPriAirVolFlow > 0.0 && MaxPriAirVolFlowDes > 0.0) {
893 : // Hardsized maximum primary air flow for reporting
894 1 : Real64 const MaxPriAirVolFlowUser = thisPIU.MaxPriAirVolFlow;
895 1 : BaseSizer::reportSizerOutput(state,
896 : thisPIU.UnitType,
897 : thisPIU.Name,
898 : "Design Size Maximum Primary Air Flow Rate [m3/s]",
899 : MaxPriAirVolFlowDes,
900 : "User-Specified Maximum Primary Air Flow Rate [m3/s]",
901 : MaxPriAirVolFlowUser);
902 1 : if (state.dataGlobal->DisplayExtraWarnings) {
903 1 : if ((std::abs(MaxPriAirVolFlowDes - MaxPriAirVolFlowUser) / MaxPriAirVolFlowUser) >
904 1 : state.dataSize->AutoVsHardSizingThreshold) {
905 1 : ShowMessage(state, format("SizePIU: Potential issue with equipment sizing for {} {}", thisPIU.UnitType, thisPIU.Name));
906 1 : ShowContinueError(state, format("User-Specified Primary Air Flow Rate of {:.5R} [m3/s]", MaxPriAirVolFlowUser));
907 1 : ShowContinueError(state, format("differs from Design Size Primary Air Flow Rate of {:.5R} [m3/s]", MaxPriAirVolFlowDes));
908 2 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
909 3 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
910 : }
911 : }
912 : }
913 : }
914 : }
915 : }
916 :
917 15 : IsAutoSize = false;
918 15 : if (thisPIU.MaxTotAirVolFlow == AutoSize) {
919 7 : IsAutoSize = true;
920 : }
921 15 : if ((state.dataSize->CurZoneEqNum > 0) && (CurTermUnitSizingNum > 0)) {
922 7 : if (!IsAutoSize && !state.dataSize->ZoneSizingRunDone) { // Simulation continue
923 0 : if (thisPIU.MaxTotAirVolFlow > 0.0) {
924 0 : BaseSizer::reportSizerOutput(
925 : state, thisPIU.UnitType, thisPIU.Name, "User-Specified Maximum Air Flow Rate [m3/s]", thisPIU.MaxTotAirVolFlow);
926 : }
927 : } else {
928 7 : CheckZoneSizing(state, thisPIU.UnitType, thisPIU.Name);
929 : // Autosized maximum air flow for reporting
930 7 : Real64 MaxTotAirVolFlowDes = max(state.dataSize->TermUnitFinalZoneSizing(CurTermUnitSizingNum).DesCoolVolFlow,
931 7 : state.dataSize->TermUnitFinalZoneSizing(CurTermUnitSizingNum).DesHeatVolFlow);
932 7 : if (MaxTotAirVolFlowDes < SmallAirVolFlow) {
933 0 : MaxTotAirVolFlowDes = 0.0;
934 : }
935 7 : if (IsAutoSize) {
936 7 : thisPIU.MaxTotAirVolFlow = MaxTotAirVolFlowDes;
937 7 : BaseSizer::reportSizerOutput(state, thisPIU.UnitType, thisPIU.Name, "Design Size Maximum Air Flow Rate [m3/s]", MaxTotAirVolFlowDes);
938 : } else {
939 0 : if (thisPIU.MaxTotAirVolFlow > 0.0 && MaxTotAirVolFlowDes > 0.0) {
940 : // Hardsized maximum air flow for reporting
941 0 : Real64 const MaxTotAirVolFlowUser = thisPIU.MaxTotAirVolFlow;
942 0 : BaseSizer::reportSizerOutput(state,
943 : thisPIU.UnitType,
944 : thisPIU.Name,
945 : "Design Size Maximum Air Flow Rate [m3/s]",
946 : MaxTotAirVolFlowDes,
947 : "User-Specified Maximum Air Flow Rate [m3/s]",
948 : MaxTotAirVolFlowUser);
949 0 : if (state.dataGlobal->DisplayExtraWarnings) {
950 0 : if ((std::abs(MaxTotAirVolFlowDes - MaxTotAirVolFlowUser) / MaxTotAirVolFlowUser) >
951 0 : state.dataSize->AutoVsHardSizingThreshold) {
952 0 : ShowMessage(state, format("SizePIU: Potential issue with equipment sizing for {} {}", thisPIU.UnitType, thisPIU.Name));
953 0 : ShowContinueError(state, format("User-Specified Maximum Air Flow Rate of {:.5R} [m3/s]", MaxTotAirVolFlowUser));
954 0 : ShowContinueError(state, format("differs from Design Size Maximum Air Flow Rate of {:.5R} [m3/s]", MaxTotAirVolFlowDes));
955 0 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
956 0 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
957 : }
958 : }
959 : }
960 : }
961 : }
962 : }
963 :
964 : // if a sizing run has been done, check if system sizing has been done for this system
965 15 : bool SizingDesRunThisAirSys = false;
966 15 : if (state.dataSize->SysSizingRunDone) {
967 6 : int const AirLoopNum = state.dataZoneEquip->ZoneEquipConfig(thisPIU.CtrlZoneNum).InletNodeAirLoopNum(thisPIU.ctrlZoneInNodeIndex);
968 6 : if (AirLoopNum > 0) {
969 6 : CheckThisAirSystemForSizing(state, AirLoopNum, SizingDesRunThisAirSys);
970 : }
971 :
972 : // get system sizing id if a sizing run has been done for this system
973 6 : if (SizingDesRunThisAirSys) {
974 6 : SysSizNum = Util::FindItemInList(
975 6 : state.dataSize->FinalSysSizing(AirLoopNum).AirPriLoopName, state.dataSize->SysSizInput, &SystemSizingInputData::AirPriLoopName);
976 6 : if (SysSizNum == 0) {
977 0 : SysSizNum = 1; // use first when none applicable
978 : }
979 : }
980 : }
981 :
982 15 : IsAutoSize = false;
983 15 : if (thisPIU.MaxSecAirVolFlow == AutoSize) {
984 1 : IsAutoSize = true;
985 : }
986 15 : if ((state.dataSize->CurZoneEqNum > 0) && (CurTermUnitSizingNum > 0)) {
987 7 : if (!IsAutoSize && !state.dataSize->ZoneSizingRunDone) { // Simulation continue
988 0 : if (thisPIU.MaxSecAirVolFlow > 0.0) {
989 0 : BaseSizer::reportSizerOutput(
990 : state, thisPIU.UnitType, thisPIU.Name, "User-Specified Maximum Secondary Air Flow Rate [m3/s]", thisPIU.MaxSecAirVolFlow);
991 : }
992 : } else {
993 7 : CheckZoneSizing(state, thisPIU.UnitType, thisPIU.Name);
994 : // Autosized maximum secondary air flow for reporting
995 7 : Real64 MaxSecAirVolFlowDes = max(state.dataSize->TermUnitFinalZoneSizing(CurTermUnitSizingNum).DesCoolVolFlow,
996 7 : state.dataSize->TermUnitFinalZoneSizing(CurTermUnitSizingNum).DesHeatVolFlow);
997 7 : if (MaxSecAirVolFlowDes < SmallAirVolFlow) {
998 0 : MaxSecAirVolFlowDes = 0.0;
999 : }
1000 7 : if (IsAutoSize) {
1001 1 : thisPIU.MaxSecAirVolFlow = MaxSecAirVolFlowDes;
1002 1 : BaseSizer::reportSizerOutput(
1003 : state, thisPIU.UnitType, thisPIU.Name, "Design Size Maximum Secondary Air Flow Rate [m3/s]", MaxSecAirVolFlowDes);
1004 : } else {
1005 6 : if (thisPIU.MaxSecAirVolFlow > 0.0 && MaxSecAirVolFlowDes > 0.0) {
1006 : // Harsized maximum secondary air flow for reporting
1007 0 : Real64 const MaxSecAirVolFlowUser = thisPIU.MaxSecAirVolFlow;
1008 0 : BaseSizer::reportSizerOutput(state,
1009 : thisPIU.UnitType,
1010 : thisPIU.Name,
1011 : "Design Size Maximum Secondary Air Flow Rate [m3/s]",
1012 : MaxSecAirVolFlowDes,
1013 : "User-Specified Maximum Secondary Air Flow Rate [m3/s]",
1014 : MaxSecAirVolFlowUser);
1015 0 : if (state.dataGlobal->DisplayExtraWarnings) {
1016 0 : if ((std::abs(MaxSecAirVolFlowDes - MaxSecAirVolFlowUser) / MaxSecAirVolFlowUser) >
1017 0 : state.dataSize->AutoVsHardSizingThreshold) {
1018 0 : ShowMessage(state, format("SizePIU: Potential issue with equipment sizing for {} {}", thisPIU.UnitType, thisPIU.Name));
1019 0 : ShowContinueError(state, format("User-Specified Maximum Secondary Air Flow Rate of {:.5R} [m3/s]", MaxSecAirVolFlowUser));
1020 0 : ShowContinueError(
1021 0 : state, format("differs from Design Size Maximum Secondary Air Flow Rate of {:.5R} [m3/s]", MaxSecAirVolFlowDes));
1022 0 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
1023 0 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
1024 : }
1025 : }
1026 : }
1027 : }
1028 : }
1029 : }
1030 :
1031 15 : IsAutoSize = false;
1032 15 : if (thisPIU.MinPriAirFlowFrac == AutoSize) {
1033 7 : IsAutoSize = true;
1034 : }
1035 15 : if ((state.dataSize->CurZoneEqNum > 0) && (CurTermUnitSizingNum > 0)) {
1036 7 : if (!IsAutoSize && !state.dataSize->ZoneSizingRunDone) { // Simulation continue
1037 0 : if (thisPIU.MinPriAirFlowFrac > 0.0) {
1038 0 : BaseSizer::reportSizerOutput(
1039 : state, thisPIU.UnitType, thisPIU.Name, "User-Specified Minimum Primary Air Flow Fraction", thisPIU.MinPriAirFlowFrac);
1040 : }
1041 : } else {
1042 7 : CheckZoneSizing(state, thisPIU.UnitType, thisPIU.Name);
1043 : // Autosized minimum primary air flow fraction for reporting
1044 7 : Real64 MinPriAirFlowFracDes = 0.0;
1045 14 : if (thisPIU.MaxPriAirVolFlow >= SmallAirVolFlow &&
1046 7 : state.dataSize->TermUnitFinalZoneSizing(CurTermUnitSizingNum).MinOA >= SmallAirVolFlow) {
1047 6 : MinPriAirFlowFracDes = state.dataSize->TermUnitFinalZoneSizing(CurTermUnitSizingNum).MinOA / thisPIU.MaxPriAirVolFlow;
1048 : }
1049 7 : if (SizingDesRunThisAirSys) {
1050 6 : if (state.dataSize->SysSizInput(SysSizNum).SystemOAMethod == SysOAMethod::SP) { // 62.1 simplified procedure
1051 5 : if (thisPIU.MaxPriAirVolFlow > 0.0) {
1052 5 : MinPriAirFlowFracDes = 1.5 *
1053 5 : max(state.dataSize->TermUnitFinalZoneSizing(state.dataSize->CurTermUnitSizingNum).VozClgByZone,
1054 5 : state.dataSize->TermUnitFinalZoneSizing(state.dataSize->CurTermUnitSizingNum).VozHtgByZone) /
1055 5 : thisPIU.MaxPriAirVolFlow;
1056 :
1057 : // adjust maximum flow rate
1058 5 : if (MinPriAirFlowFracDes > 1.0 && IsMaxPriFlowAutoSize) {
1059 4 : thisPIU.MaxPriAirVolFlow *= MinPriAirFlowFracDes;
1060 4 : MinPriAirFlowFracDes = 1.0;
1061 8 : ShowWarningError(state,
1062 8 : format("SingleDuctSystem:SizeSys: Autosized maximum air flow rate for {} was increased to meet the zone "
1063 : "primary air flow determined according to the ASHRAE Standard 62.1 Simplified Procedure.",
1064 4 : thisPIU.Name));
1065 1 : } else if (MinPriAirFlowFracDes > 1.0) {
1066 2 : ShowWarningError(
1067 : state,
1068 2 : format("SingleDuctSystem:SizeSys: Maximum primary air flow rate for {} is potentially too low.", thisPIU.Name));
1069 2 : ShowContinueError(state,
1070 : "The flow is lower than the minimum primary air flow rate calculated following the ASHRAE Standard "
1071 : "62.1 Simplified Procedure:");
1072 1 : ShowContinueError(state, format(" User-specified maximum primary air flow rate: {:.3R} m3/s.", thisPIU.MaxPriAirVolFlow));
1073 2 : ShowContinueError(
1074 : state,
1075 2 : format(" Calculated minimum primary air flow rate: {:.3R} m3/s.", thisPIU.MaxPriAirVolFlow * MinPriAirFlowFracDes));
1076 1 : MinPriAirFlowFracDes = 1.0;
1077 : }
1078 : }
1079 : }
1080 : }
1081 7 : if (IsAutoSize) {
1082 7 : if (SizingDesRunThisAirSys) {
1083 6 : if (state.dataSize->SysSizInput(SysSizNum).SystemOAMethod == SysOAMethod::SP) {
1084 5 : state.dataSize->TermUnitFinalZoneSizing(state.dataSize->CurTermUnitSizingNum).VpzMinByZoneSPSized = true;
1085 : }
1086 : }
1087 7 : thisPIU.MinPriAirFlowFrac = MinPriAirFlowFracDes;
1088 7 : BaseSizer::reportSizerOutput(
1089 : state, thisPIU.UnitType, thisPIU.Name, "Design Size Minimum Primary Air Flow Fraction", MinPriAirFlowFracDes);
1090 : } else {
1091 0 : if (thisPIU.MinPriAirFlowFrac > 0.0 && MinPriAirFlowFracDes > 0.0) {
1092 : // Hardsized minimum primary air flow fraction for reporting
1093 0 : Real64 const MinPriAirFlowFracUser = thisPIU.MinPriAirFlowFrac;
1094 0 : BaseSizer::reportSizerOutput(state,
1095 : thisPIU.UnitType,
1096 : thisPIU.Name,
1097 : "Design Size Minimum Primary Air Flow Fraction",
1098 : MinPriAirFlowFracDes,
1099 : "User-Specified Minimum Primary Air Flow Fraction",
1100 : MinPriAirFlowFracUser);
1101 0 : if (state.dataGlobal->DisplayExtraWarnings) {
1102 0 : if ((std::abs(MinPriAirFlowFracDes - MinPriAirFlowFracUser) / MinPriAirFlowFracUser) >
1103 0 : state.dataSize->AutoVsHardSizingThreshold) {
1104 0 : ShowMessage(state, format("SizePIU: Potential issue with equipment sizing for {} {}", thisPIU.UnitType, thisPIU.Name));
1105 0 : ShowContinueError(state, format("User-Specified Minimum Primary Air Flow Fraction of {:.1R}", MinPriAirFlowFracUser));
1106 0 : ShowContinueError(state,
1107 0 : format("differs from Design Size Minimum Primary Air Flow Fraction of {:.1R}", MinPriAirFlowFracDes));
1108 0 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
1109 0 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
1110 : }
1111 : }
1112 : }
1113 : }
1114 : }
1115 : }
1116 :
1117 15 : if (CurTermUnitSizingNum > 0) {
1118 :
1119 7 : if (thisPIU.UnitType_Num == DataDefineEquip::ZnAirLoopEquipType::SingleDuct_SeriesPIU_Reheat) {
1120 7 : TermUnitSizing(CurTermUnitSizingNum).AirVolFlow = thisPIU.MaxTotAirVolFlow;
1121 0 : } else if (thisPIU.UnitType_Num == DataDefineEquip::ZnAirLoopEquipType::SingleDuct_ParallelPIU_Reheat) {
1122 0 : TermUnitSizing(CurTermUnitSizingNum).AirVolFlow = thisPIU.MaxSecAirVolFlow + thisPIU.MinPriAirFlowFrac * thisPIU.MaxPriAirVolFlow;
1123 : }
1124 : }
1125 :
1126 15 : IsAutoSize = false;
1127 15 : if (thisPIU.FanOnFlowFrac == AutoSize) {
1128 1 : IsAutoSize = true;
1129 : }
1130 15 : if (state.dataSize->CurZoneEqNum > 0) {
1131 7 : if (!IsAutoSize && !state.dataSize->ZoneSizingRunDone) { // Simulation continue
1132 0 : if (thisPIU.FanOnFlowFrac > 0.0) {
1133 0 : BaseSizer::reportSizerOutput(state, thisPIU.UnitType, thisPIU.Name, "User-Specified Fan On Flow Fraction", thisPIU.FanOnFlowFrac);
1134 : }
1135 : } else {
1136 7 : CheckZoneSizing(state, thisPIU.UnitType, thisPIU.Name);
1137 : // Autosized fan on flow fraction for reporting
1138 7 : Real64 FanOnFlowFracDes = thisPIU.MinPriAirFlowFrac;
1139 7 : if (IsAutoSize) {
1140 1 : thisPIU.FanOnFlowFrac = FanOnFlowFracDes;
1141 1 : BaseSizer::reportSizerOutput(state, thisPIU.UnitType, thisPIU.Name, "Design Size Fan On Flow Fraction", FanOnFlowFracDes);
1142 : } else {
1143 6 : if (thisPIU.FanOnFlowFrac > 0.0 && FanOnFlowFracDes > 0.0) {
1144 : // Hardsized fan on flow fraction for reporting
1145 0 : Real64 const FanOnFlowFracUser = thisPIU.FanOnFlowFrac;
1146 0 : BaseSizer::reportSizerOutput(state,
1147 : thisPIU.UnitType,
1148 : thisPIU.Name,
1149 : "Design Size Fan On Flow Fraction",
1150 : FanOnFlowFracDes,
1151 : "User-Specified Fan On Flow Fraction",
1152 : FanOnFlowFracUser);
1153 0 : if (state.dataGlobal->DisplayExtraWarnings) {
1154 0 : if ((std::abs(FanOnFlowFracDes - FanOnFlowFracUser) / FanOnFlowFracUser) > state.dataSize->AutoVsHardSizingThreshold) {
1155 0 : ShowMessage(state, format("SizePIU: Potential issue with equipment sizing for {} {}", thisPIU.UnitType, thisPIU.Name));
1156 0 : ShowContinueError(state, format("User-Specified Fan On Flow Fraction of {:.1R}", FanOnFlowFracUser));
1157 0 : ShowContinueError(state, format("differs from Design Size Fan On Flow Fraction of {:.1R}", FanOnFlowFracDes));
1158 0 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
1159 0 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
1160 : }
1161 : }
1162 : }
1163 : }
1164 : }
1165 : }
1166 :
1167 15 : IsAutoSize = false;
1168 15 : if (thisPIU.MaxVolHotWaterFlow == AutoSize) { //.or.()) THEN
1169 6 : IsAutoSize = true;
1170 : }
1171 15 : if ((state.dataSize->CurZoneEqNum > 0) && (CurTermUnitSizingNum > 0)) {
1172 7 : if (!IsAutoSize && !state.dataSize->ZoneSizingRunDone) { // Simulation continue
1173 0 : if (thisPIU.MaxVolHotWaterFlow > 0.0) {
1174 0 : BaseSizer::reportSizerOutput(
1175 : state, thisPIU.UnitType, thisPIU.Name, "User-Specified Maximum Reheat Water Flow Rate [m3/s]", thisPIU.MaxVolHotWaterFlow);
1176 : }
1177 : } else {
1178 7 : CheckZoneSizing(state, thisPIU.UnitType, thisPIU.Name);
1179 7 : if (Util::SameString(HCoilNamesUC[static_cast<int>(thisPIU.HCoilType)], "Coil:Heating:Water")) {
1180 :
1181 0 : int const CoilWaterInletNode = GetCoilWaterInletNode(state, "Coil:Heating:Water", thisPIU.HCoil, ErrorsFound);
1182 0 : int const CoilWaterOutletNode = GetCoilWaterOutletNode(state, "Coil:Heating:Water", thisPIU.HCoil, ErrorsFound);
1183 :
1184 : // Autosized maximum hot water flow for reporting
1185 0 : Real64 MaxVolHotWaterFlowDes = 0.0;
1186 :
1187 0 : if (IsAutoSize) {
1188 : int const PltSizHeatNum =
1189 0 : MyPlantSizingIndex(state, "Coil:Heating:Water", thisPIU.HCoil, CoilWaterInletNode, CoilWaterOutletNode, ErrorsFound);
1190 0 : if (PltSizHeatNum > 0) {
1191 :
1192 0 : if (state.dataSize->TermUnitFinalZoneSizing(CurTermUnitSizingNum).DesHeatMassFlow >= SmallAirVolFlow) {
1193 : Real64 const CoilInTemp =
1194 0 : state.dataSize->TermUnitFinalZoneSizing(CurTermUnitSizingNum).DesHeatCoilInTempTU * thisPIU.MinPriAirFlowFrac +
1195 0 : state.dataSize->TermUnitFinalZoneSizing(CurTermUnitSizingNum).ZoneTempAtHeatPeak * (1.0 - thisPIU.MinPriAirFlowFrac);
1196 0 : Real64 const CoilOutTemp = state.dataSize->TermUnitFinalZoneSizing(CurTermUnitSizingNum).HeatDesTemp;
1197 0 : Real64 const CoilOutHumRat = state.dataSize->TermUnitFinalZoneSizing(CurTermUnitSizingNum).HeatDesHumRat;
1198 0 : Real64 const DesMassFlow = state.dataEnvrn->StdRhoAir * TermUnitSizing(CurTermUnitSizingNum).AirVolFlow;
1199 0 : DesCoilLoad = PsyCpAirFnW(CoilOutHumRat) * DesMassFlow * (CoilOutTemp - CoilInTemp);
1200 :
1201 0 : Real64 const rho = state.dataPlnt->PlantLoop(thisPIU.HWplantLoc.loopNum)
1202 0 : .glycol->getDensity(state, Constant::HWInitConvTemp, RoutineName);
1203 0 : Real64 const Cp = state.dataPlnt->PlantLoop(thisPIU.HWplantLoc.loopNum)
1204 0 : .glycol->getSpecificHeat(state, Constant::HWInitConvTemp, RoutineName);
1205 :
1206 0 : MaxVolHotWaterFlowDes = DesCoilLoad / (state.dataSize->PlantSizData(PltSizHeatNum).DeltaT * Cp * rho);
1207 : } else {
1208 0 : MaxVolHotWaterFlowDes = 0.0;
1209 : }
1210 : } else {
1211 0 : ShowSevereError(state, "Autosizing of water flow requires a heating loop Sizing:Plant object");
1212 0 : ShowContinueError(state, format("Occurs in{} Object={}", thisPIU.UnitType, thisPIU.Name));
1213 0 : ErrorsFound = true;
1214 : }
1215 0 : thisPIU.MaxVolHotWaterFlow = MaxVolHotWaterFlowDes;
1216 0 : BaseSizer::reportSizerOutput(
1217 : state, thisPIU.UnitType, thisPIU.Name, "Design Size Maximum Reheat Water Flow Rate [m3/s]", MaxVolHotWaterFlowDes);
1218 0 : BaseSizer::reportSizerOutput(state,
1219 : thisPIU.UnitType,
1220 : thisPIU.Name,
1221 : "Design Size Reheat Coil Inlet Air Temperature [C]",
1222 0 : state.dataSize->TermUnitFinalZoneSizing(CurTermUnitSizingNum).DesHeatCoilInTempTU);
1223 0 : BaseSizer::reportSizerOutput(state,
1224 : thisPIU.UnitType,
1225 : thisPIU.Name,
1226 : "Design Size Reheat Coil Inlet Air Humidity Ratio [kgWater/kgDryAir]",
1227 0 : state.dataSize->TermUnitFinalZoneSizing(CurTermUnitSizingNum).DesHeatCoilInHumRatTU);
1228 : } else { // Hardsize with sizing data
1229 0 : if (thisPIU.MaxVolHotWaterFlow > 0.0 && MaxVolHotWaterFlowDes > 0.0) {
1230 : // Hardsized maximum hot water flow for reporting
1231 0 : Real64 const MaxVolHotWaterFlowUser = thisPIU.MaxVolHotWaterFlow;
1232 0 : BaseSizer::reportSizerOutput(state,
1233 : thisPIU.UnitType,
1234 : thisPIU.Name,
1235 : "Design Size Maximum Reheat Water Flow Rate [m3/s]",
1236 : MaxVolHotWaterFlowDes,
1237 : "User-Specified Maximum Reheat Water Flow Rate [m3/s]",
1238 : MaxVolHotWaterFlowUser);
1239 0 : if (state.dataGlobal->DisplayExtraWarnings) {
1240 0 : if ((std::abs(MaxVolHotWaterFlowDes - MaxVolHotWaterFlowUser) / MaxVolHotWaterFlowUser) >
1241 0 : state.dataSize->AutoVsHardSizingThreshold) {
1242 0 : ShowMessage(state,
1243 0 : format("SizePIU: Potential issue with equipment sizing for {} {}", thisPIU.UnitType, thisPIU.Name));
1244 0 : ShowContinueError(state,
1245 0 : format("User-Specified Maximum Reheat Water Flow Rate of {:.5R} [m3/s]", MaxVolHotWaterFlowUser));
1246 0 : ShowContinueError(
1247 0 : state, format("differs from Design Size Maximum Reheat Water Flow Rate of {:.5R} [m3/s]", MaxVolHotWaterFlowDes));
1248 0 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
1249 0 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
1250 : }
1251 : }
1252 : }
1253 : }
1254 : } else {
1255 7 : thisPIU.MaxVolHotWaterFlow = 0.0;
1256 : }
1257 : }
1258 : }
1259 :
1260 15 : IsAutoSize = false;
1261 15 : if (thisPIU.MaxVolHotSteamFlow == AutoSize) {
1262 1 : IsAutoSize = true;
1263 : }
1264 15 : if ((state.dataSize->CurZoneEqNum > 0) && (CurTermUnitSizingNum > 0)) {
1265 7 : if (!IsAutoSize && !state.dataSize->ZoneSizingRunDone) { // Simulation continue
1266 0 : if (thisPIU.MaxVolHotWaterFlow > 0.0) {
1267 0 : BaseSizer::reportSizerOutput(
1268 : state, thisPIU.UnitType, thisPIU.Name, "User-Specified Maximum Reheat Steam Flow Rate [m3/s]", thisPIU.MaxVolHotWaterFlow);
1269 : }
1270 : } else {
1271 7 : if (Util::SameString(HCoilNames[static_cast<int>(thisPIU.HCoilType)], "Coil:Heating:Steam")) {
1272 :
1273 0 : int const CoilSteamInletNode = GetCoilSteamInletNode(state, "Coil:Heating:Steam", thisPIU.HCoil, ErrorsFound);
1274 0 : int const CoilSteamOutletNode = GetCoilSteamOutletNode(state, "Coil:Heating:Steam", thisPIU.HCoil, ErrorsFound);
1275 0 : Real64 MaxVolHotSteamFlowDes = 0.0; // Autosized maximum hot steam flow for reporting
1276 :
1277 0 : if (IsAutoSize) {
1278 : int const PltSizHeatNum =
1279 0 : MyPlantSizingIndex(state, "Coil:Heating:Steam", thisPIU.HCoil, CoilSteamInletNode, CoilSteamOutletNode, ErrorsFound);
1280 0 : if (PltSizHeatNum > 0) {
1281 0 : if (state.dataSize->TermUnitFinalZoneSizing(CurTermUnitSizingNum).DesHeatMassFlow >= SmallAirVolFlow) {
1282 : Real64 const CoilInTemp =
1283 0 : state.dataSize->TermUnitFinalZoneSizing(CurTermUnitSizingNum).DesHeatCoilInTempTU * thisPIU.MinPriAirFlowFrac +
1284 0 : state.dataSize->TermUnitFinalZoneSizing(CurTermUnitSizingNum).ZoneTempAtHeatPeak * (1.0 - thisPIU.MinPriAirFlowFrac);
1285 0 : Real64 const CoilOutTemp = state.dataSize->TermUnitFinalZoneSizing(CurTermUnitSizingNum).HeatDesTemp;
1286 0 : Real64 const CoilOutHumRat = state.dataSize->TermUnitFinalZoneSizing(CurTermUnitSizingNum).HeatDesHumRat;
1287 0 : Real64 const DesMassFlow = state.dataEnvrn->StdRhoAir * TermUnitSizing(CurTermUnitSizingNum).AirVolFlow;
1288 0 : DesCoilLoad = PsyCpAirFnW(CoilOutHumRat) * DesMassFlow * (CoilOutTemp - CoilInTemp);
1289 0 : Real64 constexpr TempSteamIn = 100.00;
1290 0 : Real64 const EnthSteamInDry = thisPIU.HCoil_fluid->getSatEnthalpy(state, TempSteamIn, 1.0, RoutineName);
1291 0 : Real64 const EnthSteamOutWet = thisPIU.HCoil_fluid->getSatEnthalpy(state, TempSteamIn, 0.0, RoutineName);
1292 0 : Real64 const LatentHeatSteam = EnthSteamInDry - EnthSteamOutWet;
1293 0 : Real64 const SteamDensity = thisPIU.HCoil_fluid->getSatDensity(state, TempSteamIn, 1.0, RoutineName);
1294 : Real64 const Cp =
1295 0 : Fluid::GetWater(state)->getSpecificHeat(state, state.dataSize->PlantSizData(PltSizHeatNum).ExitTemp, RoutineName);
1296 0 : MaxVolHotSteamFlowDes =
1297 0 : DesCoilLoad / (SteamDensity * (LatentHeatSteam + state.dataSize->PlantSizData(PltSizHeatNum).DeltaT * Cp));
1298 : } else {
1299 0 : MaxVolHotSteamFlowDes = 0.0;
1300 : }
1301 : } else {
1302 0 : ShowSevereError(state, "Autosizing of Steam flow requires a heating loop Sizing:Plant object");
1303 0 : ShowContinueError(state, format("Occurs in{} Object={}", thisPIU.UnitType, thisPIU.Name));
1304 0 : ErrorsFound = true;
1305 : }
1306 0 : thisPIU.MaxVolHotSteamFlow = MaxVolHotSteamFlowDes;
1307 0 : BaseSizer::reportSizerOutput(
1308 : state, thisPIU.UnitType, thisPIU.Name, "Design Size Maximum Reheat Steam Flow [m3/s]", MaxVolHotSteamFlowDes);
1309 : } else {
1310 0 : if (thisPIU.MaxVolHotSteamFlow > 0.0 && MaxVolHotSteamFlowDes > 0.0) {
1311 0 : Real64 const MaxVolHotSteamFlowUser = thisPIU.MaxVolHotSteamFlow;
1312 0 : BaseSizer::reportSizerOutput(state,
1313 : thisPIU.UnitType,
1314 : thisPIU.Name,
1315 : "Design Size Maximum Reheat Steam Flow [m3/s]",
1316 : MaxVolHotSteamFlowDes,
1317 : "User-Specified Maximum Reheat Steam Flow [m3/s]",
1318 : MaxVolHotSteamFlowUser);
1319 0 : if (state.dataGlobal->DisplayExtraWarnings) {
1320 0 : if ((std::abs(MaxVolHotSteamFlowDes - MaxVolHotSteamFlowUser) / MaxVolHotSteamFlowUser) >
1321 0 : state.dataSize->AutoVsHardSizingThreshold) {
1322 0 : ShowMessage(state,
1323 0 : format("SizePIU: Potential issue with equipment sizing for {} {}", thisPIU.UnitType, thisPIU.Name));
1324 0 : ShowContinueError(state, format("User-Specified Maximum Reheat Steam Flow of {:.5R} [m3/s]", MaxVolHotSteamFlowUser));
1325 0 : ShowContinueError(
1326 0 : state, format("differs from Design Size Maximum Reheat Steam Flow of {:.5R} [m3/s]", MaxVolHotSteamFlowDes));
1327 0 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
1328 0 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
1329 : }
1330 : }
1331 : }
1332 : }
1333 : } else {
1334 7 : thisPIU.MaxVolHotSteamFlow = 0.0;
1335 : }
1336 : }
1337 : }
1338 :
1339 15 : if (CurTermUnitSizingNum > 0) {
1340 7 : TermUnitSizing(CurTermUnitSizingNum).MinPriFlowFrac = thisPIU.MinPriAirFlowFrac;
1341 7 : TermUnitSizing(CurTermUnitSizingNum).plenumIndex = thisPIU.plenumIndex;
1342 7 : TermUnitSizing(CurTermUnitSizingNum).MaxHWVolFlow = thisPIU.MaxVolHotWaterFlow;
1343 7 : TermUnitSizing(CurTermUnitSizingNum).MaxSTVolFlow = thisPIU.MaxVolHotSteamFlow;
1344 7 : TermUnitSizing(CurTermUnitSizingNum).DesHeatingLoad = DesCoilLoad; // coil report
1345 7 : TermUnitSizing(CurTermUnitSizingNum).InducesPlenumAir = thisPIU.InducesPlenumAir;
1346 7 : if (thisPIU.HCoilType == HtgCoilType::SimpleHeating) {
1347 0 : SetCoilDesFlow(state,
1348 0 : HCoilNamesUC[static_cast<int>(thisPIU.HCoilType)],
1349 0 : thisPIU.HCoil,
1350 0 : TermUnitSizing(CurTermUnitSizingNum).AirVolFlow,
1351 : ErrorsFound);
1352 : }
1353 : }
1354 :
1355 15 : if (ErrorsFound) {
1356 0 : ShowFatalError(state, "Preceding sizing errors cause program termination");
1357 : }
1358 15 : }
1359 :
1360 15932 : void CalcSeriesPIU(EnergyPlusData &state,
1361 : int const PIUNum, // number of the current PIU being simulated
1362 : int const ZoneNum, // number of zone being served
1363 : int const ZoneNode, // zone node number
1364 : bool const FirstHVACIteration // TRUE if 1st HVAC simulation of system timestep
1365 : )
1366 : {
1367 :
1368 : // SUBROUTINE INFORMATION:
1369 : // AUTHOR Fred Buhl
1370 : // DATE WRITTEN August 2000
1371 : // MODIFIED na
1372 : // RE-ENGINEERED na
1373 :
1374 : // PURPOSE OF THIS SUBROUTINE:
1375 : // Simulate a series powered induction unit; adjust its primary air flow
1376 : // and reheat coil output to match the zone load.
1377 :
1378 : // METHODOLOGY EMPLOYED:
1379 : // If unit is on and there is a cooling load:
1380 : // (1) simulates mixer and fan at max secondary air flow and heating coil
1381 : // off. Obtains fan temperature increase.
1382 : // (2) Calculates primary and secomdary air flow to meet zone load and
1383 : // resimulates mixer, fan, and (off) coil.
1384 : // If unit is on and there is a heating load
1385 : // (1) sets primary air flow to a minimum.
1386 : // (2) simulates mixer and fan
1387 : // (3) if reheat is hot water, calls ControlCompOutput to simulate hot
1388 : // water coil and adjust water flow to match coil output to the zone load.
1389 : // (4) if reheat is electric or gas calls SimulateHeatingCoilComponents to
1390 : // simulate coil at coil output that matches the zone load
1391 :
1392 : // REFERENCES:
1393 : // na
1394 :
1395 : // Using/Aliasing
1396 : using namespace DataZoneEnergyDemands;
1397 : using HeatingCoils::SimulateHeatingCoilComponents;
1398 : using MixerComponent::SimAirMixer;
1399 : using PlantUtilities::SetComponentFlowRate;
1400 : using SteamCoils::SimulateSteamCoilComponents;
1401 : using WaterCoils::SimulateWaterCoilComponents;
1402 :
1403 : // Locals
1404 : // SUBROUTINE ARGUMENT DEFINITIONS:
1405 :
1406 : // SUBROUTINE PARAMETER DEFINITIONS:
1407 :
1408 : // INTERFACE BLOCK SPECIFICATIONS
1409 :
1410 : // DERIVED TYPE DEFINITIONS
1411 : // na
1412 :
1413 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1414 15932 : bool UnitOn(true); // TRUE if unit is on
1415 15932 : bool PriOn(true); // TRUE if primary air available
1416 :
1417 15932 : Real64 QCoilReq = 0.0; // required heating coil outlet to meet zone load
1418 15932 : Real64 MaxWaterFlow = 0.0; // maximum water flow for heating or cooling [kg/s]
1419 15932 : Real64 MinWaterFlow = 0.0; // minimum water flow for heating or cooling [kg/s]
1420 :
1421 : // initialize local variables
1422 15932 : auto &thisPIU = state.dataPowerInductionUnits->PIU(PIUNum);
1423 :
1424 15932 : Real64 const PriAirMassFlowMax = state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRateMaxAvail; // max primary air mass flow rate [kg/s]
1425 15932 : Real64 const PriAirMassFlowMin = state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRateMinAvail; // min primary air mass flow rate [kg/s]
1426 : Real64 const QZnReq =
1427 15932 : state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneNum).RemainingOutputRequired; // heating or cooling needed by zone [Watts]
1428 : Real64 const QToHeatSetPt =
1429 15932 : state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneNum).RemainingOutputReqToHeatSP; // [W] remaining load to heating setpoint
1430 15932 : Real64 const CpAirZn = PsyCpAirFnW(state.dataLoopNodes->Node(ZoneNode).HumRat); // zone air specific heat [J/kg-C]
1431 15932 : thisPIU.PriAirMassFlow = state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate; // primary air mass flow rate [kg/s]
1432 15932 : thisPIU.SecAirMassFlow = state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate; // secondary air mass flow rate [kg/s]
1433 15932 : if (thisPIU.fanControlType == FanCntrlType::VariableSpeedFan) {
1434 8 : thisPIU.heatingOperatingMode = HeatOpModeType::HeaterOff;
1435 : } else {
1436 15924 : thisPIU.heatingOperatingMode = HeatOpModeType::ConstantVolumeHeat;
1437 : }
1438 15932 : thisPIU.coolingOperatingMode = CoolOpModeType::CoolerOff;
1439 :
1440 : // On the first HVAC iteration the system values are given to the controller, but after that
1441 : // the demand limits are in place and there needs to be feedback to the Zone Equipment
1442 15932 : if (thisPIU.HotControlNode > 0) {
1443 0 : if (FirstHVACIteration) {
1444 0 : MaxWaterFlow = thisPIU.MaxHotWaterFlow;
1445 0 : MinWaterFlow = thisPIU.MinHotWaterFlow;
1446 : } else {
1447 0 : MaxWaterFlow = state.dataLoopNodes->Node(thisPIU.HotControlNode).MassFlowRateMaxAvail;
1448 0 : MinWaterFlow = state.dataLoopNodes->Node(thisPIU.HotControlNode).MassFlowRateMinAvail;
1449 : }
1450 : }
1451 15932 : if (thisPIU.availSched->getCurrentVal() <= 0.0) {
1452 0 : UnitOn = false;
1453 : }
1454 15932 : if ((thisPIU.fanAvailSched->getCurrentVal() <= 0.0 || state.dataHVACGlobal->TurnFansOff) && !state.dataHVACGlobal->TurnFansOn) {
1455 1 : UnitOn = false;
1456 : }
1457 15932 : if (thisPIU.PriAirMassFlow <= SmallMassFlow || PriAirMassFlowMax <= SmallMassFlow) {
1458 37 : PriOn = false;
1459 : }
1460 : // Set the mass flow rates
1461 15932 : if (UnitOn) {
1462 : // unit is on
1463 15931 : if (!PriOn) {
1464 : // no primary air flow
1465 36 : thisPIU.PriAirMassFlow = 0.0;
1466 : // PIU fan off if there is no heating load, also reset fan flag if fan should be off
1467 36 : if (QZnReq <= SmallLoad) {
1468 22 : thisPIU.SecAirMassFlow = 0.0;
1469 22 : state.dataHVACGlobal->TurnFansOn = false;
1470 : } else {
1471 14 : if (thisPIU.fanControlType == FanCntrlType::VariableSpeedFan &&
1472 0 : thisPIU.heatingControlType == HeatCntrlBehaviorType::StagedHeaterBehavior) {
1473 0 : CalcVariableSpeedPIUStagedHeatingBehavior(state, PIUNum, ZoneNode, QZnReq, PriOn, thisPIU.PriAirMassFlow);
1474 14 : } else if (thisPIU.fanControlType == FanCntrlType::VariableSpeedFan &&
1475 0 : thisPIU.heatingControlType == HeatCntrlBehaviorType::ModulatedHeaterBehavior) {
1476 0 : CalcVariableSpeedPIUModulatedHeatingBehavior(state, PIUNum, ZoneNode, QZnReq, PriOn, thisPIU.PriAirMassFlow);
1477 14 : } else if (thisPIU.fanControlType == FanCntrlType::ConstantSpeedFan) {
1478 14 : thisPIU.heatingOperatingMode = HeatOpModeType::ConstantVolumeHeat;
1479 14 : thisPIU.SecAirMassFlow = thisPIU.MaxTotAirMassFlow;
1480 : }
1481 : }
1482 15895 : } else if (state.dataZoneEnergyDemand->CurDeadBandOrSetback(ZoneNum) || std::abs(QZnReq) < SmallLoad) {
1483 : // in deadband or very small load: set primary air flow to the minimum
1484 1470 : thisPIU.PriAirMassFlow = PriAirMassFlowMin;
1485 1470 : if (thisPIU.fanControlType == FanCntrlType::ConstantSpeedFan) {
1486 1470 : thisPIU.heatingOperatingMode = HeatOpModeType::ConstantVolumeHeat;
1487 1470 : thisPIU.SecAirMassFlow = max(0.0, thisPIU.MaxTotAirMassFlow - thisPIU.PriAirMassFlow);
1488 0 : } else if (thisPIU.fanControlType == FanCntrlType::VariableSpeedFan) {
1489 0 : thisPIU.SecAirMassFlow = max(0.0, thisPIU.MinTotAirMassFlow - thisPIU.PriAirMassFlow);
1490 : }
1491 14425 : } else if (QZnReq > SmallLoad) {
1492 : // heating: set primary air flow to the minimum
1493 7026 : thisPIU.PriAirMassFlow = PriAirMassFlowMin;
1494 : // determine secondary flow rate
1495 7026 : if (thisPIU.fanControlType == FanCntrlType::VariableSpeedFan &&
1496 6 : thisPIU.heatingControlType == HeatCntrlBehaviorType::StagedHeaterBehavior) {
1497 2 : CalcVariableSpeedPIUStagedHeatingBehavior(state, PIUNum, ZoneNode, QZnReq, PriOn, thisPIU.PriAirMassFlow);
1498 7024 : } else if (thisPIU.fanControlType == FanCntrlType::VariableSpeedFan &&
1499 4 : thisPIU.heatingControlType == HeatCntrlBehaviorType::ModulatedHeaterBehavior) {
1500 4 : CalcVariableSpeedPIUModulatedHeatingBehavior(state, PIUNum, ZoneNode, QZnReq, PriOn, thisPIU.PriAirMassFlow);
1501 7020 : } else if (thisPIU.fanControlType == FanCntrlType::ConstantSpeedFan) {
1502 7020 : thisPIU.heatingOperatingMode = HeatOpModeType::ConstantVolumeHeat;
1503 7020 : thisPIU.SecAirMassFlow = max(0.0, thisPIU.MaxTotAirMassFlow - thisPIU.PriAirMassFlow);
1504 : }
1505 : } else {
1506 7399 : if (thisPIU.fanControlType == FanCntrlType::ConstantSpeedFan) {
1507 : // cooling: set the primary air flow rate to meet the load.
1508 : // First calculate the fan temperature rise
1509 : // use only secondary air for this calculation
1510 7397 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = 0.0;
1511 7397 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = thisPIU.MaxTotAirMassFlow;
1512 7397 : SimAirMixer(state, thisPIU.MixerName, thisPIU.Mixer_Num); // fire the mixer
1513 7397 : state.dataFans->fans(thisPIU.Fan_Index)->simulate(state, FirstHVACIteration, _, _);
1514 :
1515 : // fan temperature rise [C]
1516 : Real64 const FanDeltaTemp =
1517 7397 : state.dataLoopNodes->Node(thisPIU.HCoilInAirNode).Temp - state.dataLoopNodes->Node(thisPIU.SecAirInNode).Temp;
1518 :
1519 : // using the required zone load, calculate the air temperature needed to meet the load
1520 7397 : Real64 const OutletTempNeeded = state.dataLoopNodes->Node(ZoneNode).Temp + QZnReq / (thisPIU.MaxTotAirMassFlow * CpAirZn);
1521 :
1522 : // mixer outlet temperature needed to meet cooling load
1523 7397 : Real64 const MixTempNeeded = OutletTempNeeded - FanDeltaTemp;
1524 :
1525 7397 : if (MixTempNeeded <= state.dataLoopNodes->Node(thisPIU.PriAirInNode).Temp) { //
1526 7381 : thisPIU.PriAirMassFlow = PriAirMassFlowMax;
1527 32 : } else if (MixTempNeeded >= state.dataLoopNodes->Node(thisPIU.PriAirInNode).Temp &&
1528 16 : MixTempNeeded >= state.dataLoopNodes->Node(thisPIU.SecAirInNode).Temp) {
1529 0 : thisPIU.PriAirMassFlow = PriAirMassFlowMin;
1530 : } else {
1531 16 : thisPIU.PriAirMassFlow =
1532 16 : thisPIU.MaxTotAirMassFlow * (state.dataLoopNodes->Node(thisPIU.SecAirInNode).Temp - MixTempNeeded) /
1533 16 : max(SmallTempDiff,
1534 16 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).Temp - state.dataLoopNodes->Node(thisPIU.PriAirInNode).Temp);
1535 16 : thisPIU.PriAirMassFlow = min(max(thisPIU.PriAirMassFlow, PriAirMassFlowMin), PriAirMassFlowMax);
1536 : }
1537 7397 : thisPIU.SecAirMassFlow = max(0.0, thisPIU.MaxTotAirMassFlow - thisPIU.PriAirMassFlow);
1538 7397 : if (QZnReq < 0) {
1539 7397 : thisPIU.coolingOperatingMode = CoolOpModeType::ConstantVolumeCool;
1540 : }
1541 2 : } else if (thisPIU.fanControlType == FanCntrlType::VariableSpeedFan) {
1542 2 : CalcVariableSpeedPIUCoolingBehavior(state, PIUNum, ZoneNode, QZnReq, QToHeatSetPt, PriAirMassFlowMin, PriAirMassFlowMax);
1543 : }
1544 : }
1545 : } else {
1546 : // unit is off ; no flow
1547 1 : thisPIU.PriAirMassFlow = 0.0;
1548 1 : thisPIU.SecAirMassFlow = 0.0;
1549 : }
1550 : // set inlet node flowrates
1551 15932 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = thisPIU.PriAirMassFlow;
1552 15932 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = thisPIU.SecAirMassFlow;
1553 15932 : if (PriAirMassFlowMax == 0) {
1554 37 : thisPIU.PriDamperPosition = 0;
1555 : } else {
1556 15895 : thisPIU.PriDamperPosition = thisPIU.PriAirMassFlow / PriAirMassFlowMax;
1557 : }
1558 :
1559 : // now that inlet airflows have been set, the terminal components can be simulated.
1560 : // fire the mixer
1561 15932 : SimAirMixer(state, thisPIU.MixerName, thisPIU.Mixer_Num);
1562 :
1563 : // fire the fan
1564 15932 : if (thisPIU.fanType == HVAC::FanType::SystemModel) {
1565 13817 : if (thisPIU.fanControlType == FanCntrlType::VariableSpeedFan) {
1566 : // calculate fan speed ratio
1567 8 : Real64 fanFlowRatio(1.0);
1568 8 : if (thisPIU.MaxTotAirMassFlow > 0.0) {
1569 8 : fanFlowRatio = (thisPIU.PriAirMassFlow + thisPIU.SecAirMassFlow) / thisPIU.MaxTotAirMassFlow;
1570 : }
1571 8 : state.dataFans->fans(thisPIU.Fan_Index)->simulate(state, FirstHVACIteration, fanFlowRatio, _);
1572 : } else {
1573 13809 : state.dataFans->fans(thisPIU.Fan_Index)->simulate(state, FirstHVACIteration, _, _);
1574 : }
1575 2115 : } else if (thisPIU.fanType == HVAC::FanType::Constant) {
1576 2115 : state.dataFans->fans(thisPIU.Fan_Index)->simulate(state, FirstHVACIteration, _, _);
1577 : }
1578 :
1579 : // the heating load seen by the reheat coil [W]
1580 15932 : Real64 QActualHeating = QToHeatSetPt - state.dataLoopNodes->Node(thisPIU.HCoilInAirNode).MassFlowRate * CpAirZn *
1581 15932 : (state.dataLoopNodes->Node(thisPIU.HCoilInAirNode).Temp - state.dataLoopNodes->Node(ZoneNode).Temp);
1582 :
1583 : // check if heating coil is off
1584 15931 : if (((!UnitOn) || (QActualHeating < SmallLoad) || (state.dataHeatBalFanSys->TempControlType(ZoneNum) == HVAC::SetptType::SingleCool) ||
1585 31867 : (thisPIU.PriAirMassFlow > PriAirMassFlowMin)) &&
1586 9119 : (thisPIU.heatingOperatingMode != HeatOpModeType::StagedHeatFirstStage)) { // reheat is off during the first stage of heating
1587 9118 : thisPIU.heatingOperatingMode = HeatOpModeType::HeaterOff;
1588 : }
1589 :
1590 : // determine what is required of heater for current operating stage
1591 15932 : if (thisPIU.heatingOperatingMode == HeatOpModeType::HeaterOff) {
1592 9118 : QCoilReq = 0.0;
1593 6814 : } else if (thisPIU.heatingOperatingMode == HeatOpModeType::StagedHeatFirstStage) {
1594 1 : QCoilReq = 0.0;
1595 6813 : } else if (thisPIU.heatingOperatingMode == HeatOpModeType::ConstantVolumeHeat) {
1596 6808 : QCoilReq = QActualHeating;
1597 5 : } else if (thisPIU.heatingOperatingMode == HeatOpModeType::StagedHeatSecondStage) {
1598 1 : QCoilReq = QActualHeating;
1599 4 : } else if (thisPIU.heatingOperatingMode == HeatOpModeType::ModulatedHeatFirstStage) {
1600 1 : QCoilReq = QActualHeating;
1601 3 : } else if (thisPIU.heatingOperatingMode == HeatOpModeType::ModulatedHeatSecondStage) {
1602 : // find heater power to deliver design discharge air temperature
1603 1 : Real64 targetDATEnthalpy = Psychrometrics::PsyHFnTdbW(thisPIU.designHeatingDAT, state.dataLoopNodes->Node(ZoneNode).HumRat);
1604 : Real64 mixAirEnthalpy =
1605 1 : Psychrometrics::PsyHFnTdbW(state.dataLoopNodes->Node(thisPIU.HCoilInAirNode).Temp, state.dataLoopNodes->Node(ZoneNode).HumRat);
1606 1 : QCoilReq = state.dataLoopNodes->Node(thisPIU.HCoilInAirNode).MassFlowRate * (targetDATEnthalpy - mixAirEnthalpy);
1607 2 : } else if (thisPIU.heatingOperatingMode == HeatOpModeType::ModulatedHeatThirdStage) {
1608 : // find heater power to deliver maximum discharge air temperature
1609 2 : Real64 HiLimitDATEnthalpy = Psychrometrics::PsyHFnTdbW(thisPIU.highLimitDAT, state.dataLoopNodes->Node(ZoneNode).HumRat);
1610 : Real64 mixAirEnthalpy =
1611 2 : Psychrometrics::PsyHFnTdbW(state.dataLoopNodes->Node(thisPIU.HCoilInAirNode).Temp, state.dataLoopNodes->Node(ZoneNode).HumRat);
1612 2 : Real64 QcoilLimit = state.dataLoopNodes->Node(thisPIU.HCoilInAirNode).MassFlowRate * (HiLimitDATEnthalpy - mixAirEnthalpy);
1613 2 : if (QcoilLimit < QActualHeating) { // if requried power is too high use limit of coil discharge
1614 1 : QCoilReq = QcoilLimit;
1615 : } else {
1616 1 : QCoilReq = QActualHeating;
1617 : }
1618 : } else {
1619 0 : ShowSevereError(state, "Incorrect series PIU heating operation.");
1620 0 : ShowFatalError(state, format("Series PIU control failed for {}:{}", thisPIU.UnitType, thisPIU.Name));
1621 : }
1622 15932 : if ((QCoilReq < SmallLoad) &&
1623 9119 : (thisPIU.heatingOperatingMode != HeatOpModeType::StagedHeatFirstStage)) { // reheat is off during the first stage of heating
1624 9118 : thisPIU.heatingOperatingMode = HeatOpModeType::HeaterOff;
1625 9118 : QCoilReq = 0.0;
1626 : }
1627 :
1628 : // fire the heating coil
1629 15932 : switch (thisPIU.HCoilType) {
1630 0 : case HtgCoilType::SimpleHeating: { // COIL:WATER:SIMPLEHEATING
1631 0 : if ((thisPIU.heatingOperatingMode == HeatOpModeType::HeaterOff) || (thisPIU.heatingOperatingMode == HeatOpModeType::StagedHeatFirstStage)) {
1632 : // call the reheat coil with the NO FLOW condition
1633 0 : Real64 mdot = 0.0;
1634 0 : SetComponentFlowRate(state, mdot, thisPIU.HotControlNode, thisPIU.HotCoilOutNodeNum, thisPIU.HWplantLoc);
1635 :
1636 0 : SimulateWaterCoilComponents(state, thisPIU.HCoil, FirstHVACIteration, thisPIU.HCoil_Index);
1637 0 : } else {
1638 : // control water flow to obtain output matching QZnReq
1639 0 : ControlCompOutput(state,
1640 0 : thisPIU.HCoil,
1641 0 : thisPIU.UnitType,
1642 0 : thisPIU.HCoil_Index,
1643 : FirstHVACIteration,
1644 : QCoilReq,
1645 : thisPIU.HotControlNode,
1646 : MaxWaterFlow,
1647 : MinWaterFlow,
1648 : thisPIU.HotControlOffset,
1649 0 : thisPIU.ControlCompTypeNum,
1650 0 : thisPIU.CompErrIndex,
1651 0 : thisPIU.HCoilInAirNode,
1652 0 : thisPIU.OutAirNode,
1653 : _,
1654 : _,
1655 : _,
1656 0 : thisPIU.HWplantLoc);
1657 : }
1658 0 : break;
1659 : }
1660 0 : case HtgCoilType::SteamAirHeating: { // COIL:STEAM:AIRHEATING
1661 0 : SimulateSteamCoilComponents(state, thisPIU.HCoil, FirstHVACIteration, thisPIU.HCoil_Index, QCoilReq);
1662 0 : break;
1663 : }
1664 2124 : case HtgCoilType::Electric: { // COIL:ELECTRIC:HEATING
1665 2124 : SimulateHeatingCoilComponents(state, thisPIU.HCoil, FirstHVACIteration, QCoilReq, thisPIU.HCoil_Index);
1666 2124 : break;
1667 : }
1668 13808 : case HtgCoilType::Gas: { // COIL:GAS:HEATING
1669 13808 : SimulateHeatingCoilComponents(state, thisPIU.HCoil, FirstHVACIteration, QCoilReq, thisPIU.HCoil_Index);
1670 13808 : break;
1671 : }
1672 0 : default:
1673 0 : break;
1674 : }
1675 :
1676 : // Power supplied
1677 15932 : Real64 PowerMet = state.dataLoopNodes->Node(thisPIU.OutAirNode).MassFlowRate *
1678 15932 : (PsyHFnTdbW(state.dataLoopNodes->Node(thisPIU.OutAirNode).Temp, state.dataLoopNodes->Node(ZoneNode).HumRat) -
1679 15932 : PsyHFnTdbW(state.dataLoopNodes->Node(ZoneNode).Temp, state.dataLoopNodes->Node(ZoneNode).HumRat));
1680 15932 : thisPIU.HeatingRate = max(0.0, PowerMet);
1681 15932 : thisPIU.SensCoolRate = std::abs(min(DataPrecisionGlobals::constant_zero, PowerMet));
1682 15932 : thisPIU.TotMassFlowRate = state.dataLoopNodes->Node(thisPIU.OutAirNode).MassFlowRate;
1683 15932 : thisPIU.SecMassFlowRate = state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate;
1684 15932 : thisPIU.PriMassFlowRate = state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate;
1685 15932 : thisPIU.DischargeAirTemp = state.dataLoopNodes->Node(thisPIU.OutAirNode).Temp;
1686 15932 : if (state.dataLoopNodes->Node(thisPIU.OutAirNode).MassFlowRate == 0.0) {
1687 29 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = 0.0;
1688 29 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = 0.0;
1689 : }
1690 15932 : if (thisPIU.InducesPlenumAir) {
1691 1 : state.dataHVACGlobal->PlenumInducedMassFlow = state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate;
1692 : } else {
1693 15931 : state.dataHVACGlobal->PlenumInducedMassFlow = 0.0;
1694 : }
1695 15932 : state.dataDefineEquipment->AirDistUnit(thisPIU.ADUNum).MassFlowRatePlenInd = state.dataHVACGlobal->PlenumInducedMassFlow;
1696 15932 : state.dataLoopNodes->Node(thisPIU.OutAirNode).MassFlowRateMax = thisPIU.MaxTotAirMassFlow;
1697 :
1698 15932 : ReportCurOperatingControlStage(state, PIUNum, UnitOn, thisPIU.heatingOperatingMode, thisPIU.coolingOperatingMode);
1699 15932 : }
1700 :
1701 14 : void CalcParallelPIU(EnergyPlusData &state,
1702 : int const PIUNum, // number of the current PIU being simulated
1703 : int const ZoneNum, // number of zone being served
1704 : int const ZoneNode, // zone node number
1705 : bool const FirstHVACIteration // TRUE if 1st HVAC simulation of system timestep
1706 : )
1707 : {
1708 :
1709 : // SUBROUTINE INFORMATION:
1710 : // AUTHOR Fred Buhl
1711 : // DATE WRITTEN August 2000
1712 : // MODIFIED September 2016, March 2017
1713 :
1714 : // PURPOSE OF THIS SUBROUTINE:
1715 : // Simulate a parallel powered induction unit; adjust its primary air flow
1716 : // and reheat coil output to match the zone load.
1717 :
1718 : // METHODOLOGY EMPLOYED:
1719 : // If unit is on and there is a cooling load:
1720 : // (1) simulate fan at max secondary air flow and heating coil
1721 : // off. Obtains fan temperature increase.
1722 : // (2) Calculates primary and secomdary air flow to meet zone load.
1723 : // (a) Assume fan is off and calculate primary air flow to meet cooling load.
1724 : // (b1) If calculated primary air flow is above the fan turn on ratio, fan is off.
1725 : // Otherwise fan is on; calculate mixed secondary and primary air flow that
1726 : // will meet the zone load
1727 : // (b2) If the fan turn on ratio is zero, then the fan is on only if reheat is needed.
1728 : // (3) Simulate fan, mixer, and (off) heating coil to obtain zone inlet conditions.
1729 : // If unit is on and there is a heating load
1730 : // (1) sets primary air flow to a minimum.
1731 : // (2) simulates fan and mixer
1732 : // (3) if reheat is hot water, calls ControlCompOutput to simulate hot
1733 : // water coil and adjust water flow to match coil output to the zone load.
1734 : // (4) if reheat is electric or gas calls SimulateHeatingCoilComponents to
1735 : // simulate coil at coil output that matches the zone load
1736 :
1737 : using namespace DataZoneEnergyDemands;
1738 : using HeatingCoils::SimulateHeatingCoilComponents;
1739 : using MixerComponent::SimAirMixer;
1740 : using PlantUtilities::SetComponentFlowRate;
1741 : using SteamCoils::SimulateSteamCoilComponents;
1742 : using WaterCoils::SimulateWaterCoilComponents;
1743 :
1744 14 : bool UnitOn(true); // TRUE if unit is on
1745 14 : bool PriOn(true); // TRUE if primary air available
1746 :
1747 14 : Real64 QCoilReq = 0.0; // required heating coil outlet to meet zone load
1748 14 : Real64 MaxWaterFlow = 0.0; // maximum water flow for heating or cooling [kg/s]
1749 14 : Real64 MinWaterFlow = 0.0; // minimum water flow for heating or cooling [kg/s]
1750 :
1751 : // initialize local variables
1752 14 : auto &thisPIU = state.dataPowerInductionUnits->PIU(PIUNum);
1753 :
1754 14 : Real64 const PriAirMassFlowMax = state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRateMaxAvail; // max primary air mass flow rate [kg/s]
1755 14 : Real64 const PriAirMassFlowMin = state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRateMinAvail; // min primary air mass flow rate [kg/s]
1756 14 : thisPIU.PriAirMassFlow = state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate; // primary air mass flow rate [kg/s]
1757 14 : thisPIU.SecAirMassFlow = state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate; // secondary air mass flow rate [kg/s]
1758 : Real64 const QZnReq =
1759 14 : state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneNum).RemainingOutputRequired; // heating or cooling needed by zone [Watts]
1760 : Real64 const QToHeatSetPt =
1761 14 : state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneNum).RemainingOutputReqToHeatSP; // [W] remaining load to heating setpoint
1762 14 : Real64 const CpAirZn = PsyCpAirFnW(state.dataLoopNodes->Node(ZoneNode).HumRat); // zone air specific heat [J/kg-C]
1763 14 : thisPIU.heatingOperatingMode = HeatOpModeType::HeaterOff;
1764 14 : thisPIU.coolingOperatingMode = CoolOpModeType::CoolerOff;
1765 :
1766 : // On the first HVAC iteration the system values are given to the controller, but after that
1767 : // the demand limits are in place and there needs to be feedback to the Zone Equipment
1768 14 : if (thisPIU.HotControlNode > 0) {
1769 0 : if (FirstHVACIteration) {
1770 0 : MaxWaterFlow = thisPIU.MaxHotWaterFlow;
1771 0 : MinWaterFlow = thisPIU.MinHotWaterFlow;
1772 : } else {
1773 0 : MaxWaterFlow = state.dataLoopNodes->Node(thisPIU.HotControlNode).MassFlowRateMaxAvail;
1774 0 : MinWaterFlow = state.dataLoopNodes->Node(thisPIU.HotControlNode).MassFlowRateMinAvail;
1775 : }
1776 : }
1777 14 : if (thisPIU.availSched->getCurrentVal() <= 0.0) {
1778 0 : UnitOn = false;
1779 : }
1780 14 : if (thisPIU.PriAirMassFlow <= SmallMassFlow || PriAirMassFlowMax <= SmallMassFlow) {
1781 5 : PriOn = false;
1782 : }
1783 : // Set the mass flow rates
1784 14 : if (UnitOn) {
1785 : // unit is on
1786 : // Calculate if reheat is needed
1787 14 : bool ReheatRequired = false;
1788 : Real64 const qMinPrimary =
1789 : PriAirMassFlowMin *
1790 14 : (CpAirZn * min(-SmallTempDiff, (state.dataLoopNodes->Node(thisPIU.PriAirInNode).Temp - state.dataLoopNodes->Node(ZoneNode).Temp)));
1791 14 : if (qMinPrimary < QToHeatSetPt) {
1792 9 : ReheatRequired = true;
1793 : }
1794 :
1795 14 : if (!PriOn) {
1796 : // no primary air flow
1797 5 : thisPIU.PriAirMassFlow = 0.0;
1798 : // PIU fan off if there is no heating load, also reset fan flag if fan should be off
1799 5 : if (QZnReq <= SmallLoad) {
1800 2 : thisPIU.SecAirMassFlow = 0.0;
1801 2 : state.dataHVACGlobal->TurnFansOn = false;
1802 : } else {
1803 3 : if (thisPIU.fanControlType == FanCntrlType::VariableSpeedFan &&
1804 0 : thisPIU.heatingControlType == HeatCntrlBehaviorType::StagedHeaterBehavior) {
1805 0 : CalcVariableSpeedPIUStagedHeatingBehavior(state, PIUNum, ZoneNode, QZnReq, PriOn, thisPIU.PriAirMassFlow);
1806 3 : } else if (thisPIU.fanControlType == FanCntrlType::VariableSpeedFan &&
1807 0 : thisPIU.heatingControlType == HeatCntrlBehaviorType::ModulatedHeaterBehavior) {
1808 0 : CalcVariableSpeedPIUModulatedHeatingBehavior(state, PIUNum, ZoneNode, QZnReq, PriOn, thisPIU.PriAirMassFlow);
1809 3 : } else if (thisPIU.fanControlType == FanCntrlType::ConstantSpeedFan) {
1810 3 : thisPIU.heatingOperatingMode = HeatOpModeType::ConstantVolumeHeat;
1811 3 : thisPIU.SecAirMassFlow = thisPIU.MaxSecAirMassFlow;
1812 : }
1813 : }
1814 9 : } else if (state.dataZoneEnergyDemand->CurDeadBandOrSetback(ZoneNum) || std::abs(QZnReq) < SmallLoad) {
1815 : // in deadband or very small load: set primary air flow to the minimum
1816 1 : thisPIU.PriAirMassFlow = PriAirMassFlowMin;
1817 : // PIU fan off if reheat is not needed, also reset fan flag if fan should be off
1818 1 : if (ReheatRequired) {
1819 1 : state.dataHVACGlobal->TurnFansOn = true;
1820 1 : if (thisPIU.fanControlType == FanCntrlType::ConstantSpeedFan) {
1821 1 : thisPIU.heatingOperatingMode = HeatOpModeType::ConstantVolumeHeat;
1822 1 : thisPIU.SecAirMassFlow = thisPIU.MaxSecAirMassFlow;
1823 0 : } else if (thisPIU.fanControlType == FanCntrlType::VariableSpeedFan) {
1824 0 : if (thisPIU.heatingControlType == HeatCntrlBehaviorType::StagedHeaterBehavior) {
1825 0 : thisPIU.heatingOperatingMode = HeatOpModeType::StagedHeatFirstStage;
1826 : } else {
1827 0 : thisPIU.heatingOperatingMode = HeatOpModeType::ModulatedHeatFirstStage;
1828 : }
1829 0 : thisPIU.SecAirMassFlow = thisPIU.MinSecAirMassFlow;
1830 : }
1831 : } else {
1832 0 : thisPIU.SecAirMassFlow = 0.0;
1833 0 : state.dataHVACGlobal->TurnFansOn = false;
1834 0 : thisPIU.heatingOperatingMode = HeatOpModeType::HeaterOff;
1835 : }
1836 8 : } else if (QZnReq > SmallLoad) {
1837 : // heating
1838 : // set primary air flow to the minimum
1839 7 : thisPIU.PriAirMassFlow = PriAirMassFlowMin;
1840 : // determine secondary flow rate
1841 7 : if (thisPIU.fanControlType == FanCntrlType::VariableSpeedFan &&
1842 6 : thisPIU.heatingControlType == HeatCntrlBehaviorType::StagedHeaterBehavior) {
1843 2 : CalcVariableSpeedPIUStagedHeatingBehavior(state, PIUNum, ZoneNode, QZnReq, PriOn, thisPIU.PriAirMassFlow);
1844 5 : } else if (thisPIU.fanControlType == FanCntrlType::VariableSpeedFan &&
1845 4 : thisPIU.heatingControlType == HeatCntrlBehaviorType::ModulatedHeaterBehavior) {
1846 4 : CalcVariableSpeedPIUModulatedHeatingBehavior(state, PIUNum, ZoneNode, QZnReq, PriOn, thisPIU.PriAirMassFlow);
1847 1 : } else if (thisPIU.fanControlType == FanCntrlType::ConstantSpeedFan) {
1848 1 : thisPIU.heatingOperatingMode = HeatOpModeType::ConstantVolumeHeat;
1849 1 : thisPIU.SecAirMassFlow = thisPIU.MaxSecAirMassFlow;
1850 : }
1851 : } else {
1852 : // cooling: set the primary air flow rate to meet the load.
1853 : // First calculate the fan temperature rise
1854 1 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = thisPIU.MaxSecAirMassFlow;
1855 1 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRateMaxAvail = thisPIU.MaxSecAirMassFlow;
1856 1 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = 0.0;
1857 :
1858 1 : state.dataFans->fans(thisPIU.Fan_Index)->simulate(state, FirstHVACIteration, _, _);
1859 :
1860 1 : SimAirMixer(state, thisPIU.MixerName, thisPIU.Mixer_Num); // fire the mixer
1861 : // fan temperature rise [C]
1862 1 : Real64 const FanDeltaTemp = state.dataLoopNodes->Node(thisPIU.HCoilInAirNode).Temp - state.dataLoopNodes->Node(thisPIU.SecAirInNode).Temp;
1863 : // Assuming the fan is off, calculate the primary air flow needed to meet the zone cooling demand.
1864 1 : thisPIU.PriAirMassFlow =
1865 1 : QZnReq /
1866 1 : (CpAirZn * min(-SmallTempDiff, (state.dataLoopNodes->Node(thisPIU.PriAirInNode).Temp - state.dataLoopNodes->Node(ZoneNode).Temp)));
1867 1 : thisPIU.PriAirMassFlow = min(max(thisPIU.PriAirMassFlow, PriAirMassFlowMin), PriAirMassFlowMax);
1868 : // check for fan on or off
1869 1 : if ((thisPIU.PriAirMassFlow > thisPIU.FanOnAirMassFlow) && !ReheatRequired) {
1870 0 : thisPIU.SecAirMassFlow = 0.0; // Fan is off unless reheat is required; no secondary air; also reset fan flag
1871 0 : state.dataHVACGlobal->TurnFansOn = false;
1872 : } else {
1873 : // fan is on; recalc primary air flow
1874 1 : thisPIU.PriAirMassFlow =
1875 2 : (QZnReq - CpAirZn * thisPIU.SecAirMassFlow *
1876 1 : (state.dataLoopNodes->Node(thisPIU.SecAirInNode).Temp + FanDeltaTemp - state.dataLoopNodes->Node(ZoneNode).Temp)) /
1877 1 : (CpAirZn *
1878 1 : min(-SmallTempDiff, (state.dataLoopNodes->Node(thisPIU.PriAirInNode).Temp - state.dataLoopNodes->Node(ZoneNode).Temp)));
1879 1 : thisPIU.PriAirMassFlow = min(max(thisPIU.PriAirMassFlow, PriAirMassFlowMin), PriAirMassFlowMax);
1880 1 : thisPIU.SecAirMassFlow = thisPIU.MaxSecAirMassFlow;
1881 : }
1882 1 : if (QZnReq < 0) {
1883 1 : if (thisPIU.fanControlType == FanCntrlType::VariableSpeedFan) {
1884 0 : if (thisPIU.PriAirMassFlow == PriAirMassFlowMax) {
1885 0 : thisPIU.coolingOperatingMode = CoolOpModeType::CoolSecondStage;
1886 : } else {
1887 0 : thisPIU.coolingOperatingMode = CoolOpModeType::CoolFirstStage;
1888 : }
1889 : } else {
1890 1 : thisPIU.coolingOperatingMode = CoolOpModeType::ConstantVolumeCool;
1891 : }
1892 : }
1893 : }
1894 : } else {
1895 : // unit is off; no flow
1896 0 : thisPIU.PriAirMassFlow = 0.0;
1897 0 : thisPIU.SecAirMassFlow = 0.0;
1898 : }
1899 : // set inlet node flowrates
1900 14 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = thisPIU.PriAirMassFlow;
1901 14 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = thisPIU.SecAirMassFlow;
1902 14 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRateMaxAvail = thisPIU.SecAirMassFlow;
1903 14 : if (PriAirMassFlowMax == 0) {
1904 5 : thisPIU.PriDamperPosition = 0;
1905 : } else {
1906 9 : thisPIU.PriDamperPosition = thisPIU.PriAirMassFlow / PriAirMassFlowMax;
1907 : }
1908 :
1909 : // now that inlet airflows have been set, the terminal box components can be simulated.
1910 : // fire the fan
1911 14 : if (thisPIU.fanType == HVAC::FanType::SystemModel) {
1912 6 : if (thisPIU.fanControlType == FanCntrlType::VariableSpeedFan) {
1913 : // calculate fan speed ratio
1914 6 : Real64 fanFlowRatio(1.0);
1915 6 : if (thisPIU.MaxSecAirMassFlow > 0.0) {
1916 6 : fanFlowRatio = thisPIU.SecAirMassFlow / thisPIU.MaxSecAirMassFlow;
1917 : }
1918 6 : state.dataFans->fans(thisPIU.Fan_Index)->simulate(state, FirstHVACIteration, fanFlowRatio, _);
1919 : } else {
1920 0 : state.dataFans->fans(thisPIU.Fan_Index)->simulate(state, FirstHVACIteration, _, _);
1921 : }
1922 8 : } else if (thisPIU.fanType == HVAC::FanType::Constant) {
1923 8 : state.dataFans->fans(thisPIU.Fan_Index)->simulate(state, FirstHVACIteration, _, _);
1924 : }
1925 :
1926 : // fire the mixer
1927 14 : SimAirMixer(state, thisPIU.MixerName, thisPIU.Mixer_Num);
1928 :
1929 : // the heating load seen by the reheat coil [W]
1930 14 : Real64 QActualHeating = QToHeatSetPt - state.dataLoopNodes->Node(thisPIU.HCoilInAirNode).MassFlowRate * CpAirZn *
1931 14 : (state.dataLoopNodes->Node(thisPIU.HCoilInAirNode).Temp - state.dataLoopNodes->Node(ZoneNode).Temp);
1932 :
1933 : // check if heating coil is off
1934 14 : if (((!UnitOn) || (QActualHeating < SmallLoad) || (state.dataHeatBalFanSys->TempControlType(ZoneNum) == HVAC::SetptType::SingleCool) ||
1935 28 : (thisPIU.PriAirMassFlow > PriAirMassFlowMin)) &&
1936 9 : (thisPIU.heatingOperatingMode != HeatOpModeType::StagedHeatFirstStage)) { // reheat is off during the first stage of heating
1937 8 : thisPIU.heatingOperatingMode = HeatOpModeType::HeaterOff;
1938 : }
1939 :
1940 : // determine what is required of heater for current operating stage
1941 14 : if (thisPIU.heatingOperatingMode == HeatOpModeType::HeaterOff) {
1942 8 : QCoilReq = 0.0;
1943 6 : } else if (thisPIU.heatingOperatingMode == HeatOpModeType::StagedHeatFirstStage) {
1944 1 : QCoilReq = 0.0;
1945 5 : } else if (thisPIU.heatingOperatingMode == HeatOpModeType::ConstantVolumeHeat) {
1946 0 : QCoilReq = QActualHeating;
1947 5 : } else if (thisPIU.heatingOperatingMode == HeatOpModeType::StagedHeatSecondStage) {
1948 1 : QCoilReq = QActualHeating;
1949 4 : } else if (thisPIU.heatingOperatingMode == HeatOpModeType::ModulatedHeatFirstStage) {
1950 1 : QCoilReq = QActualHeating;
1951 3 : } else if (thisPIU.heatingOperatingMode == HeatOpModeType::ModulatedHeatSecondStage) {
1952 : // find heater power to deliver design discharge air temperature
1953 1 : Real64 targetDATEnthalpy = Psychrometrics::PsyHFnTdbW(thisPIU.designHeatingDAT, state.dataLoopNodes->Node(ZoneNode).HumRat);
1954 : Real64 mixAirEnthalpy =
1955 1 : Psychrometrics::PsyHFnTdbW(state.dataLoopNodes->Node(thisPIU.HCoilInAirNode).Temp, state.dataLoopNodes->Node(ZoneNode).HumRat);
1956 1 : QCoilReq = state.dataLoopNodes->Node(thisPIU.HCoilInAirNode).MassFlowRate * (targetDATEnthalpy - mixAirEnthalpy);
1957 2 : } else if (thisPIU.heatingOperatingMode == HeatOpModeType::ModulatedHeatThirdStage) {
1958 : // find heater power to deliver maximum discharge air temperature
1959 2 : Real64 HiLimitDATEnthalpy = Psychrometrics::PsyHFnTdbW(thisPIU.highLimitDAT, state.dataLoopNodes->Node(ZoneNode).HumRat);
1960 : Real64 mixAirEnthalpy =
1961 2 : Psychrometrics::PsyHFnTdbW(state.dataLoopNodes->Node(thisPIU.HCoilInAirNode).Temp, state.dataLoopNodes->Node(ZoneNode).HumRat);
1962 2 : Real64 QcoilLimit = state.dataLoopNodes->Node(thisPIU.HCoilInAirNode).MassFlowRate * (HiLimitDATEnthalpy - mixAirEnthalpy);
1963 2 : if (QcoilLimit < QActualHeating) { // if requried power is too high use limit of coil discharge
1964 1 : QCoilReq = QcoilLimit;
1965 : } else {
1966 1 : QCoilReq = QActualHeating;
1967 : }
1968 : } else {
1969 0 : ShowSevereError(state, "Incorrect parallel PIU heating operation.");
1970 0 : ShowFatalError(state, format("Parallel PIU control failed for {}:{}", thisPIU.UnitType, thisPIU.Name));
1971 : }
1972 14 : if ((QCoilReq < SmallLoad) &&
1973 9 : (thisPIU.heatingOperatingMode != HeatOpModeType::StagedHeatFirstStage)) { // reheat is off during the first stage of heating
1974 8 : thisPIU.heatingOperatingMode = HeatOpModeType::HeaterOff;
1975 8 : QCoilReq = 0.0;
1976 : }
1977 :
1978 : // fire the heating coil
1979 14 : switch (thisPIU.HCoilType) {
1980 0 : case HtgCoilType::SimpleHeating: { // COIL:WATER:SIMPLEHEATING
1981 0 : if ((thisPIU.heatingOperatingMode == HeatOpModeType::HeaterOff) || (thisPIU.heatingOperatingMode == HeatOpModeType::StagedHeatFirstStage)) {
1982 : // call the reheat coil with the NO FLOW condition
1983 0 : Real64 mdot = 0.0;
1984 0 : SetComponentFlowRate(state, mdot, thisPIU.HotControlNode, thisPIU.HotCoilOutNodeNum, thisPIU.HWplantLoc);
1985 0 : SimulateWaterCoilComponents(state, thisPIU.HCoil, FirstHVACIteration, thisPIU.HCoil_Index);
1986 0 : } else {
1987 : // control water flow to obtain output matching QZnReq
1988 0 : ControlCompOutput(state,
1989 0 : thisPIU.HCoil,
1990 0 : thisPIU.UnitType,
1991 0 : thisPIU.HCoil_Index,
1992 : FirstHVACIteration,
1993 : QCoilReq,
1994 : thisPIU.HotControlNode,
1995 : MaxWaterFlow,
1996 : MinWaterFlow,
1997 : thisPIU.HotControlOffset,
1998 0 : thisPIU.ControlCompTypeNum,
1999 0 : thisPIU.CompErrIndex,
2000 0 : thisPIU.HCoilInAirNode,
2001 0 : thisPIU.OutAirNode,
2002 : _,
2003 : _,
2004 : _,
2005 0 : thisPIU.HWplantLoc);
2006 : }
2007 0 : break;
2008 : }
2009 0 : case HtgCoilType::SteamAirHeating: { // COIL:STEAM:AIRHEATING
2010 0 : SimulateSteamCoilComponents(state, thisPIU.HCoil, FirstHVACIteration, thisPIU.HCoil_Index, QCoilReq);
2011 0 : break;
2012 : }
2013 14 : case HtgCoilType::Electric: { // COIL:ELECTRIC:HEATING
2014 14 : SimulateHeatingCoilComponents(state, thisPIU.HCoil, FirstHVACIteration, QCoilReq, thisPIU.HCoil_Index);
2015 14 : break;
2016 : }
2017 0 : case HtgCoilType::Gas: { // COIL:GAS:HEATING
2018 0 : SimulateHeatingCoilComponents(state, thisPIU.HCoil, FirstHVACIteration, QCoilReq, thisPIU.HCoil_Index);
2019 0 : break;
2020 : }
2021 0 : default:
2022 0 : break;
2023 : }
2024 : // Power supplied
2025 14 : Real64 PowerMet = state.dataLoopNodes->Node(thisPIU.OutAirNode).MassFlowRate *
2026 14 : (PsyHFnTdbW(state.dataLoopNodes->Node(thisPIU.OutAirNode).Temp, state.dataLoopNodes->Node(ZoneNode).HumRat) -
2027 14 : PsyHFnTdbW(state.dataLoopNodes->Node(ZoneNode).Temp, state.dataLoopNodes->Node(ZoneNode).HumRat));
2028 14 : thisPIU.HeatingRate = max(0.0, PowerMet);
2029 14 : thisPIU.SensCoolRate = std::abs(min(DataPrecisionGlobals::constant_zero, PowerMet));
2030 14 : thisPIU.TotMassFlowRate = state.dataLoopNodes->Node(thisPIU.OutAirNode).MassFlowRate;
2031 14 : thisPIU.SecMassFlowRate = state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate;
2032 14 : thisPIU.PriMassFlowRate = state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate;
2033 14 : thisPIU.DischargeAirTemp = state.dataLoopNodes->Node(thisPIU.OutAirNode).Temp;
2034 14 : if (state.dataLoopNodes->Node(thisPIU.OutAirNode).MassFlowRate == 0.0) {
2035 3 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = 0.0;
2036 3 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = 0.0;
2037 : }
2038 14 : if (thisPIU.InducesPlenumAir) {
2039 0 : state.dataHVACGlobal->PlenumInducedMassFlow = state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate;
2040 : } else {
2041 14 : state.dataHVACGlobal->PlenumInducedMassFlow = 0.0;
2042 : }
2043 14 : state.dataDefineEquipment->AirDistUnit(thisPIU.ADUNum).MassFlowRatePlenInd = state.dataHVACGlobal->PlenumInducedMassFlow;
2044 14 : state.dataLoopNodes->Node(thisPIU.OutAirNode).MassFlowRateMax = thisPIU.MaxPriAirMassFlow;
2045 :
2046 14 : ReportCurOperatingControlStage(state, PIUNum, UnitOn, thisPIU.heatingOperatingMode, thisPIU.coolingOperatingMode);
2047 14 : }
2048 :
2049 15946 : void ReportCurOperatingControlStage(EnergyPlusData &state, int const piuNum, bool const unitOn, HeatOpModeType heaterMode, CoolOpModeType coolingMode)
2050 : {
2051 15946 : int constexpr undetermined = -1;
2052 15946 : int constexpr off = 0;
2053 15946 : int constexpr constantVolumeCooling = 1;
2054 15946 : int constexpr constantVolumeHeating = 2;
2055 15946 : int constexpr deadband = 3;
2056 15946 : int constexpr variableSpeedFirstStageCooling = 4;
2057 15946 : int constexpr variableSpeedSecondStageCooling = 5;
2058 15946 : int constexpr variableSpeedStagedHeatFirstStageHeating = 6;
2059 15946 : int constexpr variableSpeedStagedHeatSecondStageHeating = 7;
2060 15946 : int constexpr variableSpeedModulatedHeatFirstStageHeating = 8;
2061 15946 : int constexpr variableSpeedModulatedHeatSecondStageHeating = 9;
2062 15946 : int constexpr variableSpeedModulatedHeatThirdStageHeating = 10;
2063 :
2064 15946 : state.dataPowerInductionUnits->PIU(piuNum).CurOperationControlStage = undetermined;
2065 :
2066 15946 : if (!unitOn) {
2067 1 : state.dataPowerInductionUnits->PIU(piuNum).CurOperationControlStage = off;
2068 : } else {
2069 15945 : if (state.dataPowerInductionUnits->PIU(piuNum).fanControlType == FanCntrlType::ConstantSpeedFan) {
2070 15931 : if (heaterMode != HeatOpModeType::HeaterOff && coolingMode == CoolOpModeType::CoolerOff) {
2071 6802 : state.dataPowerInductionUnits->PIU(piuNum).CurOperationControlStage = constantVolumeHeating;
2072 9129 : } else if (coolingMode != CoolOpModeType::CoolerOff && heaterMode == HeatOpModeType::HeaterOff) {
2073 7392 : state.dataPowerInductionUnits->PIU(piuNum).CurOperationControlStage = constantVolumeCooling;
2074 : } else {
2075 1737 : state.dataPowerInductionUnits->PIU(piuNum).CurOperationControlStage = deadband;
2076 : }
2077 : }
2078 15945 : if (state.dataPowerInductionUnits->PIU(piuNum).fanControlType == FanCntrlType::VariableSpeedFan) {
2079 14 : if (heaterMode != HeatOpModeType::HeaterOff) {
2080 12 : if (state.dataPowerInductionUnits->PIU(piuNum).heatingControlType == HeatCntrlBehaviorType::StagedHeaterBehavior) {
2081 4 : if (heaterMode == HeatOpModeType::StagedHeatFirstStage) {
2082 2 : state.dataPowerInductionUnits->PIU(piuNum).CurOperationControlStage = variableSpeedStagedHeatFirstStageHeating;
2083 2 : } else if (heaterMode == HeatOpModeType::StagedHeatSecondStage) {
2084 2 : state.dataPowerInductionUnits->PIU(piuNum).CurOperationControlStage = variableSpeedStagedHeatSecondStageHeating;
2085 : }
2086 8 : } else if (state.dataPowerInductionUnits->PIU(piuNum).heatingControlType == HeatCntrlBehaviorType::ModulatedHeaterBehavior) {
2087 8 : if (heaterMode == HeatOpModeType::ModulatedHeatFirstStage) {
2088 2 : state.dataPowerInductionUnits->PIU(piuNum).CurOperationControlStage = variableSpeedModulatedHeatFirstStageHeating;
2089 6 : } else if (heaterMode == HeatOpModeType::ModulatedHeatSecondStage) {
2090 2 : state.dataPowerInductionUnits->PIU(piuNum).CurOperationControlStage = variableSpeedModulatedHeatSecondStageHeating;
2091 4 : } else if (heaterMode == HeatOpModeType::ModulatedHeatThirdStage) {
2092 4 : state.dataPowerInductionUnits->PIU(piuNum).CurOperationControlStage = variableSpeedModulatedHeatThirdStageHeating;
2093 : }
2094 : }
2095 2 : } else if (coolingMode == CoolOpModeType::CoolFirstStage) {
2096 1 : state.dataPowerInductionUnits->PIU(piuNum).CurOperationControlStage = variableSpeedFirstStageCooling;
2097 1 : } else if (coolingMode == CoolOpModeType::CoolSecondStage) {
2098 1 : state.dataPowerInductionUnits->PIU(piuNum).CurOperationControlStage = variableSpeedSecondStageCooling;
2099 0 : } else if (heaterMode == HeatOpModeType::HeaterOff && coolingMode == CoolOpModeType::CoolerOff) {
2100 0 : state.dataPowerInductionUnits->PIU(piuNum).CurOperationControlStage = deadband;
2101 : }
2102 : }
2103 : }
2104 15946 : }
2105 :
2106 2 : void CalcVariableSpeedPIUCoolingBehavior(EnergyPlusData &state,
2107 : int const piuNum, // number of the current PIU being simulated
2108 : int const zoneNode, // zone node number
2109 : Real64 const zoneLoad,
2110 : Real64 const loadToHeatSetPt,
2111 : Real64 const priAirMassFlowMin,
2112 : [[maybe_unused]] Real64 const priAirMassFlowMax)
2113 : {
2114 2 : auto &thisPIU = state.dataPowerInductionUnits->PIU(piuNum);
2115 2 : thisPIU.coolingOperatingMode = CoolOpModeType::CoolerOff;
2116 :
2117 : // set min primary flow and low secondary
2118 2 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = priAirMassFlowMin;
2119 2 : Real64 TotAirMassFlow = thisPIU.MinTotAirMassFlow;
2120 2 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = max(0.0, TotAirMassFlow - priAirMassFlowMin);
2121 :
2122 : // calculate cooling provided to zone at minimum fan speed and minimum primary air mass flow
2123 2 : Real64 qdotDelivMinPrim = CalcVariableSpeedPIUQdotDelivered(state, piuNum, zoneNode, false, TotAirMassFlow, thisPIU.MinFanTurnDownRatio);
2124 :
2125 2 : if (qdotDelivMinPrim <= zoneLoad) { // will provide more cooling than required at minimum primary flow
2126 0 : thisPIU.PriAirMassFlow = priAirMassFlowMin;
2127 0 : if (qdotDelivMinPrim >=
2128 : loadToHeatSetPt) { // will provide more cooling than required but not enough to drop below the heating thermostat setpoint
2129 0 : thisPIU.SecAirMassFlow = max(0.0, thisPIU.MinTotAirMassFlow - thisPIU.PriAirMassFlow);
2130 0 : thisPIU.heatingOperatingMode = HeatOpModeType::HeaterOff;
2131 : } else {
2132 0 : if (thisPIU.heatingControlType == HeatCntrlBehaviorType::StagedHeaterBehavior) {
2133 0 : CalcVariableSpeedPIUStagedHeatingBehavior(state, piuNum, zoneNode, loadToHeatSetPt, true, thisPIU.PriAirMassFlow);
2134 0 : } else if (thisPIU.heatingControlType == HeatCntrlBehaviorType::ModulatedHeaterBehavior) {
2135 0 : CalcVariableSpeedPIUModulatedHeatingBehavior(state, piuNum, zoneNode, loadToHeatSetPt, true, thisPIU.PriAirMassFlow);
2136 : }
2137 : }
2138 : } else {
2139 : // check how much cooling provided at max fan and primary air
2140 2 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = thisPIU.MaxPriAirMassFlow;
2141 2 : TotAirMassFlow = thisPIU.MaxTotAirMassFlow;
2142 2 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = max(0.0, TotAirMassFlow - thisPIU.MaxPriAirMassFlow);
2143 2 : Real64 qdotDelivMaxFan = CalcVariableSpeedPIUQdotDelivered(state, piuNum, zoneNode, false, TotAirMassFlow, 1.0);
2144 :
2145 2 : if (zoneLoad <= qdotDelivMaxFan) { // not going to make it just run at max
2146 1 : thisPIU.PriAirMassFlow = thisPIU.PriAirMassFlow;
2147 1 : TotAirMassFlow = thisPIU.MaxTotAirMassFlow;
2148 1 : thisPIU.SecAirMassFlow = max(0.0, TotAirMassFlow - thisPIU.PriAirMassFlow);
2149 1 : thisPIU.heatingOperatingMode = HeatOpModeType::HeaterOff;
2150 1 : thisPIU.coolingOperatingMode = CoolOpModeType::CoolSecondStage;
2151 : } else {
2152 : // call regula falsi solver, vary a coooling control signal for fan speed and primary air flow together from min to max.
2153 1 : int constexpr MaxIte(500); // Maximum number of iterations
2154 1 : Real64 constexpr Acc(0.0001); // Accuracy of result
2155 1 : int SolFla(0); // Flag of solver
2156 1 : Real64 coolSignal = 0.5; // starting value
2157 7 : auto f = [&state, piuNum, zoneLoad, zoneNode](Real64 const coolSignal) {
2158 7 : return CalcVariableSpeedPIUCoolingResidual(state, coolSignal, piuNum, zoneLoad, zoneNode);
2159 1 : };
2160 :
2161 1 : General::SolveRoot(state, Acc, MaxIte, SolFla, coolSignal, f, 0.0, 1.0);
2162 :
2163 1 : if (SolFla == -1) {
2164 0 : ShowSevereError(state, "Iteration limit exceeded in calculating variable speed fan powered box cooling signal");
2165 0 : ShowContinueErrorTimeStamp(state, "");
2166 0 : ShowFatalError(state, format("Series PIU control failed for {}:{} ", thisPIU.UnitType, thisPIU.Name));
2167 1 : } else if (SolFla == -2) {
2168 0 : ShowSevereError(state, "Bad starting values for in calculating variable speed fan powered box cooling signal");
2169 0 : ShowContinueError(state, format("Zone Load to Cooling Setpoint = {:.2R} [W]", zoneLoad));
2170 0 : ShowContinueError(state, format("Load Delivered to Zone at Minimum Fan Speed = {:.2R} [W]", qdotDelivMinPrim));
2171 0 : ShowContinueErrorTimeStamp(state, "");
2172 0 : ShowFatalError(state, format("Series PIU control failed for {}:{}", thisPIU.UnitType, thisPIU.Name));
2173 : } else {
2174 1 : thisPIU.PriAirMassFlow = coolSignal * (thisPIU.MaxPriAirMassFlow - thisPIU.MinPriAirMassFlow) + thisPIU.MinPriAirMassFlow;
2175 1 : TotAirMassFlow = coolSignal * (thisPIU.MaxTotAirMassFlow - thisPIU.MinTotAirMassFlow) + thisPIU.MinTotAirMassFlow;
2176 1 : thisPIU.SecAirMassFlow = max(0.0, TotAirMassFlow - thisPIU.PriAirMassFlow);
2177 1 : thisPIU.heatingOperatingMode = HeatOpModeType::HeaterOff;
2178 1 : thisPIU.coolingOperatingMode = CoolOpModeType::CoolFirstStage;
2179 : }
2180 : }
2181 : }
2182 2 : }
2183 :
2184 4 : void CalcVariableSpeedPIUStagedHeatingBehavior(EnergyPlusData &state,
2185 : int const piuNum, // number of the current PIU being simulated
2186 : int const zoneNode, // zone node number
2187 : Real64 const zoneLoad,
2188 : bool const pri,
2189 : Real64 const primaryAirMassFlow)
2190 : {
2191 4 : auto &thisPIU = state.dataPowerInductionUnits->PIU(piuNum);
2192 :
2193 : // Calculate heating provided to zone with no coil at the maximum secondary flow rate: "1st stage, max fan"
2194 4 : if (pri) {
2195 4 : if (thisPIU.UnitType == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
2196 2 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = thisPIU.MinPriAirMassFlow;
2197 2 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = max(0.0, thisPIU.MaxTotAirMassFlow - thisPIU.MinPriAirMassFlow);
2198 2 : } else if (thisPIU.UnitType == "AirTerminal:SingleDuct:ParallelPIU:Reheat") {
2199 2 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = thisPIU.MinPriAirMassFlow;
2200 2 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = thisPIU.MaxSecAirMassFlow;
2201 : }
2202 : } else {
2203 0 : if (thisPIU.UnitType == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
2204 0 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = 0.0;
2205 0 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = thisPIU.MaxTotAirMassFlow;
2206 0 : } else if (thisPIU.UnitType == "AirTerminal:SingleDuct:ParallelPIU:Reheat") {
2207 0 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = 0.0;
2208 0 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = thisPIU.MaxSecAirMassFlow;
2209 : }
2210 : }
2211 : Real64 TotAirMassFlow =
2212 4 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate + state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate;
2213 4 : Real64 qdotDelivered1stStageMaxFan = CalcVariableSpeedPIUQdotDelivered(state, piuNum, zoneNode, false, TotAirMassFlow, 1.0);
2214 :
2215 : // Calculate heating provided to zone with no coil at the minimum secondary flow rate: "1st stage, min fan"
2216 4 : if (pri) {
2217 4 : if (thisPIU.UnitType == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
2218 2 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = thisPIU.MinPriAirMassFlow;
2219 2 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = max(0.0, thisPIU.MinTotAirMassFlow - thisPIU.MinPriAirMassFlow);
2220 2 : } else if (thisPIU.UnitType == "AirTerminal:SingleDuct:ParallelPIU:Reheat") {
2221 2 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = thisPIU.MinPriAirMassFlow;
2222 2 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = thisPIU.MinSecAirMassFlow;
2223 : }
2224 : } else {
2225 0 : if (thisPIU.UnitType == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
2226 0 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = 0.0;
2227 0 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = thisPIU.MinTotAirMassFlow;
2228 0 : } else if (thisPIU.UnitType == "AirTerminal:SingleDuct:ParallelPIU:Reheat") {
2229 0 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = 0.0;
2230 0 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = thisPIU.MinSecAirMassFlow;
2231 : }
2232 : }
2233 4 : TotAirMassFlow = state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate + state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate;
2234 : Real64 qdotDelivered1stStageMinFan =
2235 4 : CalcVariableSpeedPIUQdotDelivered(state, piuNum, zoneNode, false, TotAirMassFlow, thisPIU.MinFanTurnDownRatio);
2236 :
2237 4 : if (qdotDelivered1stStageMinFan <= zoneLoad && qdotDelivered1stStageMaxFan >= zoneLoad) { // 1st of heating (no coil) can meet the load
2238 : // Find fan speed/flow that meets the load through iteration
2239 2 : thisPIU.heatingOperatingMode = HeatOpModeType::StagedHeatFirstStage;
2240 2 : int constexpr MaxIte(500); // Maximum number of iterations
2241 2 : Real64 constexpr Acc(0.001); // Accuracy of result
2242 2 : int SolFla(0); // Flag of solver
2243 2 : Real64 fanSignal = 0.0;
2244 2 : fanSignal = (1.0 - thisPIU.MinFanTurnDownRatio) * 0.5 + thisPIU.MinFanTurnDownRatio; // average speed as the initial value
2245 10 : auto f = [&state, piuNum, zoneLoad, zoneNode, primaryAirMassFlow](Real64 const fanSignal) {
2246 10 : return CalcVariableSpeedPIUHeatingResidual(state, fanSignal, piuNum, zoneLoad, zoneNode, primaryAirMassFlow, false, fanSignal);
2247 2 : };
2248 :
2249 2 : General::SolveRoot(state, Acc, MaxIte, SolFla, fanSignal, f, thisPIU.MinFanTurnDownRatio, 1.0);
2250 :
2251 2 : if (SolFla == -1) {
2252 0 : ShowSevereError(state, "Iteration limit exceeded in calculating variable speed fan powered box 1st stage heating fan speed");
2253 0 : ShowContinueErrorTimeStamp(state, "");
2254 0 : ShowFatalError(state, format("PIU control failed for {}:{} ", thisPIU.UnitType, thisPIU.Name));
2255 2 : } else if (SolFla == -2) {
2256 0 : ShowSevereError(state, "Bad starting values in calculating variable speed fan powered box 1st stage heating fan speed");
2257 0 : ShowContinueErrorTimeStamp(state, "");
2258 0 : ShowFatalError(state, format("PIU control failed for {}:{}", thisPIU.UnitType, thisPIU.Name));
2259 : } else {
2260 2 : if (thisPIU.UnitType == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
2261 1 : thisPIU.SecAirMassFlow = max(0.0, fanSignal * thisPIU.MaxTotAirMassFlow - primaryAirMassFlow);
2262 1 : } else if (thisPIU.UnitType == "AirTerminal:SingleDuct:ParallelPIU:Reheat") {
2263 1 : thisPIU.SecAirMassFlow = max(0.0, fanSignal * thisPIU.MaxSecAirMassFlow);
2264 : }
2265 : }
2266 4 : } else if (qdotDelivered1stStageMaxFan < zoneLoad) {
2267 2 : thisPIU.heatingOperatingMode = HeatOpModeType::StagedHeatSecondStage;
2268 2 : if (thisPIU.UnitType == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
2269 1 : thisPIU.SecAirMassFlow = max(0.0, thisPIU.MaxTotAirMassFlow - primaryAirMassFlow);
2270 1 : } else if (thisPIU.UnitType == "AirTerminal:SingleDuct:ParallelPIU:Reheat") {
2271 1 : thisPIU.SecAirMassFlow = thisPIU.MaxSecAirMassFlow;
2272 : }
2273 0 : } else if (qdotDelivered1stStageMinFan > zoneLoad) {
2274 0 : thisPIU.heatingOperatingMode = HeatOpModeType::HeaterOff;
2275 0 : if (thisPIU.UnitType == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
2276 0 : thisPIU.SecAirMassFlow = max(0.0, thisPIU.MinTotAirMassFlow - thisPIU.MinPriAirMassFlow);
2277 0 : } else if (thisPIU.UnitType == "AirTerminal:SingleDuct:ParallelPIU:Reheat") {
2278 0 : thisPIU.SecAirMassFlow = 0.0;
2279 : }
2280 : }
2281 4 : }
2282 :
2283 49 : Real64 CalcVariableSpeedPIUQdotDelivered(EnergyPlusData &state,
2284 : int const piuNum, // number of the current PIU being simulated
2285 : int const zoneNode, // zone node number
2286 : bool const useDAT,
2287 : Real64 const totAirMassFlow,
2288 : Real64 const fanTurnDown)
2289 : {
2290 49 : Real64 qdotDelivered = 0.0;
2291 49 : auto &thisPIU = state.dataPowerInductionUnits->PIU(piuNum);
2292 49 : if (thisPIU.UnitType == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
2293 30 : MixerComponent::SimAirMixer(state, thisPIU.MixerName, thisPIU.Mixer_Num);
2294 30 : state.dataFans->fans(thisPIU.Fan_Index)->simulate(state, false, fanTurnDown, _);
2295 : } else {
2296 19 : state.dataFans->fans(thisPIU.Fan_Index)->simulate(state, false, fanTurnDown, _);
2297 19 : MixerComponent::SimAirMixer(state, thisPIU.MixerName, thisPIU.Mixer_Num);
2298 : }
2299 49 : Real64 zoneEnthalpy = Psychrometrics::PsyHFnTdbW(state.dataLoopNodes->Node(zoneNode).Temp, state.dataLoopNodes->Node(zoneNode).HumRat);
2300 49 : Real64 piuTemp = 0.0;
2301 49 : if (useDAT) {
2302 20 : piuTemp = thisPIU.designHeatingDAT;
2303 : } else {
2304 29 : piuTemp = state.dataLoopNodes->Node(thisPIU.HCoilInAirNode).Temp;
2305 : }
2306 49 : Real64 piuEnthalpy = Psychrometrics::PsyHFnTdbW(piuTemp, state.dataLoopNodes->Node(zoneNode).HumRat);
2307 49 : qdotDelivered = totAirMassFlow * (piuEnthalpy - zoneEnthalpy);
2308 49 : return qdotDelivered;
2309 : }
2310 :
2311 8 : void CalcVariableSpeedPIUModulatedHeatingBehavior(EnergyPlusData &state,
2312 : int const piuNum, // number of the current PIU being simulated
2313 : int const zoneNode, // zone node number
2314 : Real64 const zoneLoad,
2315 : bool const pri,
2316 : Real64 const primaryAirMassFlow)
2317 : {
2318 8 : auto &thisPIU = state.dataPowerInductionUnits->PIU(piuNum);
2319 :
2320 : // Calculate heating provided to zone with no coil at the minimum secondary flow rate: "1st stage, min fan"
2321 8 : if (pri) {
2322 8 : if (thisPIU.UnitType == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
2323 4 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = thisPIU.MinPriAirMassFlow;
2324 4 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = max(0.0, thisPIU.MinTotAirMassFlow - thisPIU.MinPriAirMassFlow);
2325 4 : } else if (thisPIU.UnitType == "AirTerminal:SingleDuct:ParallelPIU:Reheat") {
2326 4 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = thisPIU.MinPriAirMassFlow;
2327 4 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = thisPIU.MinSecAirMassFlow;
2328 : }
2329 : } else {
2330 0 : if (thisPIU.UnitType == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
2331 0 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = 0.0;
2332 0 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = thisPIU.MinTotAirMassFlow;
2333 0 : } else if (thisPIU.UnitType == "AirTerminal:SingleDuct:ParallelPIU:Reheat") {
2334 0 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = 0.0;
2335 0 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = thisPIU.MinSecAirMassFlow;
2336 : }
2337 : }
2338 : Real64 TotAirMassFlow =
2339 8 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate + state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate;
2340 8 : Real64 qdotDeliveredEnd1stStage = CalcVariableSpeedPIUQdotDelivered(state, piuNum, zoneNode, true, TotAirMassFlow, thisPIU.MinFanTurnDownRatio);
2341 8 : if (qdotDeliveredEnd1stStage >= zoneLoad) { // 1st stage, find heating power at minimum fan speed
2342 2 : thisPIU.heatingOperatingMode = HeatOpModeType::ModulatedHeatFirstStage;
2343 2 : if (thisPIU.UnitType == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
2344 1 : thisPIU.SecAirMassFlow = thisPIU.MinSecAirMassFlow;
2345 1 : } else if (thisPIU.UnitType == "AirTerminal:SingleDuct:ParallelPIU:Reheat") {
2346 1 : thisPIU.SecAirMassFlow = thisPIU.MinSecAirMassFlow;
2347 : }
2348 : } else {
2349 6 : if (pri) {
2350 6 : if (thisPIU.UnitType == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
2351 3 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = thisPIU.MinPriAirMassFlow;
2352 3 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = max(0.0, thisPIU.MaxTotAirMassFlow - thisPIU.MinPriAirMassFlow);
2353 3 : } else if (thisPIU.UnitType == "AirTerminal:SingleDuct:ParallelPIU:Reheat") {
2354 3 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = thisPIU.MinPriAirMassFlow;
2355 3 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = thisPIU.MaxSecAirMassFlow;
2356 : }
2357 : } else {
2358 0 : if (thisPIU.UnitType == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
2359 0 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = 0.0;
2360 0 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = thisPIU.MaxTotAirMassFlow;
2361 0 : } else if (thisPIU.UnitType == "AirTerminal:SingleDuct:ParallelPIU:Reheat") {
2362 0 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = 0.0;
2363 0 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = thisPIU.MaxSecAirMassFlow;
2364 : }
2365 : }
2366 6 : TotAirMassFlow = state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate + state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate;
2367 : Real64 qdotDeliveredEnd2ndStage =
2368 6 : CalcVariableSpeedPIUQdotDelivered(state, piuNum, zoneNode, true, TotAirMassFlow, thisPIU.MinFanTurnDownRatio);
2369 6 : if (qdotDeliveredEnd2ndStage > zoneLoad) { // 2nd stage
2370 2 : thisPIU.heatingOperatingMode = HeatOpModeType::ModulatedHeatSecondStage;
2371 : // Find fan speed that meets zone heating load
2372 2 : int constexpr MaxIte(500); // Maximum number of iterations
2373 2 : Real64 constexpr Acc(0.0001); // Accuracy of result
2374 2 : int SolFla(0); // Flag of solver
2375 2 : Real64 fanSignal = (1.0 - thisPIU.MinFanTurnDownRatio) * 0.5 + thisPIU.MinFanTurnDownRatio; // starting value in middle
2376 6 : auto f = [&state, piuNum, zoneLoad, zoneNode, primaryAirMassFlow](Real64 const fanSignal) {
2377 6 : return CalcVariableSpeedPIUHeatingResidual(state, fanSignal, piuNum, zoneLoad, zoneNode, primaryAirMassFlow, true, fanSignal);
2378 2 : };
2379 :
2380 2 : General::SolveRoot(state, Acc, MaxIte, SolFla, fanSignal, f, thisPIU.MinFanTurnDownRatio, 1.0);
2381 :
2382 2 : if (SolFla == -1) {
2383 0 : ShowSevereError(state, "Iteration limit exceeded in calculating variable speed fan powered box 2nd stage heating fan speed");
2384 0 : ShowContinueErrorTimeStamp(state, "");
2385 0 : ShowFatalError(state, format("PIU control failed for {}:{}", thisPIU.UnitType, thisPIU.Name));
2386 2 : } else if (SolFla == -2) {
2387 0 : ShowSevereError(state, "Bad starting values for in calculating variable speed fan powered box 2nd stage heating fan speed");
2388 0 : ShowContinueErrorTimeStamp(state, "");
2389 0 : ShowFatalError(state, format("PIU control failed for {}:{}", thisPIU.UnitType, thisPIU.Name));
2390 : } else {
2391 2 : if (thisPIU.UnitType == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
2392 1 : thisPIU.SecAirMassFlow = max(0.0, fanSignal * thisPIU.MaxTotAirMassFlow - primaryAirMassFlow);
2393 1 : } else if (thisPIU.UnitType == "AirTerminal:SingleDuct:ParallelPIU:Reheat") {
2394 1 : thisPIU.SecAirMassFlow = max(0.0, fanSignal * thisPIU.MaxSecAirMassFlow);
2395 : }
2396 : }
2397 : } else { // 3rd stage, full fan speed
2398 4 : thisPIU.heatingOperatingMode = HeatOpModeType::ModulatedHeatThirdStage;
2399 4 : if (thisPIU.UnitType == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
2400 2 : thisPIU.SecAirMassFlow = thisPIU.MaxTotAirMassFlow - thisPIU.MinPriAirMassFlow;
2401 2 : } else if (thisPIU.UnitType == "AirTerminal:SingleDuct:ParallelPIU:Reheat") {
2402 2 : thisPIU.SecAirMassFlow = thisPIU.MaxSecAirMassFlow;
2403 : }
2404 : }
2405 : }
2406 8 : }
2407 :
2408 16 : Real64 CalcVariableSpeedPIUHeatingResidual(EnergyPlusData &state,
2409 : Real64 const fanSignal,
2410 : int const piuNum,
2411 : Real64 const targetQznReq,
2412 : int const zoneNodeNum,
2413 : Real64 const primaryMassFlow,
2414 : bool useDAT,
2415 : Real64 const fanTurnDown)
2416 :
2417 : {
2418 : // used to find a fan speed to meet load to heating setpoint with no heater power
2419 : // 1st stage heating for staged heat, also used for undershoot case where cooling at min primary flow would push below heating
2420 : // setpoint.
2421 16 : auto &thisPIU = state.dataPowerInductionUnits->PIU(piuNum);
2422 16 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = primaryMassFlow;
2423 16 : Real64 TotAirMassFlow = 0.0;
2424 16 : if (thisPIU.UnitType == "AirTerminal:SingleDuct:SeriesPIU:Reheat") {
2425 8 : TotAirMassFlow = fanSignal * thisPIU.MaxTotAirMassFlow;
2426 8 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = max(0.0, TotAirMassFlow - primaryMassFlow);
2427 : } else {
2428 : // parallel
2429 8 : TotAirMassFlow = fanSignal * thisPIU.MaxSecAirMassFlow + primaryMassFlow;
2430 8 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = fanSignal * thisPIU.MaxSecAirMassFlow;
2431 : }
2432 :
2433 : // calculate heating provided to zone
2434 16 : Real64 qdotDelivered = CalcVariableSpeedPIUQdotDelivered(state, piuNum, zoneNodeNum, useDAT, TotAirMassFlow, fanTurnDown);
2435 : // formulate residual and return
2436 16 : Real64 Residuum = (targetQznReq - qdotDelivered);
2437 16 : return Residuum;
2438 : }
2439 :
2440 7 : Real64 CalcVariableSpeedPIUCoolingResidual(EnergyPlusData &state, Real64 const coolSignal, int piuNum, Real64 targetQznReq, int zoneNodeNum)
2441 : {
2442 : // used for cooling control with VS fan. Simultaneous control of fan speed and primary air damper
2443 : // given trial cooling signal, calculate the cooling provided and a residual that compares what is delivered vs what the zone
2444 : // needs. set the flows, controller acts on fan and damper simultaneously
2445 7 : auto &thisPIU = state.dataPowerInductionUnits->PIU(piuNum);
2446 7 : Real64 PriAirMassFlow = coolSignal * (thisPIU.MaxPriAirMassFlow - thisPIU.MinPriAirMassFlow) + thisPIU.MinPriAirMassFlow;
2447 7 : Real64 TotAirMassFlow = coolSignal * (thisPIU.MaxTotAirMassFlow - thisPIU.MinTotAirMassFlow) + thisPIU.MinTotAirMassFlow;
2448 7 : Real64 SecAirMassFlow = max(0.0, TotAirMassFlow - PriAirMassFlow);
2449 7 : state.dataLoopNodes->Node(thisPIU.PriAirInNode).MassFlowRate = PriAirMassFlow;
2450 7 : state.dataLoopNodes->Node(thisPIU.SecAirInNode).MassFlowRate = SecAirMassFlow;
2451 :
2452 7 : Real64 fanTurnDown = coolSignal * (1.0 - thisPIU.MinFanTurnDownRatio) + thisPIU.MinFanTurnDownRatio;
2453 7 : Real64 qdotDelivered = CalcVariableSpeedPIUQdotDelivered(state, piuNum, zoneNodeNum, false, TotAirMassFlow, fanTurnDown);
2454 : // formulate residual and return
2455 7 : Real64 Residuum = (targetQznReq - qdotDelivered);
2456 7 : return Residuum;
2457 : }
2458 :
2459 15916 : void ReportPIU(EnergyPlusData &state, int const PIUNum) // number of the current fan coil unit being simulated
2460 : {
2461 :
2462 : // SUBROUTINE INFORMATION:
2463 : // AUTHOR Fred Buhl
2464 : // DATE WRITTEN August 2000
2465 : // MODIFIED na
2466 : // RE-ENGINEERED na
2467 :
2468 : // PURPOSE OF THIS SUBROUTINE:
2469 : // Fills some report variables for the PIU terminal boxes
2470 :
2471 : // Using/Aliasing
2472 15916 : Real64 TimeStepSysSec = state.dataHVACGlobal->TimeStepSysSec;
2473 :
2474 15916 : auto &thisPIU = state.dataPowerInductionUnits->PIU(PIUNum);
2475 15916 : thisPIU.HeatingEnergy = thisPIU.HeatingRate * TimeStepSysSec;
2476 15916 : thisPIU.SensCoolEnergy = thisPIU.SensCoolRate * TimeStepSysSec;
2477 :
2478 : // set zone OA Volume flow rate
2479 15916 : thisPIU.CalcOutdoorAirVolumeFlowRate(state);
2480 15916 : }
2481 :
2482 : // ===================== Utilities =====================================
2483 :
2484 6 : bool PIUnitHasMixer(EnergyPlusData &state, std::string_view CompName) // component (mixer) name
2485 : {
2486 :
2487 : // FUNCTION INFORMATION:
2488 : // AUTHOR Linda Lawrie
2489 : // DATE WRITTEN September 2011
2490 : // MODIFIED na
2491 : // RE-ENGINEERED na
2492 :
2493 : // PURPOSE OF THIS FUNCTION:
2494 : // Given a mixer name, this routine determines if that mixer is found on
2495 : // PIUnits.
2496 :
2497 : // Return value
2498 6 : bool YesNo = false; // True if found
2499 :
2500 6 : if (state.dataPowerInductionUnits->GetPIUInputFlag) {
2501 0 : GetPIUs(state);
2502 0 : state.dataPowerInductionUnits->GetPIUInputFlag = false;
2503 : }
2504 :
2505 6 : if (state.dataPowerInductionUnits->NumPIUs > 0) {
2506 6 : int const ItemNum = Util::FindItemInList(CompName, state.dataPowerInductionUnits->PIU, &PowIndUnitData::MixerName);
2507 6 : if (ItemNum > 0) {
2508 6 : YesNo = true;
2509 : }
2510 : }
2511 :
2512 6 : return YesNo;
2513 : }
2514 :
2515 6 : void PIUInducesPlenumAir(EnergyPlusData &state, int const NodeNum, int const plenumNum) // induced air node number
2516 : {
2517 :
2518 : // SUBROUTINE INFORMATION:
2519 : // AUTHOR Fred Buhl
2520 : // DATE WRITTEN January 2012
2521 : // MODIFIED na
2522 : // RE-ENGINEERED na
2523 :
2524 : // PURPOSE OF THIS FUNCTION:
2525 : // Marks a PIU air terminal unit as obtaining its induced air from
2526 : // a plenum.
2527 :
2528 6 : if (state.dataPowerInductionUnits->GetPIUInputFlag) {
2529 3 : GetPIUs(state);
2530 3 : state.dataPowerInductionUnits->GetPIUInputFlag = false;
2531 : }
2532 :
2533 6 : for (int PIUIndex = 1; PIUIndex <= state.dataPowerInductionUnits->NumPIUs; ++PIUIndex) {
2534 1 : if (NodeNum == state.dataPowerInductionUnits->PIU(PIUIndex).SecAirInNode) {
2535 1 : state.dataPowerInductionUnits->PIU(PIUIndex).InducesPlenumAir = true;
2536 1 : state.dataPowerInductionUnits->PIU(PIUIndex).plenumIndex = plenumNum;
2537 1 : break;
2538 : }
2539 : }
2540 6 : }
2541 :
2542 15916 : void PowIndUnitData::CalcOutdoorAirVolumeFlowRate(EnergyPlusData &state)
2543 : {
2544 : // calculates zone outdoor air volume flow rate using the supply air flow rate and OA fraction
2545 15916 : if (this->AirLoopNum > 0) {
2546 15909 : this->OutdoorAirFlowRate = (state.dataLoopNodes->Node(this->PriAirInNode).MassFlowRate / state.dataEnvrn->StdRhoAir) *
2547 15909 : state.dataAirLoop->AirLoopFlow(this->AirLoopNum).OAFrac;
2548 : } else {
2549 7 : this->OutdoorAirFlowRate = 0.0;
2550 : }
2551 15916 : }
2552 :
2553 7 : void PowIndUnitData::reportTerminalUnit(EnergyPlusData &state)
2554 : {
2555 : // populate the predefined equipment summary report related to air terminals
2556 7 : auto &orp = state.dataOutRptPredefined;
2557 7 : auto &adu = state.dataDefineEquipment->AirDistUnit(this->ADUNum);
2558 7 : if (!state.dataSize->TermUnitFinalZoneSizing.empty()) {
2559 7 : auto &sizing = state.dataSize->TermUnitFinalZoneSizing(adu.TermUnitSizingNum);
2560 7 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermMinFlow, adu.Name, sizing.DesCoolVolFlowMin);
2561 7 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermMinOutdoorFlow, adu.Name, sizing.MinOA);
2562 7 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermSupCoolingSP, adu.Name, sizing.CoolDesTemp);
2563 7 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermSupHeatingSP, adu.Name, sizing.HeatDesTemp);
2564 7 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermHeatingCap, adu.Name, sizing.DesHeatLoad);
2565 7 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermCoolingCap, adu.Name, sizing.DesCoolLoad);
2566 : }
2567 7 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermTypeInp, adu.Name, this->UnitType);
2568 7 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermPrimFlow, adu.Name, this->MaxPriAirVolFlow);
2569 7 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermSecdFlow, adu.Name, this->MaxSecAirVolFlow);
2570 7 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermMinFlowSch, adu.Name, "n/a");
2571 7 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermMaxFlowReh, adu.Name, "n/a");
2572 7 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermMinOAflowSch, adu.Name, "n/a");
2573 7 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermHeatCoilType, adu.Name, HCoilNamesUC[(int)this->HCoilType]);
2574 7 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermCoolCoilType, adu.Name, "n/a");
2575 7 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermFanType, adu.Name, HVAC::fanTypeNames[(int)this->fanType]);
2576 7 : OutputReportPredefined::PreDefTableEntry(state, orp->pdchAirTermFanName, adu.Name, this->FanName);
2577 7 : }
2578 :
2579 : } // namespace EnergyPlus::PoweredInductionUnits
|