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 :
54 : // EnergyPlus Headers
55 : #include <EnergyPlus/Autosizing/HeatingAirFlowSizing.hh>
56 : #include <EnergyPlus/Autosizing/HeatingCapacitySizing.hh>
57 : #include <EnergyPlus/BranchNodeConnections.hh>
58 : #include <EnergyPlus/Data/EnergyPlusData.hh>
59 : #include <EnergyPlus/DataEnvironment.hh>
60 : #include <EnergyPlus/DataHVACGlobals.hh>
61 : #include <EnergyPlus/DataHeatBalance.hh>
62 : #include <EnergyPlus/DataLoopNode.hh>
63 : #include <EnergyPlus/DataSizing.hh>
64 : #include <EnergyPlus/DataZoneEnergyDemands.hh>
65 : #include <EnergyPlus/DataZoneEquipment.hh>
66 : #include <EnergyPlus/Fans.hh>
67 : #include <EnergyPlus/FluidProperties.hh>
68 : #include <EnergyPlus/General.hh>
69 : #include <EnergyPlus/GeneralRoutines.hh>
70 : #include <EnergyPlus/HeatingCoils.hh>
71 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
72 : #include <EnergyPlus/NodeInputManager.hh>
73 : #include <EnergyPlus/OutputProcessor.hh>
74 : #include <EnergyPlus/PlantUtilities.hh>
75 : #include <EnergyPlus/Psychrometrics.hh>
76 : #include <EnergyPlus/ReportCoilSelection.hh>
77 : #include <EnergyPlus/ScheduleManager.hh>
78 : #include <EnergyPlus/SteamCoils.hh>
79 : #include <EnergyPlus/UnitHeater.hh>
80 : #include <EnergyPlus/UtilityRoutines.hh>
81 : #include <EnergyPlus/WaterCoils.hh>
82 :
83 : namespace EnergyPlus {
84 :
85 : namespace UnitHeater {
86 :
87 : // Module containing the routines dealing with the Unit Heater
88 :
89 : // MODULE INFORMATION:
90 : // AUTHOR Rick Strand
91 : // DATE WRITTEN May 2000
92 : // MODIFIED Brent Griffith, Sept 2010, plant upgrades, fluid properties
93 : // MODIFIED Bereket Nigusse, FSEC, October 2013, Added cycling fan operating mode
94 :
95 : // PURPOSE OF THIS MODULE:
96 : // To simulate unit heaters. It is assumed that unit heaters are zone equipment
97 : // without any connection to outside air other than through a separately defined
98 : // air loop.
99 :
100 : // METHODOLOGY EMPLOYED:
101 : // Units are modeled as a collection of a fan and a heating coil. The fan
102 : // can either be a continuously running fan or an on-off fan which turns on
103 : // only when there is actually a heating load. This fan control works together
104 : // with the unit operation schedule to determine what the unit heater actually
105 : // does at a given point in time.
106 :
107 : // REFERENCES:
108 : // ASHRAE Systems and Equipment Handbook (SI), 1996. pp. 31.3-31.8
109 : // Rick Strand's unit heater module which was based upon Fred Buhl's fan coil
110 : // module (FanCoilUnits.cc)
111 :
112 362 : void SimUnitHeater(EnergyPlusData &state,
113 : std::string_view CompName, // name of the fan coil unit
114 : int const ZoneNum, // number of zone being served
115 : bool const FirstHVACIteration, // TRUE if 1st HVAC simulation of system timestep
116 : Real64 &PowerMet, // Sensible power supplied (W)
117 : Real64 &LatOutputProvided, // Latent add/removal supplied by window AC (kg/s), dehumid = negative
118 : int &CompIndex)
119 : {
120 :
121 : // SUBROUTINE INFORMATION:
122 : // AUTHOR Rick Strand
123 : // DATE WRITTEN May 2000
124 : // MODIFIED Don Shirey, Aug 2009 (LatOutputProvided)
125 :
126 : // PURPOSE OF THIS SUBROUTINE:
127 : // This is the main driver subroutine for the Unit Heater simulation.
128 :
129 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
130 : int UnitHeatNum; // index of unit heater being simulated
131 :
132 362 : if (state.dataUnitHeaters->GetUnitHeaterInputFlag) {
133 3 : GetUnitHeaterInput(state);
134 3 : state.dataUnitHeaters->GetUnitHeaterInputFlag = false;
135 : }
136 :
137 : // Find the correct Unit Heater Equipment
138 362 : if (CompIndex == 0) {
139 2 : UnitHeatNum = Util::FindItemInList(CompName, state.dataUnitHeaters->UnitHeat);
140 2 : if (UnitHeatNum == 0) {
141 0 : ShowFatalError(state, format("SimUnitHeater: Unit not found={}", CompName));
142 : }
143 2 : CompIndex = UnitHeatNum;
144 : } else {
145 360 : UnitHeatNum = CompIndex;
146 360 : if (UnitHeatNum > state.dataUnitHeaters->NumOfUnitHeats || UnitHeatNum < 1) {
147 0 : ShowFatalError(state,
148 0 : format("SimUnitHeater: Invalid CompIndex passed={}, Number of Units={}, Entered Unit name={}",
149 : UnitHeatNum,
150 0 : state.dataUnitHeaters->NumOfUnitHeats,
151 : CompName));
152 : }
153 360 : if (state.dataUnitHeaters->CheckEquipName(UnitHeatNum)) {
154 2 : if (CompName != state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name) {
155 0 : ShowFatalError(state,
156 0 : format("SimUnitHeater: Invalid CompIndex passed={}, Unit name={}, stored Unit Name for that index={}",
157 : UnitHeatNum,
158 : CompName,
159 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name));
160 : }
161 2 : state.dataUnitHeaters->CheckEquipName(UnitHeatNum) = false;
162 : }
163 : }
164 :
165 362 : state.dataSize->ZoneEqUnitHeater = true;
166 :
167 362 : InitUnitHeater(state, UnitHeatNum, ZoneNum, FirstHVACIteration);
168 :
169 362 : state.dataSize->ZoneHeatingOnlyFan = true;
170 :
171 362 : CalcUnitHeater(state, UnitHeatNum, ZoneNum, FirstHVACIteration, PowerMet, LatOutputProvided);
172 :
173 362 : state.dataSize->ZoneHeatingOnlyFan = false;
174 :
175 : // CALL UpdateUnitHeater
176 :
177 362 : ReportUnitHeater(state, UnitHeatNum);
178 :
179 362 : state.dataSize->ZoneEqUnitHeater = false;
180 362 : }
181 :
182 4 : void GetUnitHeaterInput(EnergyPlusData &state)
183 : {
184 :
185 : // SUBROUTINE INFORMATION:
186 : // AUTHOR Rick Strand
187 : // DATE WRITTEN May 2000
188 : // MODIFIED Chandan Sharma, FSEC, March 2011: Added ZoneHVAC sys avail manager
189 : // Bereket Nigusse, FSEC, April 2011: eliminated input node names
190 : // & added fan object type
191 :
192 : // PURPOSE OF THIS SUBROUTINE:
193 : // Obtain the user input data for all of the unit heaters in the input file.
194 :
195 : // METHODOLOGY EMPLOYED:
196 : // Standard EnergyPlus methodology.
197 :
198 : // REFERENCES:
199 : // Fred Buhl's fan coil module (FanCoilUnits.cc)
200 :
201 : static constexpr std::string_view RoutineName("GetUnitHeaterInput: "); // include trailing blank space
202 : static constexpr std::string_view routineName = "GetUnitHeaterInput"; // include trailing blank space
203 :
204 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
205 4 : bool ErrorsFound(false); // Set to true if errors in input, fatal at end of routine
206 : int IOStatus; // Used in GetObjectItem
207 : bool IsNotOK; // TRUE if there was a problem with a list name
208 4 : bool errFlag(false); // interim error flag
209 : int NumAlphas; // Number of Alphas for each GetObjectItem call
210 : int NumNumbers; // Number of Numbers for each GetObjectItem call
211 : int NumFields; // Total number of fields in object
212 :
213 : Real64 FanVolFlow; // Fan volumetric flow rate
214 4 : Array1D_string Alphas; // Alpha items for object
215 4 : Array1D<Real64> Numbers; // Numeric items for object
216 4 : Array1D_string cAlphaFields; // Alpha field names
217 4 : Array1D_string cNumericFields; // Numeric field names
218 4 : Array1D_bool lAlphaBlanks; // Logical array, alpha field input BLANK = .TRUE.
219 4 : Array1D_bool lNumericBlanks; // Logical array, numeric field input BLANK = .TRUE.
220 : int CtrlZone; // index to loop counter
221 : int NodeNum; // index to loop counter
222 :
223 : // Figure out how many unit heaters there are in the input file
224 4 : std::string CurrentModuleObject = state.dataUnitHeaters->cMO_UnitHeater;
225 4 : state.dataUnitHeaters->NumOfUnitHeats = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, CurrentModuleObject);
226 4 : state.dataInputProcessing->inputProcessor->getObjectDefMaxArgs(state, CurrentModuleObject, NumFields, NumAlphas, NumNumbers);
227 :
228 4 : Alphas.allocate(NumAlphas);
229 4 : Numbers.dimension(NumNumbers, 0.0);
230 4 : cAlphaFields.allocate(NumAlphas);
231 4 : cNumericFields.allocate(NumNumbers);
232 4 : lAlphaBlanks.dimension(NumAlphas, true);
233 4 : lNumericBlanks.dimension(NumNumbers, true);
234 :
235 : // Allocate the local derived type and do one-time initializations for all parts of it
236 4 : if (state.dataUnitHeaters->NumOfUnitHeats > 0) {
237 4 : state.dataUnitHeaters->UnitHeat.allocate(state.dataUnitHeaters->NumOfUnitHeats);
238 4 : state.dataUnitHeaters->CheckEquipName.allocate(state.dataUnitHeaters->NumOfUnitHeats);
239 4 : state.dataUnitHeaters->UnitHeatNumericFields.allocate(state.dataUnitHeaters->NumOfUnitHeats);
240 : }
241 4 : state.dataUnitHeaters->CheckEquipName = true;
242 :
243 8 : for (int UnitHeatNum = 1; UnitHeatNum <= state.dataUnitHeaters->NumOfUnitHeats;
244 : ++UnitHeatNum) { // Begin looping over all of the unit heaters found in the input file...
245 :
246 4 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
247 : CurrentModuleObject,
248 : UnitHeatNum,
249 : Alphas,
250 : NumAlphas,
251 : Numbers,
252 : NumNumbers,
253 : IOStatus,
254 : lNumericBlanks,
255 : lAlphaBlanks,
256 : cAlphaFields,
257 : cNumericFields);
258 :
259 4 : ErrorObjectHeader eoh{routineName, CurrentModuleObject, Alphas(1)};
260 :
261 4 : state.dataUnitHeaters->UnitHeatNumericFields(UnitHeatNum).FieldNames.allocate(NumNumbers);
262 4 : state.dataUnitHeaters->UnitHeatNumericFields(UnitHeatNum).FieldNames = "";
263 4 : state.dataUnitHeaters->UnitHeatNumericFields(UnitHeatNum).FieldNames = cNumericFields;
264 4 : Util::IsNameEmpty(state, Alphas(1), CurrentModuleObject, ErrorsFound);
265 :
266 4 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name = Alphas(1);
267 :
268 4 : if (lAlphaBlanks(2)) {
269 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).availSched = Sched::GetScheduleAlwaysOn(state);
270 4 : } else if ((state.dataUnitHeaters->UnitHeat(UnitHeatNum).availSched = Sched::GetSchedule(state, Alphas(2))) == nullptr) {
271 0 : ShowSevereItemNotFound(state, eoh, cAlphaFields(2), Alphas(2));
272 0 : ErrorsFound = true;
273 : }
274 :
275 : // Main air nodes (except outside air node):
276 4 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode =
277 8 : NodeInputManager::GetOnlySingleNode(state,
278 4 : Alphas(3),
279 : ErrorsFound,
280 : DataLoopNode::ConnectionObjectType::ZoneHVACUnitHeater,
281 4 : Alphas(1),
282 : DataLoopNode::NodeFluidType::Air,
283 : DataLoopNode::ConnectionType::Inlet,
284 : NodeInputManager::CompFluidStream::Primary,
285 : DataLoopNode::ObjectIsParent);
286 :
287 4 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirOutNode =
288 8 : NodeInputManager::GetOnlySingleNode(state,
289 4 : Alphas(4),
290 : ErrorsFound,
291 : DataLoopNode::ConnectionObjectType::ZoneHVACUnitHeater,
292 4 : Alphas(1),
293 : DataLoopNode::NodeFluidType::Air,
294 : DataLoopNode::ConnectionType::Outlet,
295 : NodeInputManager::CompFluidStream::Primary,
296 : DataLoopNode::ObjectIsParent);
297 :
298 4 : auto &unitHeat = state.dataUnitHeaters->UnitHeat(UnitHeatNum);
299 : // Fan information:
300 4 : unitHeat.fanType = static_cast<HVAC::FanType>(getEnumValue(HVAC::fanTypeNamesUC, Alphas(5)));
301 4 : if (unitHeat.fanType != HVAC::FanType::Constant && unitHeat.fanType != HVAC::FanType::VAV && unitHeat.fanType != HVAC::FanType::OnOff &&
302 0 : unitHeat.fanType != HVAC::FanType::SystemModel) {
303 0 : ShowSevereInvalidKey(state, eoh, cAlphaFields(5), Alphas(5), "Fan Type must be Fan:ConstantVolume, Fan:VariableVolume, or Fan:OnOff");
304 0 : ErrorsFound = true;
305 : }
306 :
307 4 : unitHeat.FanName = Alphas(6);
308 4 : unitHeat.MaxAirVolFlow = Numbers(1);
309 :
310 4 : if ((unitHeat.Fan_Index = Fans::GetFanIndex(state, unitHeat.FanName)) == 0) {
311 0 : ShowSevereItemNotFound(state, eoh, cAlphaFields(6), unitHeat.FanName);
312 0 : ErrorsFound = true;
313 :
314 : } else {
315 4 : auto *fan = state.dataFans->fans(unitHeat.Fan_Index);
316 :
317 4 : unitHeat.FanOutletNode = fan->outletNodeNum;
318 :
319 4 : FanVolFlow = fan->maxAirFlowRate;
320 :
321 4 : if (FanVolFlow != DataSizing::AutoSize && unitHeat.MaxAirVolFlow != DataSizing::AutoSize && FanVolFlow < unitHeat.MaxAirVolFlow) {
322 0 : ShowSevereError(state, format("Specified in {} = {}", CurrentModuleObject, unitHeat.Name));
323 0 : ShowContinueError(
324 : state,
325 0 : format("...air flow rate ({:.7T}) in fan object {} is less than the unit heater maximum supply air flow rate ({:.7T}).",
326 : FanVolFlow,
327 0 : unitHeat.FanName,
328 0 : unitHeat.MaxAirVolFlow));
329 0 : ShowContinueError(state, "...the fan flow rate must be greater than or equal to the unit heater maximum supply air flow rate.");
330 0 : ErrorsFound = true;
331 4 : } else if (FanVolFlow == DataSizing::AutoSize && unitHeat.MaxAirVolFlow != DataSizing::AutoSize) {
332 0 : ShowWarningError(state, format("Specified in {} = {}", CurrentModuleObject, unitHeat.Name));
333 0 : ShowContinueError(state, "...the fan flow rate is autosized while the unit heater flow rate is not.");
334 0 : ShowContinueError(state, "...this can lead to unexpected results where the fan flow rate is less than required.");
335 4 : } else if (FanVolFlow != DataSizing::AutoSize && unitHeat.MaxAirVolFlow == DataSizing::AutoSize) {
336 0 : ShowWarningError(state, format("Specified in {} = {}", CurrentModuleObject, unitHeat.Name));
337 0 : ShowContinueError(state, "...the unit heater flow rate is autosized while the fan flow rate is not.");
338 0 : ShowContinueError(state, "...this can lead to unexpected results where the fan flow rate is less than required.");
339 : }
340 4 : unitHeat.fanAvailSched = fan->availSched;
341 : }
342 :
343 : // Heating coil information:
344 : {
345 4 : unitHeat.Type = static_cast<HCoilType>(getEnumValue(HCoilTypeNamesUC, Util::makeUPPER(Alphas(7))));
346 4 : switch (unitHeat.Type) {
347 3 : case HCoilType::WaterHeatingCoil:
348 3 : unitHeat.HeatingCoilType = DataPlant::PlantEquipmentType::CoilWaterSimpleHeating;
349 3 : break;
350 0 : case HCoilType::SteamCoil:
351 0 : unitHeat.HeatingCoilType = DataPlant::PlantEquipmentType::CoilSteamAirHeating;
352 0 : break;
353 1 : case HCoilType::Electric:
354 : case HCoilType::Gas:
355 1 : break;
356 0 : default: {
357 0 : ShowSevereError(state, format("Illegal {} = {}", cAlphaFields(7), Alphas(7)));
358 0 : ShowContinueError(state, format("Occurs in {}={}", CurrentModuleObject, unitHeat.Name));
359 0 : ErrorsFound = true;
360 0 : errFlag = true;
361 : }
362 : }
363 : }
364 :
365 4 : if (!errFlag) {
366 4 : unitHeat.HCoilTypeCh = Alphas(7);
367 4 : unitHeat.HCoilName = Alphas(8);
368 4 : ValidateComponent(state, Alphas(7), unitHeat.HCoilName, IsNotOK, CurrentModuleObject);
369 4 : if (IsNotOK) {
370 0 : ShowContinueError(state, format("specified in {} = \"{}\"", CurrentModuleObject, unitHeat.Name));
371 0 : ErrorsFound = true;
372 : } else {
373 : // The heating coil control node is necessary for hot water and steam coils, but not necessary for an
374 : // electric or gas coil.
375 4 : if (unitHeat.Type == HCoilType::WaterHeatingCoil || unitHeat.Type == HCoilType::SteamCoil) {
376 : // mine the hot water or steam node from the coil object
377 3 : errFlag = false;
378 3 : if (unitHeat.Type == HCoilType::WaterHeatingCoil) {
379 3 : unitHeat.HotControlNode = WaterCoils::GetCoilWaterInletNode(state, "Coil:Heating:Water", unitHeat.HCoilName, errFlag);
380 : } else { // its a steam coil
381 0 : unitHeat.HCoil_Index = SteamCoils::GetSteamCoilIndex(state, "COIL:HEATING:STEAM", unitHeat.HCoilName, errFlag);
382 0 : unitHeat.HotControlNode = SteamCoils::GetCoilSteamInletNode(state, unitHeat.HCoil_Index, unitHeat.HCoilName, errFlag);
383 0 : unitHeat.HCoil_fluid = Fluid::GetSteam(state);
384 : }
385 : // Other error checks should trap before it gets to this point in the code, but including just in case.
386 3 : if (errFlag) {
387 0 : ShowContinueError(state, format("that was specified in {} = \"{}\"", CurrentModuleObject, unitHeat.Name));
388 0 : ErrorsFound = true;
389 : }
390 : }
391 : }
392 : }
393 :
394 4 : if (lAlphaBlanks(9)) {
395 11 : unitHeat.fanOp = (unitHeat.fanType == HVAC::FanType::OnOff || unitHeat.fanType == HVAC::FanType::SystemModel)
396 4 : ? HVAC::FanOp::Cycling
397 : : HVAC::FanOp::Continuous;
398 0 : } else if ((state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanOpModeSched = Sched::GetSchedule(state, Alphas(9))) == nullptr) {
399 0 : ShowSevereItemNotFound(state, eoh, cAlphaFields(9), Alphas(9));
400 0 : ErrorsFound = true;
401 0 : } else if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanType == HVAC::FanType::Constant &&
402 0 : !state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanOpModeSched->checkMinMaxVals(state, Clusive::In, 0.0, Clusive::In, 1.0)) {
403 0 : Sched::ShowSevereBadMinMax(state, eoh, cAlphaFields(9), Alphas(9), Clusive::In, 0.0, Clusive::In, 1.0);
404 0 : ErrorsFound = true;
405 : }
406 :
407 4 : unitHeat.FanOperatesDuringNoHeating = Alphas(10);
408 4 : if ((!Util::SameString(unitHeat.FanOperatesDuringNoHeating, "Yes")) && (!Util::SameString(unitHeat.FanOperatesDuringNoHeating, "No"))) {
409 0 : ErrorsFound = true;
410 0 : ShowSevereError(state, format("Illegal {} = {}", cAlphaFields(10), Alphas(10)));
411 0 : ShowContinueError(state, format("Occurs in {}={}", CurrentModuleObject, unitHeat.Name));
412 4 : } else if (Util::SameString(unitHeat.FanOperatesDuringNoHeating, "No")) {
413 4 : unitHeat.FanOffNoHeating = true;
414 : }
415 :
416 4 : unitHeat.MaxVolHotWaterFlow = Numbers(2);
417 4 : unitHeat.MinVolHotWaterFlow = Numbers(3);
418 4 : unitHeat.MaxVolHotSteamFlow = Numbers(2);
419 4 : unitHeat.MinVolHotSteamFlow = Numbers(3);
420 :
421 4 : unitHeat.HotControlOffset = Numbers(4);
422 : // Set default convergence tolerance
423 4 : if (unitHeat.HotControlOffset <= 0.0) {
424 0 : unitHeat.HotControlOffset = 0.001;
425 : }
426 :
427 4 : if (!lAlphaBlanks(11)) {
428 0 : unitHeat.AvailManagerListName = Alphas(11);
429 : }
430 :
431 4 : unitHeat.HVACSizingIndex = 0;
432 4 : if (!lAlphaBlanks(12)) {
433 0 : unitHeat.HVACSizingIndex = Util::FindItemInList(Alphas(12), state.dataSize->ZoneHVACSizing);
434 0 : if (unitHeat.HVACSizingIndex == 0) {
435 0 : ShowSevereError(state, format("{} = {} not found.", cAlphaFields(12), Alphas(12)));
436 0 : ShowContinueError(state, format("Occurs in {} = {}", CurrentModuleObject, unitHeat.Name));
437 0 : ErrorsFound = true;
438 : }
439 : }
440 :
441 : // check that unit heater air inlet node must be the same as a zone exhaust node
442 4 : bool ZoneNodeNotFound = true;
443 8 : for (CtrlZone = 1; CtrlZone <= state.dataGlobal->NumOfZones; ++CtrlZone) {
444 4 : if (!state.dataZoneEquip->ZoneEquipConfig(CtrlZone).IsControlled) continue;
445 4 : for (NodeNum = 1; NodeNum <= state.dataZoneEquip->ZoneEquipConfig(CtrlZone).NumExhaustNodes; ++NodeNum) {
446 4 : if (unitHeat.AirInNode == state.dataZoneEquip->ZoneEquipConfig(CtrlZone).ExhaustNode(NodeNum)) {
447 4 : ZoneNodeNotFound = false;
448 4 : break;
449 : }
450 : }
451 : }
452 4 : if (ZoneNodeNotFound) {
453 0 : ShowSevereError(state,
454 0 : format("{} = \"{}\". Unit heater air inlet node name must be the same as a zone exhaust node name.",
455 : CurrentModuleObject,
456 0 : unitHeat.Name));
457 0 : ShowContinueError(state, "..Zone exhaust node name is specified in ZoneHVAC:EquipmentConnections object.");
458 0 : ShowContinueError(state, format("..Unit heater air inlet node name = {}", state.dataLoopNodes->NodeID(unitHeat.AirInNode)));
459 0 : ErrorsFound = true;
460 : }
461 : // check that unit heater air outlet node is a zone inlet node.
462 4 : ZoneNodeNotFound = true;
463 8 : for (CtrlZone = 1; CtrlZone <= state.dataGlobal->NumOfZones; ++CtrlZone) {
464 4 : if (!state.dataZoneEquip->ZoneEquipConfig(CtrlZone).IsControlled) continue;
465 5 : for (NodeNum = 1; NodeNum <= state.dataZoneEquip->ZoneEquipConfig(CtrlZone).NumInletNodes; ++NodeNum) {
466 5 : if (unitHeat.AirOutNode == state.dataZoneEquip->ZoneEquipConfig(CtrlZone).InletNode(NodeNum)) {
467 4 : unitHeat.ZonePtr = CtrlZone;
468 4 : ZoneNodeNotFound = false;
469 4 : break;
470 : }
471 : }
472 : }
473 4 : if (ZoneNodeNotFound) {
474 0 : ShowSevereError(state,
475 0 : format("{} = \"{}\". Unit heater air outlet node name must be the same as a zone inlet node name.",
476 : CurrentModuleObject,
477 0 : unitHeat.Name));
478 0 : ShowContinueError(state, "..Zone inlet node name is specified in ZoneHVAC:EquipmentConnections object.");
479 0 : ShowContinueError(state, format("..Unit heater air outlet node name = {}", state.dataLoopNodes->NodeID(unitHeat.AirOutNode)));
480 0 : ErrorsFound = true;
481 : }
482 :
483 : // Add fan to component sets array
484 12 : BranchNodeConnections::SetUpCompSets(state,
485 : CurrentModuleObject,
486 : unitHeat.Name,
487 4 : HVAC::fanTypeNamesUC[(int)unitHeat.fanType],
488 : unitHeat.FanName,
489 4 : state.dataLoopNodes->NodeID(unitHeat.AirInNode),
490 4 : state.dataLoopNodes->NodeID(unitHeat.FanOutletNode));
491 :
492 : // Add heating coil to component sets array
493 8 : BranchNodeConnections::SetUpCompSets(state,
494 : CurrentModuleObject,
495 : unitHeat.Name,
496 : unitHeat.HCoilTypeCh,
497 : unitHeat.HCoilName,
498 4 : state.dataLoopNodes->NodeID(unitHeat.FanOutletNode),
499 4 : state.dataLoopNodes->NodeID(unitHeat.AirOutNode));
500 :
501 : } // ...loop over all of the unit heaters found in the input file
502 :
503 4 : Alphas.deallocate();
504 4 : Numbers.deallocate();
505 4 : cAlphaFields.deallocate();
506 4 : cNumericFields.deallocate();
507 4 : lAlphaBlanks.deallocate();
508 4 : lNumericBlanks.deallocate();
509 :
510 4 : if (ErrorsFound) ShowFatalError(state, format("{}Errors found in input", RoutineName));
511 :
512 : // Setup Report variables for the Unit Heaters, CurrentModuleObject='ZoneHVAC:UnitHeater'
513 8 : for (int UnitHeatNum = 1; UnitHeatNum <= state.dataUnitHeaters->NumOfUnitHeats; ++UnitHeatNum) {
514 4 : auto &unitHeat = state.dataUnitHeaters->UnitHeat(UnitHeatNum);
515 8 : SetupOutputVariable(state,
516 : "Zone Unit Heater Heating Rate",
517 : Constant::Units::W,
518 4 : unitHeat.HeatPower,
519 : OutputProcessor::TimeStepType::System,
520 : OutputProcessor::StoreType::Average,
521 4 : unitHeat.Name);
522 8 : SetupOutputVariable(state,
523 : "Zone Unit Heater Heating Energy",
524 : Constant::Units::J,
525 4 : unitHeat.HeatEnergy,
526 : OutputProcessor::TimeStepType::System,
527 : OutputProcessor::StoreType::Sum,
528 4 : unitHeat.Name);
529 8 : SetupOutputVariable(state,
530 : "Zone Unit Heater Fan Electricity Rate",
531 : Constant::Units::W,
532 4 : unitHeat.ElecPower,
533 : OutputProcessor::TimeStepType::System,
534 : OutputProcessor::StoreType::Average,
535 4 : unitHeat.Name);
536 : // Note that the unit heater fan electric is NOT metered because this value is already metered through the fan component
537 8 : SetupOutputVariable(state,
538 : "Zone Unit Heater Fan Electricity Energy",
539 : Constant::Units::J,
540 4 : unitHeat.ElecEnergy,
541 : OutputProcessor::TimeStepType::System,
542 : OutputProcessor::StoreType::Sum,
543 4 : unitHeat.Name);
544 4 : SetupOutputVariable(state,
545 : "Zone Unit Heater Fan Availability Status",
546 : Constant::Units::None,
547 4 : (int &)unitHeat.availStatus,
548 : OutputProcessor::TimeStepType::System,
549 : OutputProcessor::StoreType::Average,
550 4 : unitHeat.Name);
551 4 : if (unitHeat.fanType == HVAC::FanType::OnOff) {
552 2 : SetupOutputVariable(state,
553 : "Zone Unit Heater Fan Part Load Ratio",
554 : Constant::Units::None,
555 1 : unitHeat.FanPartLoadRatio,
556 : OutputProcessor::TimeStepType::System,
557 : OutputProcessor::StoreType::Average,
558 1 : unitHeat.Name);
559 : }
560 4 : state.dataRptCoilSelection->coilSelectionReportObj->setCoilSupplyFanInfo(
561 4 : state, unitHeat.HCoilName, unitHeat.HCoilTypeCh, unitHeat.FanName, unitHeat.fanType, unitHeat.Fan_Index);
562 : }
563 4 : }
564 :
565 363 : void InitUnitHeater(EnergyPlusData &state,
566 : int const UnitHeatNum, // index for the current unit heater
567 : int const ZoneNum, // number of zone being served
568 : [[maybe_unused]] bool const FirstHVACIteration // TRUE if 1st HVAC simulation of system timestep
569 : )
570 : {
571 :
572 : // SUBROUTINE INFORMATION:
573 : // AUTHOR Rick Strand
574 : // DATE WRITTEN May 2000
575 : // MODIFIED Chandan Sharma, FSEC, March 2011: Added ZoneHVAC sys avail manager
576 :
577 : // PURPOSE OF THIS SUBROUTINE:
578 : // This subroutine initializes all of the data elements which are necessary
579 : // to simulate a unit heater.
580 :
581 : // METHODOLOGY EMPLOYED:
582 : // Uses the status flags to trigger initializations.
583 :
584 : // SUBROUTINE PARAMETER DEFINITIONS:
585 : static constexpr std::string_view RoutineName("InitUnitHeater");
586 :
587 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
588 : int InNode; // inlet node number in unit heater loop
589 : int OutNode; // outlet node number in unit heater loop
590 : Real64 RhoAir; // air density at InNode
591 : Real64 TempSteamIn;
592 : Real64 SteamDensity;
593 : Real64 rho; // local fluid density
594 :
595 : // Do the one time initializations
596 363 : if (state.dataUnitHeaters->InitUnitHeaterOneTimeFlag) {
597 :
598 3 : state.dataUnitHeaters->MyEnvrnFlag.allocate(state.dataUnitHeaters->NumOfUnitHeats);
599 3 : state.dataUnitHeaters->MySizeFlag.allocate(state.dataUnitHeaters->NumOfUnitHeats);
600 3 : state.dataUnitHeaters->MyPlantScanFlag.allocate(state.dataUnitHeaters->NumOfUnitHeats);
601 3 : state.dataUnitHeaters->MyZoneEqFlag.allocate(state.dataUnitHeaters->NumOfUnitHeats);
602 3 : state.dataUnitHeaters->MyEnvrnFlag = true;
603 3 : state.dataUnitHeaters->MySizeFlag = true;
604 3 : state.dataUnitHeaters->MyPlantScanFlag = true;
605 3 : state.dataUnitHeaters->MyZoneEqFlag = true;
606 3 : state.dataUnitHeaters->InitUnitHeaterOneTimeFlag = false;
607 : }
608 :
609 363 : if (allocated(state.dataAvail->ZoneComp)) {
610 361 : auto &availMgr = state.dataAvail->ZoneComp(DataZoneEquipment::ZoneEquipType::UnitHeater).ZoneCompAvailMgrs(UnitHeatNum);
611 361 : if (state.dataUnitHeaters->MyZoneEqFlag(UnitHeatNum)) { // initialize the name of each availability manager list and zone number
612 2 : availMgr.AvailManagerListName = state.dataUnitHeaters->UnitHeat(UnitHeatNum).AvailManagerListName;
613 2 : availMgr.ZoneNum = ZoneNum;
614 2 : state.dataUnitHeaters->MyZoneEqFlag(UnitHeatNum) = false;
615 : }
616 361 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).availStatus = availMgr.availStatus;
617 : }
618 :
619 363 : if (state.dataUnitHeaters->MyPlantScanFlag(UnitHeatNum) && allocated(state.dataPlnt->PlantLoop)) {
620 4 : if ((state.dataUnitHeaters->UnitHeat(UnitHeatNum).HeatingCoilType == DataPlant::PlantEquipmentType::CoilWaterSimpleHeating) ||
621 1 : (state.dataUnitHeaters->UnitHeat(UnitHeatNum).HeatingCoilType == DataPlant::PlantEquipmentType::CoilSteamAirHeating)) {
622 2 : bool errFlag = false;
623 4 : PlantUtilities::ScanPlantLoopsForObject(state,
624 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName,
625 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HeatingCoilType,
626 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc,
627 : errFlag,
628 : _,
629 : _,
630 : _,
631 : _,
632 : _);
633 2 : if (errFlag) {
634 0 : ShowContinueError(state,
635 0 : format("Reference Unit=\"{}\", type=ZoneHVAC:UnitHeater", state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name));
636 0 : ShowFatalError(state, "InitUnitHeater: Program terminated due to previous condition(s).");
637 : }
638 :
639 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotCoilOutNodeNum =
640 2 : DataPlant::CompData::getPlantComponent(state, state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc).NodeNumOut;
641 : }
642 3 : state.dataUnitHeaters->MyPlantScanFlag(UnitHeatNum) = false;
643 360 : } else if (state.dataUnitHeaters->MyPlantScanFlag(UnitHeatNum) && !state.dataGlobal->AnyPlantInModel) {
644 0 : state.dataUnitHeaters->MyPlantScanFlag(UnitHeatNum) = false;
645 : }
646 : // need to check all units to see if they are on Zone Equipment List or issue warning
647 363 : if (!state.dataUnitHeaters->ZoneEquipmentListChecked && state.dataZoneEquip->ZoneEquipInputsFilled) {
648 2 : state.dataUnitHeaters->ZoneEquipmentListChecked = true;
649 4 : for (int Loop = 1; Loop <= state.dataUnitHeaters->NumOfUnitHeats; ++Loop) {
650 2 : if (DataZoneEquipment::CheckZoneEquipmentList(state, "ZoneHVAC:UnitHeater", state.dataUnitHeaters->UnitHeat(Loop).Name)) continue;
651 0 : ShowSevereError(state,
652 0 : format("InitUnitHeater: Unit=[UNIT HEATER,{}] is not on any ZoneHVAC:EquipmentList. It will not be simulated.",
653 0 : state.dataUnitHeaters->UnitHeat(Loop).Name));
654 : }
655 : }
656 :
657 365 : if (!state.dataGlobal->SysSizingCalc && state.dataUnitHeaters->MySizeFlag(UnitHeatNum) &&
658 2 : !state.dataUnitHeaters->MyPlantScanFlag(UnitHeatNum)) {
659 :
660 2 : SizeUnitHeater(state, UnitHeatNum);
661 :
662 2 : state.dataUnitHeaters->MySizeFlag(UnitHeatNum) = false;
663 : } // Do the one time initializations
664 :
665 366 : if (state.dataGlobal->BeginEnvrnFlag && state.dataUnitHeaters->MyEnvrnFlag(UnitHeatNum) &&
666 3 : !state.dataUnitHeaters->MyPlantScanFlag(UnitHeatNum)) {
667 3 : InNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode;
668 3 : OutNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirOutNode;
669 3 : RhoAir = state.dataEnvrn->StdRhoAir;
670 :
671 : // set the mass flow rates from the input volume flow rates
672 3 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirMassFlow = RhoAir * state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirVolFlow;
673 :
674 : // set the node max and min mass flow rates
675 3 : state.dataLoopNodes->Node(OutNode).MassFlowRateMax = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirMassFlow;
676 3 : state.dataLoopNodes->Node(OutNode).MassFlowRateMin = 0.0;
677 :
678 3 : state.dataLoopNodes->Node(InNode).MassFlowRateMax = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirMassFlow;
679 3 : state.dataLoopNodes->Node(InNode).MassFlowRateMin = 0.0;
680 :
681 3 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type == HCoilType::WaterHeatingCoil) {
682 1 : rho = state.dataPlnt->PlantLoop(state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc.loopNum)
683 1 : .glycol->getDensity(state, Constant::HWInitConvTemp, RoutineName);
684 :
685 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxHotWaterFlow = rho * state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotWaterFlow;
686 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MinHotWaterFlow = rho * state.dataUnitHeaters->UnitHeat(UnitHeatNum).MinVolHotWaterFlow;
687 4 : PlantUtilities::InitComponentNodes(state,
688 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MinHotWaterFlow,
689 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxHotWaterFlow,
690 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlNode,
691 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotCoilOutNodeNum);
692 : }
693 3 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type == HCoilType::SteamCoil) {
694 0 : TempSteamIn = 100.00;
695 0 : SteamDensity = state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoil_fluid->getSatDensity(state, TempSteamIn, 1.0, RoutineName);
696 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxHotSteamFlow =
697 0 : SteamDensity * state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotSteamFlow;
698 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MinHotSteamFlow =
699 0 : SteamDensity * state.dataUnitHeaters->UnitHeat(UnitHeatNum).MinVolHotSteamFlow;
700 :
701 0 : PlantUtilities::InitComponentNodes(state,
702 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MinHotSteamFlow,
703 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxHotSteamFlow,
704 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlNode,
705 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotCoilOutNodeNum);
706 : }
707 :
708 3 : state.dataUnitHeaters->MyEnvrnFlag(UnitHeatNum) = false;
709 : } // ...end start of environment inits
710 :
711 363 : if (!state.dataGlobal->BeginEnvrnFlag) state.dataUnitHeaters->MyEnvrnFlag(UnitHeatNum) = true;
712 :
713 : // These initializations are done every iteration...
714 363 : InNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode;
715 363 : OutNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirOutNode;
716 :
717 363 : state.dataUnitHeaters->QZnReq = state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneNum).RemainingOutputReqToHeatSP; // zone load needed
718 363 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanOpModeSched != nullptr) {
719 0 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanOpModeSched->getCurrentVal() == 0.0 &&
720 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanType == HVAC::FanType::OnOff) {
721 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanOp = HVAC::FanOp::Cycling;
722 : } else {
723 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanOp = HVAC::FanOp::Continuous;
724 : }
725 0 : if ((state.dataUnitHeaters->QZnReq < HVAC::SmallLoad) || state.dataZoneEnergyDemand->CurDeadBandOrSetback(ZoneNum)) {
726 : // Unit is available, but there is no load on it or we are in setback/deadband
727 0 : if (!state.dataUnitHeaters->UnitHeat(UnitHeatNum).FanOffNoHeating &&
728 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanOpModeSched->getCurrentVal() > 0.0) {
729 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanOp = HVAC::FanOp::Continuous;
730 : }
731 : }
732 : }
733 :
734 363 : state.dataUnitHeaters->SetMassFlowRateToZero = false;
735 363 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).availSched->getCurrentVal() > 0) {
736 726 : if ((state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanAvailSched->getCurrentVal() > 0 || state.dataHVACGlobal->TurnFansOn) &&
737 363 : !state.dataHVACGlobal->TurnFansOff) {
738 726 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).FanOffNoHeating &&
739 363 : ((state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneNum).RemainingOutputReqToHeatSP < HVAC::SmallLoad) ||
740 3 : (state.dataZoneEnergyDemand->CurDeadBandOrSetback(ZoneNum)))) {
741 360 : state.dataUnitHeaters->SetMassFlowRateToZero = true;
742 : }
743 : } else {
744 0 : state.dataUnitHeaters->SetMassFlowRateToZero = true;
745 : }
746 : } else {
747 0 : state.dataUnitHeaters->SetMassFlowRateToZero = true;
748 : }
749 :
750 363 : if (state.dataUnitHeaters->SetMassFlowRateToZero) {
751 360 : state.dataLoopNodes->Node(InNode).MassFlowRate = 0.0;
752 360 : state.dataLoopNodes->Node(InNode).MassFlowRateMaxAvail = 0.0;
753 360 : state.dataLoopNodes->Node(InNode).MassFlowRateMinAvail = 0.0;
754 360 : state.dataLoopNodes->Node(OutNode).MassFlowRate = 0.0;
755 360 : state.dataLoopNodes->Node(OutNode).MassFlowRateMaxAvail = 0.0;
756 360 : state.dataLoopNodes->Node(OutNode).MassFlowRateMinAvail = 0.0;
757 : } else {
758 3 : state.dataLoopNodes->Node(InNode).MassFlowRate = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirMassFlow;
759 3 : state.dataLoopNodes->Node(InNode).MassFlowRateMaxAvail = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirMassFlow;
760 3 : state.dataLoopNodes->Node(InNode).MassFlowRateMinAvail = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirMassFlow;
761 3 : state.dataLoopNodes->Node(OutNode).MassFlowRate = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirMassFlow;
762 3 : state.dataLoopNodes->Node(OutNode).MassFlowRateMaxAvail = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirMassFlow;
763 3 : state.dataLoopNodes->Node(OutNode).MassFlowRateMinAvail = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirMassFlow;
764 : }
765 :
766 : // Just in case the unit is off and conditions do not get sent through
767 : // the unit for some reason, set the outlet conditions equal to the inlet
768 : // conditions of the unit heater
769 363 : state.dataLoopNodes->Node(OutNode).Temp = state.dataLoopNodes->Node(InNode).Temp;
770 363 : state.dataLoopNodes->Node(OutNode).Press = state.dataLoopNodes->Node(InNode).Press;
771 363 : state.dataLoopNodes->Node(OutNode).HumRat = state.dataLoopNodes->Node(InNode).HumRat;
772 363 : state.dataLoopNodes->Node(OutNode).Enthalpy = state.dataLoopNodes->Node(InNode).Enthalpy;
773 363 : }
774 :
775 2 : void SizeUnitHeater(EnergyPlusData &state, int const UnitHeatNum)
776 : {
777 :
778 : // SUBROUTINE INFORMATION:
779 : // AUTHOR Fred Buhl
780 : // DATE WRITTEN February 2002
781 : // MODIFIED August 2013 Daeho Kang, add component sizing table entries
782 : // July 2014, B. Nigusse, added scalable sizing
783 : // RE-ENGINEERED na
784 :
785 : // PURPOSE OF THIS SUBROUTINE:
786 : // This subroutine is for sizing Unit Heater components for which flow rates have not been
787 : // specified in the input.
788 :
789 : // METHODOLOGY EMPLOYED:
790 : // Obtains flow rates from the zone sizing arrays and plant sizing data.
791 :
792 : // SUBROUTINE PARAMETER DEFINITIONS:
793 : static constexpr std::string_view RoutineName("SizeUnitHeater");
794 :
795 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
796 : int PltSizHeatNum; // index of plant sizing object for 1st heating loop
797 : Real64 DesCoilLoad;
798 : Real64 TempSteamIn;
799 : Real64 EnthSteamInDry;
800 : Real64 EnthSteamOutWet;
801 : Real64 LatentHeatSteam;
802 : Real64 SteamDensity;
803 : Real64 Cp; // local temporary for fluid specific heat
804 : Real64 rho; // local temporary for fluid density
805 2 : std::string SizingString; // input field sizing description (e.g., Nominal Capacity)
806 : Real64 TempSize; // autosized value of coil input field
807 : bool PrintFlag; // TRUE when sizing information is reported in the eio file
808 : int zoneHVACIndex; // index of zoneHVAC equipment sizing specification
809 : Real64 WaterCoilSizDeltaT; // water coil deltaT for design water flow rate autosizing
810 :
811 2 : int &CurZoneEqNum = state.dataSize->CurZoneEqNum;
812 :
813 2 : bool ErrorsFound = false;
814 2 : Real64 MaxAirVolFlowDes = 0.0;
815 2 : Real64 MaxAirVolFlowUser = 0.0;
816 2 : Real64 MaxVolHotWaterFlowDes = 0.0;
817 2 : Real64 MaxVolHotWaterFlowUser = 0.0;
818 2 : Real64 MaxVolHotSteamFlowDes = 0.0;
819 2 : Real64 MaxVolHotSteamFlowUser = 0.0;
820 :
821 2 : state.dataSize->DataScalableSizingON = false;
822 2 : state.dataSize->DataScalableCapSizingON = false;
823 2 : state.dataSize->ZoneHeatingOnlyFan = true;
824 2 : std::string CompType = "ZoneHVAC:UnitHeater";
825 2 : std::string CompName = state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name;
826 2 : state.dataSize->DataZoneNumber = state.dataUnitHeaters->UnitHeat(UnitHeatNum).ZonePtr;
827 2 : state.dataSize->DataFanType = state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanType;
828 2 : state.dataSize->DataFanIndex = state.dataUnitHeaters->UnitHeat(UnitHeatNum).Fan_Index;
829 : // unit heater is always blow thru
830 2 : state.dataSize->DataFanPlacement = HVAC::FanPlace::BlowThru;
831 :
832 2 : if (CurZoneEqNum > 0) {
833 2 : auto &ZoneEqSizing = state.dataSize->ZoneEqSizing(CurZoneEqNum);
834 2 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).HVACSizingIndex > 0) {
835 0 : zoneHVACIndex = state.dataUnitHeaters->UnitHeat(UnitHeatNum).HVACSizingIndex;
836 0 : int SizingMethod = HVAC::HeatingAirflowSizing;
837 0 : int FieldNum = 1; // N1 , \field Maximum Supply Air Flow Rate
838 0 : PrintFlag = true;
839 0 : SizingString = state.dataUnitHeaters->UnitHeatNumericFields(UnitHeatNum).FieldNames(FieldNum) + " [m3/s]";
840 0 : int SAFMethod = state.dataSize->ZoneHVACSizing(zoneHVACIndex).HeatingSAFMethod;
841 0 : ZoneEqSizing.SizingMethod(SizingMethod) = SAFMethod;
842 0 : if (SAFMethod == DataSizing::None || SAFMethod == DataSizing::SupplyAirFlowRate || SAFMethod == DataSizing::FlowPerFloorArea ||
843 : SAFMethod == DataSizing::FractionOfAutosizedHeatingAirflow) {
844 0 : if (SAFMethod == DataSizing::SupplyAirFlowRate) {
845 0 : if (state.dataSize->ZoneHVACSizing(zoneHVACIndex).MaxHeatAirVolFlow > 0.0) {
846 0 : ZoneEqSizing.AirVolFlow = state.dataSize->ZoneHVACSizing(zoneHVACIndex).MaxHeatAirVolFlow;
847 0 : ZoneEqSizing.SystemAirFlow = true;
848 : }
849 0 : TempSize = state.dataSize->ZoneHVACSizing(zoneHVACIndex).MaxHeatAirVolFlow;
850 0 : } else if (SAFMethod == DataSizing::FlowPerFloorArea) {
851 0 : ZoneEqSizing.SystemAirFlow = true;
852 0 : ZoneEqSizing.AirVolFlow = state.dataSize->ZoneHVACSizing(zoneHVACIndex).MaxHeatAirVolFlow *
853 0 : state.dataHeatBal->Zone(state.dataSize->DataZoneNumber).FloorArea;
854 0 : TempSize = ZoneEqSizing.AirVolFlow;
855 0 : state.dataSize->DataScalableSizingON = true;
856 0 : } else if (SAFMethod == DataSizing::FractionOfAutosizedHeatingAirflow) {
857 0 : state.dataSize->DataFracOfAutosizedCoolingAirflow = state.dataSize->ZoneHVACSizing(zoneHVACIndex).MaxHeatAirVolFlow;
858 0 : TempSize = DataSizing::AutoSize;
859 0 : state.dataSize->DataScalableSizingON = true;
860 : } else {
861 0 : TempSize = state.dataSize->ZoneHVACSizing(zoneHVACIndex).MaxHeatAirVolFlow;
862 : }
863 0 : bool errorsFound = false;
864 0 : HeatingAirFlowSizer sizingHeatingAirFlow;
865 0 : sizingHeatingAirFlow.overrideSizingString(SizingString);
866 : // sizingHeatingAirFlow.setHVACSizingIndexData(FanCoil(FanCoilNum).HVACSizingIndex);
867 0 : sizingHeatingAirFlow.initializeWithinEP(state, CompType, CompName, PrintFlag, RoutineName);
868 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirVolFlow = sizingHeatingAirFlow.size(state, TempSize, errorsFound);
869 :
870 0 : } else if (SAFMethod == DataSizing::FlowPerHeatingCapacity) {
871 0 : TempSize = DataSizing::AutoSize;
872 0 : PrintFlag = false;
873 0 : state.dataSize->DataScalableSizingON = true;
874 0 : state.dataSize->DataFlowUsedForSizing = state.dataSize->FinalZoneSizing(CurZoneEqNum).DesHeatVolFlow;
875 0 : bool errorsFound = false;
876 0 : HeatingCapacitySizer sizerHeatingCapacity;
877 0 : sizerHeatingCapacity.overrideSizingString(SizingString);
878 0 : sizerHeatingCapacity.initializeWithinEP(state, CompType, CompName, PrintFlag, RoutineName);
879 0 : TempSize = sizerHeatingCapacity.size(state, TempSize, errorsFound);
880 0 : if (state.dataSize->ZoneHVACSizing(zoneHVACIndex).HeatingCapMethod == DataSizing::FractionOfAutosizedHeatingCapacity) {
881 0 : state.dataSize->DataFracOfAutosizedHeatingCapacity = state.dataSize->ZoneHVACSizing(zoneHVACIndex).ScaledHeatingCapacity;
882 : }
883 0 : state.dataSize->DataAutosizedHeatingCapacity = TempSize;
884 0 : state.dataSize->DataFlowPerHeatingCapacity = state.dataSize->ZoneHVACSizing(zoneHVACIndex).MaxHeatAirVolFlow;
885 0 : PrintFlag = true;
886 0 : TempSize = DataSizing::AutoSize;
887 0 : errorsFound = false;
888 0 : HeatingAirFlowSizer sizingHeatingAirFlow;
889 0 : sizingHeatingAirFlow.overrideSizingString(SizingString);
890 : // sizingHeatingAirFlow.setHVACSizingIndexData(FanCoil(FanCoilNum).HVACSizingIndex);
891 0 : sizingHeatingAirFlow.initializeWithinEP(state, CompType, CompName, PrintFlag, RoutineName);
892 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirVolFlow = sizingHeatingAirFlow.size(state, TempSize, errorsFound);
893 0 : }
894 0 : state.dataSize->DataScalableSizingON = false;
895 : } else {
896 : // no scalble sizing method has been specified. Sizing proceeds using the method
897 : // specified in the zoneHVAC object
898 2 : int FieldNum = 1; // N1 , \field Maximum Supply Air Flow Rate
899 2 : PrintFlag = true;
900 2 : SizingString = state.dataUnitHeaters->UnitHeatNumericFields(UnitHeatNum).FieldNames(FieldNum) + " [m3/s]";
901 2 : TempSize = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirVolFlow;
902 2 : bool errorsFound = false;
903 2 : HeatingAirFlowSizer sizingHeatingAirFlow;
904 2 : sizingHeatingAirFlow.overrideSizingString(SizingString);
905 : // sizingHeatingAirFlow.setHVACSizingIndexData(FanCoil(FanCoilNum).HVACSizingIndex);
906 2 : sizingHeatingAirFlow.initializeWithinEP(state, CompType, CompName, PrintFlag, RoutineName);
907 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirVolFlow = sizingHeatingAirFlow.size(state, TempSize, errorsFound);
908 2 : }
909 : }
910 :
911 2 : bool IsAutoSize = false;
912 2 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotWaterFlow == DataSizing::AutoSize) {
913 1 : IsAutoSize = true;
914 : }
915 :
916 2 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type == HCoilType::WaterHeatingCoil) {
917 :
918 1 : if (CurZoneEqNum > 0) {
919 1 : if (!IsAutoSize && !state.dataSize->ZoneSizingRunDone) { // Simulation continue
920 0 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotWaterFlow > 0.0) {
921 0 : BaseSizer::reportSizerOutput(state,
922 : "ZoneHVAC:UnitHeater",
923 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name,
924 : "User-Specified Maximum Hot Water Flow [m3/s]",
925 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotWaterFlow);
926 : }
927 : } else {
928 1 : CheckZoneSizing(state, "ZoneHVAC:UnitHeater", state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name);
929 :
930 1 : int CoilWaterInletNode = WaterCoils::GetCoilWaterInletNode(
931 1 : state, "Coil:Heating:Water", state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName, ErrorsFound);
932 1 : int CoilWaterOutletNode = WaterCoils::GetCoilWaterOutletNode(
933 1 : state, "Coil:Heating:Water", state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName, ErrorsFound);
934 1 : if (IsAutoSize) {
935 1 : bool DoWaterCoilSizing = false; // if TRUE do water coil sizing calculation
936 1 : PltSizHeatNum = PlantUtilities::MyPlantSizingIndex(state,
937 : "Coil:Heating:Water",
938 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName,
939 : CoilWaterInletNode,
940 : CoilWaterOutletNode,
941 : ErrorsFound);
942 1 : int CoilNum = WaterCoils::GetWaterCoilIndex(
943 1 : state, "COIL:HEATING:WATER", state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName, ErrorsFound);
944 1 : if (state.dataWaterCoils->WaterCoil(CoilNum).UseDesignWaterDeltaTemp) {
945 0 : WaterCoilSizDeltaT = state.dataWaterCoils->WaterCoil(CoilNum).DesignWaterDeltaTemp;
946 0 : DoWaterCoilSizing = true;
947 : } else {
948 1 : if (PltSizHeatNum > 0) {
949 1 : WaterCoilSizDeltaT = state.dataSize->PlantSizData(PltSizHeatNum).DeltaT;
950 1 : DoWaterCoilSizing = true;
951 : } else {
952 0 : DoWaterCoilSizing = false;
953 : // If there is no heating Plant Sizing object and autosizing was requested, issue fatal error message
954 0 : ShowSevereError(state, "Autosizing of water coil requires a heating loop Sizing:Plant object");
955 0 : ShowContinueError(
956 0 : state, format("Occurs in ZoneHVAC:UnitHeater Object={}", state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name));
957 0 : ErrorsFound = true;
958 : }
959 : }
960 :
961 1 : if (DoWaterCoilSizing) {
962 1 : auto &ZoneEqSizing = state.dataSize->ZoneEqSizing(CurZoneEqNum);
963 1 : int SizingMethod = HVAC::HeatingCapacitySizing;
964 1 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).HVACSizingIndex > 0) {
965 0 : zoneHVACIndex = state.dataUnitHeaters->UnitHeat(UnitHeatNum).HVACSizingIndex;
966 0 : int CapSizingMethod = state.dataSize->ZoneHVACSizing(zoneHVACIndex).HeatingCapMethod;
967 0 : ZoneEqSizing.SizingMethod(SizingMethod) = CapSizingMethod;
968 0 : if (CapSizingMethod == DataSizing::HeatingDesignCapacity || CapSizingMethod == DataSizing::CapacityPerFloorArea ||
969 : CapSizingMethod == DataSizing::FractionOfAutosizedHeatingCapacity) {
970 0 : if (CapSizingMethod == DataSizing::HeatingDesignCapacity) {
971 0 : if (state.dataSize->ZoneHVACSizing(zoneHVACIndex).ScaledHeatingCapacity == DataSizing::AutoSize) {
972 0 : ZoneEqSizing.DesHeatingLoad = state.dataSize->FinalZoneSizing(CurZoneEqNum).DesHeatLoad;
973 : } else {
974 0 : ZoneEqSizing.DesHeatingLoad = state.dataSize->ZoneHVACSizing(zoneHVACIndex).ScaledHeatingCapacity;
975 : }
976 0 : ZoneEqSizing.HeatingCapacity = true;
977 0 : TempSize = DataSizing::AutoSize;
978 0 : } else if (CapSizingMethod == DataSizing::CapacityPerFloorArea) {
979 0 : ZoneEqSizing.HeatingCapacity = true;
980 0 : ZoneEqSizing.DesHeatingLoad = state.dataSize->ZoneHVACSizing(zoneHVACIndex).ScaledHeatingCapacity *
981 0 : state.dataHeatBal->Zone(state.dataSize->DataZoneNumber).FloorArea;
982 0 : state.dataSize->DataScalableCapSizingON = true;
983 0 : } else if (CapSizingMethod == DataSizing::FractionOfAutosizedHeatingCapacity) {
984 0 : state.dataSize->DataFracOfAutosizedHeatingCapacity =
985 0 : state.dataSize->ZoneHVACSizing(zoneHVACIndex).ScaledHeatingCapacity;
986 0 : state.dataSize->DataScalableCapSizingON = true;
987 0 : TempSize = DataSizing::AutoSize;
988 : }
989 : }
990 0 : PrintFlag = false;
991 0 : bool errorsFound = false;
992 0 : HeatingCapacitySizer sizerHeatingCapacity;
993 0 : sizerHeatingCapacity.overrideSizingString(SizingString);
994 0 : sizerHeatingCapacity.initializeWithinEP(state, CompType, CompName, PrintFlag, RoutineName);
995 0 : DesCoilLoad = sizerHeatingCapacity.size(state, TempSize, errorsFound);
996 0 : state.dataSize->DataScalableCapSizingON = false;
997 0 : } else {
998 1 : SizingString = "";
999 1 : PrintFlag = false;
1000 1 : TempSize = DataSizing::AutoSize;
1001 1 : ZoneEqSizing.HeatingCapacity = true;
1002 1 : ZoneEqSizing.DesHeatingLoad = state.dataSize->FinalZoneSizing(CurZoneEqNum).DesHeatLoad;
1003 1 : bool errorsFound = false;
1004 1 : HeatingCapacitySizer sizerHeatingCapacity;
1005 1 : sizerHeatingCapacity.overrideSizingString(SizingString);
1006 1 : sizerHeatingCapacity.initializeWithinEP(state, CompType, CompName, PrintFlag, RoutineName);
1007 1 : DesCoilLoad = sizerHeatingCapacity.size(state, TempSize, errorsFound);
1008 1 : }
1009 :
1010 1 : if (DesCoilLoad >= HVAC::SmallLoad) {
1011 1 : rho = state.dataPlnt->PlantLoop(state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc.loopNum)
1012 1 : .glycol->getDensity(state, Constant::HWInitConvTemp, RoutineName);
1013 1 : Cp = state.dataPlnt->PlantLoop(state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc.loopNum)
1014 1 : .glycol->getSpecificHeat(state, Constant::HWInitConvTemp, RoutineName);
1015 1 : MaxVolHotWaterFlowDes = DesCoilLoad / (WaterCoilSizDeltaT * Cp * rho);
1016 : } else {
1017 0 : MaxVolHotWaterFlowDes = 0.0;
1018 : }
1019 : }
1020 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotWaterFlow = MaxVolHotWaterFlowDes;
1021 2 : BaseSizer::reportSizerOutput(state,
1022 : "ZoneHVAC:UnitHeater",
1023 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name,
1024 : "Design Size Maximum Hot Water Flow [m3/s]",
1025 : MaxVolHotWaterFlowDes);
1026 : } else {
1027 0 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotWaterFlow > 0.0 && MaxVolHotWaterFlowDes > 0.0) {
1028 0 : MaxVolHotWaterFlowUser = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotWaterFlow;
1029 0 : BaseSizer::reportSizerOutput(state,
1030 : "ZoneHVAC:UnitHeater",
1031 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name,
1032 : "Design Size Maximum Hot Water Flow [m3/s]",
1033 : MaxVolHotWaterFlowDes,
1034 : "User-Specified Maximum Hot Water Flow [m3/s]",
1035 : MaxVolHotWaterFlowUser);
1036 0 : if (state.dataGlobal->DisplayExtraWarnings) {
1037 0 : if ((std::abs(MaxVolHotWaterFlowDes - MaxVolHotWaterFlowUser) / MaxVolHotWaterFlowUser) >
1038 0 : state.dataSize->AutoVsHardSizingThreshold) {
1039 0 : ShowMessage(state,
1040 0 : format("SizeUnitHeater: Potential issue with equipment sizing for ZoneHVAC:UnitHeater {}",
1041 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name));
1042 0 : ShowContinueError(state,
1043 0 : format("User-Specified Maximum Hot Water Flow of {:.5R} [m3/s]", MaxVolHotWaterFlowUser));
1044 0 : ShowContinueError(
1045 0 : state, format("differs from Design Size Maximum Hot Water Flow of {:.5R} [m3/s]", MaxVolHotWaterFlowDes));
1046 0 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
1047 0 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
1048 : }
1049 : }
1050 : }
1051 : }
1052 : }
1053 : }
1054 : } else {
1055 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotWaterFlow = 0.0;
1056 : }
1057 :
1058 2 : IsAutoSize = false;
1059 2 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotSteamFlow == DataSizing::AutoSize) {
1060 1 : IsAutoSize = true;
1061 : }
1062 :
1063 2 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type == HCoilType::SteamCoil) {
1064 :
1065 0 : if (CurZoneEqNum > 0) {
1066 0 : if (!IsAutoSize && !state.dataSize->ZoneSizingRunDone) { // Simulation continue
1067 0 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotSteamFlow > 0.0) {
1068 0 : BaseSizer::reportSizerOutput(state,
1069 : "ZoneHVAC:UnitHeater",
1070 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name,
1071 : "User-Specified Maximum Steam Flow [m3/s]",
1072 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotSteamFlow);
1073 : }
1074 : } else {
1075 0 : auto &ZoneEqSizing = state.dataSize->ZoneEqSizing(CurZoneEqNum);
1076 0 : CheckZoneSizing(state, "ZoneHVAC:UnitHeater", state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name);
1077 :
1078 0 : int CoilSteamInletNode = SteamCoils::GetCoilSteamInletNode(
1079 0 : state, "Coil:Heating:Steam", state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName, ErrorsFound);
1080 0 : int CoilSteamOutletNode = SteamCoils::GetCoilSteamInletNode(
1081 0 : state, "Coil:Heating:Steam", state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName, ErrorsFound);
1082 0 : if (IsAutoSize) {
1083 0 : PltSizHeatNum = PlantUtilities::MyPlantSizingIndex(state,
1084 : "Coil:Heating:Steam",
1085 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName,
1086 : CoilSteamInletNode,
1087 : CoilSteamOutletNode,
1088 : ErrorsFound);
1089 0 : if (PltSizHeatNum > 0) {
1090 0 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).HVACSizingIndex > 0) {
1091 0 : zoneHVACIndex = state.dataUnitHeaters->UnitHeat(UnitHeatNum).HVACSizingIndex;
1092 0 : int SizingMethod = HVAC::HeatingCapacitySizing;
1093 0 : int CapSizingMethod = state.dataSize->ZoneHVACSizing(zoneHVACIndex).HeatingCapMethod;
1094 0 : ZoneEqSizing.SizingMethod(SizingMethod) = CapSizingMethod;
1095 0 : if (CapSizingMethod == DataSizing::HeatingDesignCapacity || CapSizingMethod == DataSizing::CapacityPerFloorArea ||
1096 : CapSizingMethod == DataSizing::FractionOfAutosizedHeatingCapacity) {
1097 0 : if (CapSizingMethod == DataSizing::HeatingDesignCapacity) {
1098 0 : if (state.dataSize->ZoneHVACSizing(zoneHVACIndex).ScaledHeatingCapacity == DataSizing::AutoSize) {
1099 0 : ZoneEqSizing.DesHeatingLoad = state.dataSize->FinalZoneSizing(CurZoneEqNum).DesHeatLoad;
1100 : } else {
1101 0 : ZoneEqSizing.DesHeatingLoad = state.dataSize->ZoneHVACSizing(zoneHVACIndex).ScaledHeatingCapacity;
1102 : }
1103 0 : ZoneEqSizing.HeatingCapacity = true;
1104 0 : TempSize = DataSizing::AutoSize;
1105 0 : } else if (CapSizingMethod == DataSizing::CapacityPerFloorArea) {
1106 0 : ZoneEqSizing.HeatingCapacity = true;
1107 0 : ZoneEqSizing.DesHeatingLoad = state.dataSize->ZoneHVACSizing(zoneHVACIndex).ScaledHeatingCapacity *
1108 0 : state.dataHeatBal->Zone(state.dataSize->DataZoneNumber).FloorArea;
1109 0 : state.dataSize->DataScalableCapSizingON = true;
1110 0 : } else if (CapSizingMethod == DataSizing::FractionOfAutosizedHeatingCapacity) {
1111 0 : state.dataSize->DataFracOfAutosizedHeatingCapacity =
1112 0 : state.dataSize->ZoneHVACSizing(zoneHVACIndex).ScaledHeatingCapacity;
1113 0 : TempSize = DataSizing::AutoSize;
1114 0 : state.dataSize->DataScalableCapSizingON = true;
1115 : }
1116 : }
1117 0 : PrintFlag = false;
1118 0 : bool errorsFound = false;
1119 0 : HeatingCapacitySizer sizerHeatingCapacity;
1120 0 : sizerHeatingCapacity.overrideSizingString(SizingString);
1121 0 : sizerHeatingCapacity.initializeWithinEP(state, CompType, CompName, PrintFlag, RoutineName);
1122 0 : DesCoilLoad = sizerHeatingCapacity.size(state, TempSize, errorsFound);
1123 0 : state.dataSize->DataScalableCapSizingON = false;
1124 0 : } else {
1125 0 : DesCoilLoad = state.dataSize->FinalZoneSizing(CurZoneEqNum).DesHeatLoad;
1126 : }
1127 0 : if (DesCoilLoad >= HVAC::SmallLoad) {
1128 0 : TempSteamIn = 100.00;
1129 0 : auto *steam = Fluid::GetSteam(state);
1130 0 : EnthSteamInDry = steam->getSatEnthalpy(state, TempSteamIn, 1.0, RoutineName);
1131 0 : EnthSteamOutWet = steam->getSatEnthalpy(state, TempSteamIn, 0.0, RoutineName);
1132 0 : LatentHeatSteam = EnthSteamInDry - EnthSteamOutWet;
1133 0 : SteamDensity = steam->getSatDensity(state, TempSteamIn, 1.0, RoutineName);
1134 0 : MaxVolHotSteamFlowDes =
1135 0 : DesCoilLoad / (SteamDensity * (LatentHeatSteam +
1136 0 : state.dataSize->PlantSizData(PltSizHeatNum).DeltaT *
1137 0 : Psychrometrics::CPHW(state.dataSize->PlantSizData(PltSizHeatNum).ExitTemp)));
1138 : } else {
1139 0 : MaxVolHotSteamFlowDes = 0.0;
1140 : }
1141 : } else {
1142 0 : ShowSevereError(state, "Autosizing of Steam flow requires a heating loop Sizing:Plant object");
1143 0 : ShowContinueError(state,
1144 0 : format("Occurs in ZoneHVAC:UnitHeater Object={}", state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name));
1145 0 : ErrorsFound = true;
1146 : }
1147 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotSteamFlow = MaxVolHotSteamFlowDes;
1148 0 : BaseSizer::reportSizerOutput(state,
1149 : "ZoneHVAC:UnitHeater",
1150 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name,
1151 : "Design Size Maximum Steam Flow [m3/s]",
1152 : MaxVolHotSteamFlowDes);
1153 : } else {
1154 0 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotSteamFlow > 0.0 && MaxVolHotSteamFlowDes > 0.0) {
1155 0 : MaxVolHotSteamFlowUser = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotSteamFlow;
1156 0 : BaseSizer::reportSizerOutput(state,
1157 : "ZoneHVAC:UnitHeater",
1158 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name,
1159 : "Design Size Maximum Steam Flow [m3/s]",
1160 : MaxVolHotSteamFlowDes,
1161 : "User-Specified Maximum Steam Flow [m3/s]",
1162 : MaxVolHotSteamFlowUser);
1163 0 : if (state.dataGlobal->DisplayExtraWarnings) {
1164 0 : if ((std::abs(MaxVolHotSteamFlowDes - MaxVolHotSteamFlowUser) / MaxVolHotSteamFlowUser) >
1165 0 : state.dataSize->AutoVsHardSizingThreshold) {
1166 0 : ShowMessage(state,
1167 0 : format("SizeUnitHeater: Potential issue with equipment sizing for ZoneHVAC:UnitHeater {}",
1168 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name));
1169 0 : ShowContinueError(state, format("User-Specified Maximum Steam Flow of {:.5R} [m3/s]", MaxVolHotSteamFlowUser));
1170 0 : ShowContinueError(state,
1171 0 : format("differs from Design Size Maximum Steam Flow of {:.5R} [m3/s]", MaxVolHotSteamFlowDes));
1172 0 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
1173 0 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
1174 : }
1175 : }
1176 : }
1177 : }
1178 : }
1179 : }
1180 : } else {
1181 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotSteamFlow = 0.0;
1182 : }
1183 :
1184 : // set the design air flow rate for the heating coil
1185 :
1186 2 : WaterCoils::SetCoilDesFlow(state,
1187 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilTypeCh,
1188 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName,
1189 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirVolFlow,
1190 : ErrorsFound);
1191 2 : if (CurZoneEqNum > 0) {
1192 2 : auto &ZoneEqSizing = state.dataSize->ZoneEqSizing(CurZoneEqNum);
1193 2 : ZoneEqSizing.MaxHWVolFlow = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotWaterFlow;
1194 : }
1195 :
1196 2 : if (ErrorsFound) {
1197 0 : ShowFatalError(state, "Preceding sizing errors cause program termination");
1198 : }
1199 2 : }
1200 :
1201 362 : void CalcUnitHeater(EnergyPlusData &state,
1202 : int &UnitHeatNum, // number of the current fan coil unit being simulated
1203 : int const ZoneNum, // number of zone being served
1204 : bool const FirstHVACIteration, // TRUE if 1st HVAC simulation of system timestep
1205 : Real64 &PowerMet, // Sensible power supplied (W)
1206 : Real64 &LatOutputProvided // Latent power supplied (kg/s), negative = dehumidification
1207 : )
1208 : {
1209 :
1210 : // SUBROUTINE INFORMATION:
1211 : // AUTHOR Rick Strand
1212 : // DATE WRITTEN May 2000
1213 : // MODIFIED Don Shirey, Aug 2009 (LatOutputProvided)
1214 : // July 2012, Chandan Sharma - FSEC: Added zone sys avail managers
1215 :
1216 : // PURPOSE OF THIS SUBROUTINE:
1217 : // This subroutine mainly controls the action of the unit heater
1218 : // based on the user input for controls and the defined controls
1219 : // algorithms. There are currently (at the initial creation of this
1220 : // subroutine) two control methods: on-off fan operation or continuous
1221 : // fan operation.
1222 :
1223 : // METHODOLOGY EMPLOYED:
1224 : // Unit is controlled based on user input and what is happening in the
1225 : // simulation. There are various cases to consider:
1226 : // 1. OFF: Unit is schedule off. All flow rates are set to zero and
1227 : // the temperatures are set to zone conditions.
1228 : // 2. NO LOAD OR COOLING/ON-OFF FAN CONTROL: Unit is available, but
1229 : // there is no heating load. All flow rates are set to zero and
1230 : // the temperatures are set to zone conditions.
1231 : // 3. NO LOAD OR COOLING/CONTINUOUS FAN CONTROL: Unit is available and
1232 : // the fan is running (if it is scheduled to be available also).
1233 : // No heating is provided, only circulation via the fan running.
1234 : // 4. HEATING: The unit is on/available and there is a heating load.
1235 : // The heating coil is modulated (constant fan speed) to meet the
1236 : // heating load.
1237 :
1238 : // REFERENCES:
1239 : // ASHRAE Systems and Equipment Handbook (SI), 1996. page 31.7
1240 :
1241 : // SUBROUTINE PARAMETER DEFINITIONS:
1242 362 : int constexpr MaxIter = 100; // maximum number of iterations
1243 :
1244 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1245 : Real64 SpecHumOut; // Specific humidity ratio of outlet air (kg moisture / kg moist air)
1246 : Real64 SpecHumIn; // Specific humidity ratio of inlet air (kg moisture / kg moist air)
1247 : Real64 mdot; // local temporary for fluid mass flow rate
1248 :
1249 : // initialize local variables
1250 362 : Real64 QUnitOut = 0.0;
1251 362 : Real64 NoOutput = 0.0;
1252 362 : Real64 FullOutput = 0.0;
1253 362 : Real64 LatentOutput = 0.0; // Latent (moisture) add/removal rate, negative is dehumidification [kg/s]
1254 362 : Real64 MaxWaterFlow = 0.0;
1255 362 : Real64 MinWaterFlow = 0.0;
1256 362 : Real64 PartLoadFrac = 0.0;
1257 362 : int InletNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode;
1258 362 : int OutletNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirOutNode;
1259 362 : int ControlNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlNode;
1260 362 : Real64 ControlOffset = state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlOffset;
1261 362 : HVAC::FanOp fanOp = state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanOp;
1262 :
1263 362 : if (fanOp != HVAC::FanOp::Cycling) {
1264 :
1265 9 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).availSched->getCurrentVal() <= 0 ||
1266 3 : ((state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanAvailSched->getCurrentVal() <= 0 && !state.dataHVACGlobal->TurnFansOn) ||
1267 3 : state.dataHVACGlobal->TurnFansOff)) {
1268 : // Case 1: OFF-->unit schedule says that it it not available
1269 : // OR child fan in not available OR child fan not being cycled ON by sys avail manager
1270 : // OR child fan being forced OFF by sys avail manager
1271 0 : state.dataUnitHeaters->HCoilOn = false;
1272 0 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type == HCoilType::WaterHeatingCoil) {
1273 0 : mdot = 0.0; // try to turn off
1274 :
1275 0 : PlantUtilities::SetComponentFlowRate(state,
1276 : mdot,
1277 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlNode,
1278 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotCoilOutNodeNum,
1279 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc);
1280 : }
1281 0 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type == HCoilType::SteamCoil) {
1282 0 : mdot = 0.0; // try to turn off
1283 :
1284 0 : PlantUtilities::SetComponentFlowRate(state,
1285 : mdot,
1286 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlNode,
1287 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotCoilOutNodeNum,
1288 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc);
1289 : }
1290 0 : CalcUnitHeaterComponents(state, UnitHeatNum, FirstHVACIteration, QUnitOut);
1291 :
1292 3 : } else if ((state.dataUnitHeaters->QZnReq < HVAC::SmallLoad) || state.dataZoneEnergyDemand->CurDeadBandOrSetback(ZoneNum)) {
1293 : // Unit is available, but there is no load on it or we are in setback/deadband
1294 1 : if (!state.dataUnitHeaters->UnitHeat(UnitHeatNum).FanOffNoHeating) {
1295 :
1296 : // Case 2: NO LOAD OR COOLING/ON-OFF FAN CONTROL-->turn everything off
1297 : // because there is no load on the unit heater
1298 0 : state.dataUnitHeaters->HCoilOn = false;
1299 0 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type == HCoilType::WaterHeatingCoil) {
1300 0 : mdot = 0.0; // try to turn off
1301 :
1302 0 : PlantUtilities::SetComponentFlowRate(state,
1303 : mdot,
1304 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlNode,
1305 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotCoilOutNodeNum,
1306 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc);
1307 : }
1308 0 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type == HCoilType::SteamCoil) {
1309 0 : mdot = 0.0; // try to turn off
1310 :
1311 0 : PlantUtilities::SetComponentFlowRate(state,
1312 : mdot,
1313 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlNode,
1314 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotCoilOutNodeNum,
1315 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc);
1316 : }
1317 0 : CalcUnitHeaterComponents(state, UnitHeatNum, FirstHVACIteration, QUnitOut);
1318 :
1319 : } else {
1320 : // Case 3: NO LOAD OR COOLING/CONTINUOUS FAN CONTROL-->let the fan
1321 : // continue to run even though there is no load (air circulation)
1322 : // Note that the flow rates were already set in the initialization routine
1323 : // so there is really nothing else left to do except call the components.
1324 :
1325 1 : state.dataUnitHeaters->HCoilOn = false;
1326 1 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type == HCoilType::WaterHeatingCoil) {
1327 1 : mdot = 0.0; // try to turn off
1328 :
1329 1 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc.loopNum > 0) {
1330 2 : PlantUtilities::SetComponentFlowRate(state,
1331 : mdot,
1332 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlNode,
1333 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotCoilOutNodeNum,
1334 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc);
1335 : }
1336 : }
1337 1 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type == HCoilType::SteamCoil) {
1338 0 : mdot = 0.0; // try to turn off
1339 0 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc.loopNum > 0) {
1340 0 : PlantUtilities::SetComponentFlowRate(state,
1341 : mdot,
1342 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlNode,
1343 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotCoilOutNodeNum,
1344 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc);
1345 : }
1346 : }
1347 :
1348 1 : CalcUnitHeaterComponents(state, UnitHeatNum, FirstHVACIteration, QUnitOut);
1349 : }
1350 :
1351 : } else { // Case 4: HEATING-->unit is available and there is a heating load
1352 :
1353 2 : switch (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type) {
1354 :
1355 2 : case HCoilType::WaterHeatingCoil: {
1356 :
1357 : // On the first HVAC iteration the system values are given to the controller, but after that
1358 : // the demand limits are in place and there needs to be feedback to the Zone Equipment
1359 2 : if (FirstHVACIteration) {
1360 2 : MaxWaterFlow = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxHotWaterFlow;
1361 2 : MinWaterFlow = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MinHotWaterFlow;
1362 : } else {
1363 0 : MaxWaterFlow = state.dataLoopNodes->Node(ControlNode).MassFlowRateMaxAvail;
1364 0 : MinWaterFlow = state.dataLoopNodes->Node(ControlNode).MassFlowRateMinAvail;
1365 : }
1366 : // control water flow to obtain output matching QZnReq
1367 6 : ControlCompOutput(state,
1368 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name,
1369 2 : state.dataUnitHeaters->cMO_UnitHeater,
1370 : UnitHeatNum,
1371 : FirstHVACIteration,
1372 2 : state.dataUnitHeaters->QZnReq,
1373 : ControlNode,
1374 : MaxWaterFlow,
1375 : MinWaterFlow,
1376 : ControlOffset,
1377 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).ControlCompTypeNum,
1378 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).CompErrIndex,
1379 : _,
1380 : _,
1381 : _,
1382 : _,
1383 : _,
1384 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc);
1385 2 : break;
1386 : }
1387 0 : case HCoilType::Electric:
1388 : case HCoilType::Gas:
1389 : case HCoilType::SteamCoil: {
1390 0 : state.dataUnitHeaters->HCoilOn = true;
1391 0 : CalcUnitHeaterComponents(state, UnitHeatNum, FirstHVACIteration, QUnitOut);
1392 0 : break;
1393 : }
1394 0 : default:
1395 0 : break;
1396 : }
1397 : }
1398 3 : if (state.dataLoopNodes->Node(InletNode).MassFlowRateMax > 0.0) {
1399 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).FanPartLoadRatio =
1400 2 : state.dataLoopNodes->Node(InletNode).MassFlowRate / state.dataLoopNodes->Node(InletNode).MassFlowRateMax;
1401 : }
1402 : } else { // OnOff fan and cycling
1403 361 : if ((state.dataUnitHeaters->QZnReq < HVAC::SmallLoad) || (state.dataZoneEnergyDemand->CurDeadBandOrSetback(ZoneNum)) ||
1404 362 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).availSched->getCurrentVal() <= 0 ||
1405 1 : ((state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanAvailSched->getCurrentVal() <= 0 && !state.dataHVACGlobal->TurnFansOn) ||
1406 1 : state.dataHVACGlobal->TurnFansOff)) {
1407 : // Case 1: OFF-->unit schedule says that it it not available
1408 : // OR child fan in not available OR child fan not being cycled ON by sys avail manager
1409 : // OR child fan being forced OFF by sys avail manager
1410 358 : PartLoadFrac = 0.0;
1411 358 : state.dataUnitHeaters->HCoilOn = false;
1412 358 : CalcUnitHeaterComponents(state, UnitHeatNum, FirstHVACIteration, QUnitOut, fanOp, PartLoadFrac);
1413 :
1414 358 : if (state.dataLoopNodes->Node(InletNode).MassFlowRateMax > 0.0) {
1415 358 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).FanPartLoadRatio =
1416 358 : state.dataLoopNodes->Node(InletNode).MassFlowRate / state.dataLoopNodes->Node(InletNode).MassFlowRateMax;
1417 : }
1418 :
1419 : } else { // Case 4: HEATING-->unit is available and there is a heating load
1420 :
1421 1 : state.dataUnitHeaters->HCoilOn = true;
1422 :
1423 : // Find part load ratio of unit heater coils
1424 1 : PartLoadFrac = 0.0;
1425 1 : CalcUnitHeaterComponents(state, UnitHeatNum, FirstHVACIteration, NoOutput, fanOp, PartLoadFrac);
1426 1 : if ((NoOutput - state.dataUnitHeaters->QZnReq) < HVAC::SmallLoad) {
1427 : // Unit heater is unable to meet the load with coil off, set PLR = 1
1428 1 : PartLoadFrac = 1.0;
1429 1 : CalcUnitHeaterComponents(state, UnitHeatNum, FirstHVACIteration, FullOutput, fanOp, PartLoadFrac);
1430 1 : if ((FullOutput - state.dataUnitHeaters->QZnReq) > HVAC::SmallLoad) {
1431 : // Unit heater full load capacity is able to meet the load, Find PLR
1432 :
1433 0 : auto f = [&state, UnitHeatNum, FirstHVACIteration, fanOp](Real64 const PartLoadRatio) {
1434 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1435 : Real64 QUnitOut; // heating provided by unit heater [watts]
1436 :
1437 0 : CalcUnitHeaterComponents(state, UnitHeatNum, FirstHVACIteration, QUnitOut, fanOp, PartLoadRatio);
1438 :
1439 : // Calculate residual based on output calculation flag
1440 0 : if (state.dataUnitHeaters->QZnReq != 0.0) {
1441 0 : return (QUnitOut - state.dataUnitHeaters->QZnReq) / state.dataUnitHeaters->QZnReq;
1442 : } else
1443 0 : return 0.0;
1444 0 : };
1445 :
1446 : // Tolerance is in fraction of load, MaxIter = 30, SolFalg = # of iterations or error as appropriate
1447 0 : int SolFlag = 0; // # of iterations IF positive, -1 means failed to converge, -2 means bounds are incorrect
1448 0 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadFrac, f, 0.0, 1.0);
1449 : }
1450 : }
1451 :
1452 1 : CalcUnitHeaterComponents(state, UnitHeatNum, FirstHVACIteration, QUnitOut, fanOp, PartLoadFrac);
1453 :
1454 : } // ...end of unit ON/OFF IF-THEN block
1455 359 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).PartLoadFrac = PartLoadFrac;
1456 359 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).FanPartLoadRatio = PartLoadFrac;
1457 359 : state.dataLoopNodes->Node(OutletNode).MassFlowRate = state.dataLoopNodes->Node(InletNode).MassFlowRate;
1458 : }
1459 :
1460 : // CR9155 Remove specific humidity calculations
1461 362 : SpecHumOut = state.dataLoopNodes->Node(OutletNode).HumRat;
1462 362 : SpecHumIn = state.dataLoopNodes->Node(InletNode).HumRat;
1463 362 : LatentOutput = state.dataLoopNodes->Node(OutletNode).MassFlowRate * (SpecHumOut - SpecHumIn); // Latent rate (kg/s), dehumid = negative
1464 :
1465 362 : QUnitOut = state.dataLoopNodes->Node(OutletNode).MassFlowRate *
1466 362 : (Psychrometrics::PsyHFnTdbW(state.dataLoopNodes->Node(OutletNode).Temp, state.dataLoopNodes->Node(InletNode).HumRat) -
1467 362 : Psychrometrics::PsyHFnTdbW(state.dataLoopNodes->Node(InletNode).Temp, state.dataLoopNodes->Node(InletNode).HumRat));
1468 :
1469 : // Report variables...
1470 362 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HeatPower = max(0.0, QUnitOut);
1471 362 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).ElecPower =
1472 362 : state.dataFans->fans(state.dataUnitHeaters->UnitHeat(UnitHeatNum).Fan_Index)->totalPower;
1473 :
1474 362 : PowerMet = QUnitOut;
1475 362 : LatOutputProvided = LatentOutput;
1476 362 : }
1477 :
1478 377 : void CalcUnitHeaterComponents(EnergyPlusData &state,
1479 : int const UnitHeatNum, // Unit index in unit heater array
1480 : bool const FirstHVACIteration, // flag for 1st HVAV iteration in the time step
1481 : Real64 &LoadMet, // load met by unit (watts)
1482 : HVAC::FanOp const fanOp, // fan operating mode
1483 : Real64 const PartLoadRatio // part-load ratio
1484 : )
1485 : {
1486 :
1487 : // SUBROUTINE INFORMATION:
1488 : // AUTHOR Rick Strand
1489 : // DATE WRITTEN May 2000
1490 : // MODIFIED July 2012, Chandan Sharma - FSEC: Added zone sys avail managers
1491 :
1492 : // PURPOSE OF THIS SUBROUTINE:
1493 : // This subroutine launches the individual component simulations.
1494 : // This is called either when the unit is off to carry null conditions
1495 : // through the unit or during control iterations to continue updating
1496 : // what is going on within the unit.
1497 :
1498 : // METHODOLOGY EMPLOYED:
1499 : // Simply calls the different components in order.
1500 :
1501 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1502 : Real64 AirMassFlow; // total mass flow through the unit
1503 : Real64 CpAirZn; // specific heat of dry air at zone conditions (zone conditions same as unit inlet)
1504 : int HCoilInAirNode; // inlet node number for fan exit/coil inlet
1505 : Real64 mdot; // local temporary for fluid mass flow rate
1506 :
1507 377 : int InletNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode;
1508 377 : int OutletNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirOutNode;
1509 377 : Real64 QCoilReq = 0.0;
1510 :
1511 377 : if (fanOp != HVAC::FanOp::Cycling) {
1512 16 : state.dataFans->fans(state.dataUnitHeaters->UnitHeat(UnitHeatNum).Fan_Index)->simulate(state, FirstHVACIteration, _, _);
1513 :
1514 16 : switch (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type) {
1515 :
1516 16 : case HCoilType::WaterHeatingCoil: {
1517 :
1518 32 : WaterCoils::SimulateWaterCoilComponents(state,
1519 16 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName,
1520 : FirstHVACIteration,
1521 16 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoil_Index);
1522 16 : break;
1523 : }
1524 0 : case HCoilType::SteamCoil: {
1525 :
1526 0 : if (!state.dataUnitHeaters->HCoilOn) {
1527 0 : QCoilReq = 0.0;
1528 : } else {
1529 0 : HCoilInAirNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).FanOutletNode;
1530 0 : CpAirZn = Psychrometrics::PsyCpAirFnW(state.dataLoopNodes->Node(state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode).HumRat);
1531 0 : QCoilReq =
1532 0 : state.dataUnitHeaters->QZnReq - state.dataLoopNodes->Node(HCoilInAirNode).MassFlowRate * CpAirZn *
1533 0 : (state.dataLoopNodes->Node(HCoilInAirNode).Temp -
1534 0 : state.dataLoopNodes->Node(state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode).Temp);
1535 : }
1536 0 : if (QCoilReq < 0.0) QCoilReq = 0.0; // a heating coil can only heat, not cool
1537 0 : SteamCoils::SimulateSteamCoilComponents(state,
1538 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName,
1539 : FirstHVACIteration,
1540 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoil_Index,
1541 : QCoilReq);
1542 0 : break;
1543 : }
1544 0 : case HCoilType::Electric:
1545 : case HCoilType::Gas: {
1546 :
1547 0 : if (!state.dataUnitHeaters->HCoilOn) {
1548 0 : QCoilReq = 0.0;
1549 : } else {
1550 0 : HCoilInAirNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).FanOutletNode;
1551 0 : CpAirZn = Psychrometrics::PsyCpAirFnW(state.dataLoopNodes->Node(state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode).HumRat);
1552 0 : QCoilReq =
1553 0 : state.dataUnitHeaters->QZnReq - state.dataLoopNodes->Node(HCoilInAirNode).MassFlowRate * CpAirZn *
1554 0 : (state.dataLoopNodes->Node(HCoilInAirNode).Temp -
1555 0 : state.dataLoopNodes->Node(state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode).Temp);
1556 : }
1557 0 : if (QCoilReq < 0.0) QCoilReq = 0.0; // a heating coil can only heat, not cool
1558 0 : HeatingCoils::SimulateHeatingCoilComponents(state,
1559 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName,
1560 : FirstHVACIteration,
1561 : QCoilReq,
1562 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoil_Index);
1563 0 : break;
1564 : }
1565 0 : default:
1566 0 : break;
1567 : }
1568 :
1569 16 : AirMassFlow = state.dataLoopNodes->Node(OutletNode).MassFlowRate;
1570 :
1571 16 : state.dataLoopNodes->Node(InletNode).MassFlowRate =
1572 16 : state.dataLoopNodes->Node(OutletNode).MassFlowRate; // maintain continuity through unit heater
1573 :
1574 : } else { // OnOff fan cycling
1575 :
1576 361 : state.dataLoopNodes->Node(InletNode).MassFlowRate = state.dataLoopNodes->Node(InletNode).MassFlowRateMax * PartLoadRatio;
1577 361 : AirMassFlow = state.dataLoopNodes->Node(InletNode).MassFlowRate;
1578 : // Set the fan inlet node maximum available mass flow rates for cycling fans
1579 361 : state.dataLoopNodes->Node(InletNode).MassFlowRateMaxAvail = AirMassFlow;
1580 :
1581 361 : if (QCoilReq < 0.0) QCoilReq = 0.0; // a heating coil can only heat, not cool
1582 361 : state.dataFans->fans(state.dataUnitHeaters->UnitHeat(UnitHeatNum).Fan_Index)->simulate(state, FirstHVACIteration, _, _);
1583 :
1584 361 : switch (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type) {
1585 :
1586 0 : case HCoilType::WaterHeatingCoil: {
1587 :
1588 0 : if (!state.dataUnitHeaters->HCoilOn) {
1589 0 : mdot = 0.0;
1590 0 : QCoilReq = 0.0;
1591 : } else {
1592 0 : HCoilInAirNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).FanOutletNode;
1593 0 : CpAirZn = Psychrometrics::PsyCpAirFnW(state.dataLoopNodes->Node(state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode).HumRat);
1594 0 : QCoilReq =
1595 0 : state.dataUnitHeaters->QZnReq - state.dataLoopNodes->Node(HCoilInAirNode).MassFlowRate * CpAirZn *
1596 0 : (state.dataLoopNodes->Node(HCoilInAirNode).Temp -
1597 0 : state.dataLoopNodes->Node(state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode).Temp);
1598 0 : mdot = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxHotWaterFlow * PartLoadRatio;
1599 : }
1600 0 : if (QCoilReq < 0.0) QCoilReq = 0.0; // a heating coil can only heat, not cool
1601 0 : PlantUtilities::SetComponentFlowRate(state,
1602 : mdot,
1603 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlNode,
1604 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotCoilOutNodeNum,
1605 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc);
1606 0 : WaterCoils::SimulateWaterCoilComponents(state,
1607 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName,
1608 : FirstHVACIteration,
1609 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoil_Index,
1610 : QCoilReq,
1611 : fanOp,
1612 : PartLoadRatio);
1613 0 : break;
1614 : }
1615 0 : case HCoilType::SteamCoil: {
1616 0 : if (!state.dataUnitHeaters->HCoilOn) {
1617 0 : mdot = 0.0;
1618 0 : QCoilReq = 0.0;
1619 : } else {
1620 0 : HCoilInAirNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).FanOutletNode;
1621 0 : CpAirZn = Psychrometrics::PsyCpAirFnW(state.dataLoopNodes->Node(state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode).HumRat);
1622 0 : QCoilReq =
1623 0 : state.dataUnitHeaters->QZnReq - state.dataLoopNodes->Node(HCoilInAirNode).MassFlowRate * CpAirZn *
1624 0 : (state.dataLoopNodes->Node(HCoilInAirNode).Temp -
1625 0 : state.dataLoopNodes->Node(state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode).Temp);
1626 0 : mdot = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxHotSteamFlow * PartLoadRatio;
1627 : }
1628 0 : if (QCoilReq < 0.0) QCoilReq = 0.0; // a heating coil can only heat, not cool
1629 0 : PlantUtilities::SetComponentFlowRate(state,
1630 : mdot,
1631 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlNode,
1632 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotCoilOutNodeNum,
1633 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc);
1634 0 : SteamCoils::SimulateSteamCoilComponents(state,
1635 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName,
1636 : FirstHVACIteration,
1637 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoil_Index,
1638 : QCoilReq,
1639 : _,
1640 : fanOp,
1641 : PartLoadRatio);
1642 0 : break;
1643 : }
1644 361 : case HCoilType::Electric:
1645 : case HCoilType::Gas: {
1646 :
1647 361 : if (!state.dataUnitHeaters->HCoilOn) {
1648 358 : QCoilReq = 0.0;
1649 : } else {
1650 3 : HCoilInAirNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).FanOutletNode;
1651 3 : CpAirZn = Psychrometrics::PsyCpAirFnW(state.dataLoopNodes->Node(state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode).HumRat);
1652 3 : QCoilReq =
1653 3 : state.dataUnitHeaters->QZnReq - state.dataLoopNodes->Node(HCoilInAirNode).MassFlowRate * CpAirZn *
1654 3 : (state.dataLoopNodes->Node(HCoilInAirNode).Temp -
1655 3 : state.dataLoopNodes->Node(state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode).Temp);
1656 : }
1657 361 : if (QCoilReq < 0.0) QCoilReq = 0.0; // a heating coil can only heat, not cool
1658 1083 : HeatingCoils::SimulateHeatingCoilComponents(state,
1659 361 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName,
1660 : FirstHVACIteration,
1661 : QCoilReq,
1662 361 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoil_Index,
1663 : _,
1664 : _,
1665 : fanOp,
1666 : PartLoadRatio);
1667 361 : break;
1668 : }
1669 0 : default:
1670 0 : break;
1671 : }
1672 361 : state.dataLoopNodes->Node(OutletNode).MassFlowRate =
1673 361 : state.dataLoopNodes->Node(InletNode).MassFlowRate; // maintain continuity through unit heater
1674 : }
1675 377 : LoadMet = AirMassFlow * (Psychrometrics::PsyHFnTdbW(state.dataLoopNodes->Node(OutletNode).Temp, state.dataLoopNodes->Node(InletNode).HumRat) -
1676 377 : Psychrometrics::PsyHFnTdbW(state.dataLoopNodes->Node(InletNode).Temp, state.dataLoopNodes->Node(InletNode).HumRat));
1677 377 : }
1678 :
1679 : // SUBROUTINE UpdateUnitHeater
1680 :
1681 : // No update routine needed in this module since all of the updates happen on
1682 : // the Node derived type directly and these updates are done by other routines.
1683 :
1684 : // END SUBROUTINE UpdateUnitHeater
1685 :
1686 362 : void ReportUnitHeater(EnergyPlusData &state, int const UnitHeatNum) // Unit index in unit heater array
1687 : {
1688 :
1689 : // SUBROUTINE INFORMATION:
1690 : // AUTHOR Rick Strand
1691 : // DATE WRITTEN May 2000
1692 :
1693 : // PURPOSE OF THIS SUBROUTINE:
1694 : // This subroutine needs a description.
1695 :
1696 : // METHODOLOGY EMPLOYED:
1697 : // Needs description, as appropriate.
1698 :
1699 : // Using/Aliasing
1700 362 : Real64 TimeStepSysSec = state.dataHVACGlobal->TimeStepSysSec;
1701 :
1702 362 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HeatEnergy = state.dataUnitHeaters->UnitHeat(UnitHeatNum).HeatPower * TimeStepSysSec;
1703 362 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).ElecEnergy = state.dataUnitHeaters->UnitHeat(UnitHeatNum).ElecPower * TimeStepSysSec;
1704 :
1705 362 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).FirstPass) { // reset sizing flags so other zone equipment can size normally
1706 4 : if (!state.dataGlobal->SysSizingCalc) {
1707 1 : DataSizing::resetHVACSizingGlobals(state, state.dataSize->CurZoneEqNum, 0, state.dataUnitHeaters->UnitHeat(UnitHeatNum).FirstPass);
1708 : }
1709 : }
1710 362 : }
1711 :
1712 1 : int getUnitHeaterIndex(EnergyPlusData &state, std::string_view CompName)
1713 : {
1714 1 : if (state.dataUnitHeaters->GetUnitHeaterInputFlag) {
1715 0 : GetUnitHeaterInput(state);
1716 0 : state.dataUnitHeaters->GetUnitHeaterInputFlag = false;
1717 : }
1718 :
1719 1 : for (int UnitHeatNum = 1; UnitHeatNum <= state.dataUnitHeaters->NumOfUnitHeats; ++UnitHeatNum) {
1720 1 : if (Util::SameString(state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name, CompName)) {
1721 1 : return UnitHeatNum;
1722 : }
1723 : }
1724 :
1725 0 : return 0;
1726 : }
1727 :
1728 : } // namespace UnitHeater
1729 :
1730 : } // namespace EnergyPlus
|