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) {
445 0 : continue;
446 : }
447 4 : for (NodeNum = 1; NodeNum <= state.dataZoneEquip->ZoneEquipConfig(CtrlZone).NumExhaustNodes; ++NodeNum) {
448 4 : if (unitHeat.AirInNode == state.dataZoneEquip->ZoneEquipConfig(CtrlZone).ExhaustNode(NodeNum)) {
449 4 : ZoneNodeNotFound = false;
450 4 : break;
451 : }
452 : }
453 : }
454 4 : if (ZoneNodeNotFound) {
455 0 : ShowSevereError(state,
456 0 : format("{} = \"{}\". Unit heater air inlet node name must be the same as a zone exhaust node name.",
457 : CurrentModuleObject,
458 0 : unitHeat.Name));
459 0 : ShowContinueError(state, "..Zone exhaust node name is specified in ZoneHVAC:EquipmentConnections object.");
460 0 : ShowContinueError(state, format("..Unit heater air inlet node name = {}", state.dataLoopNodes->NodeID(unitHeat.AirInNode)));
461 0 : ErrorsFound = true;
462 : }
463 : // check that unit heater air outlet node is a zone inlet node.
464 4 : ZoneNodeNotFound = true;
465 8 : for (CtrlZone = 1; CtrlZone <= state.dataGlobal->NumOfZones; ++CtrlZone) {
466 4 : if (!state.dataZoneEquip->ZoneEquipConfig(CtrlZone).IsControlled) {
467 0 : continue;
468 : }
469 5 : for (NodeNum = 1; NodeNum <= state.dataZoneEquip->ZoneEquipConfig(CtrlZone).NumInletNodes; ++NodeNum) {
470 5 : if (unitHeat.AirOutNode == state.dataZoneEquip->ZoneEquipConfig(CtrlZone).InletNode(NodeNum)) {
471 4 : unitHeat.ZonePtr = CtrlZone;
472 4 : ZoneNodeNotFound = false;
473 4 : break;
474 : }
475 : }
476 : }
477 4 : if (ZoneNodeNotFound) {
478 0 : ShowSevereError(state,
479 0 : format("{} = \"{}\". Unit heater air outlet node name must be the same as a zone inlet node name.",
480 : CurrentModuleObject,
481 0 : unitHeat.Name));
482 0 : ShowContinueError(state, "..Zone inlet node name is specified in ZoneHVAC:EquipmentConnections object.");
483 0 : ShowContinueError(state, format("..Unit heater air outlet node name = {}", state.dataLoopNodes->NodeID(unitHeat.AirOutNode)));
484 0 : ErrorsFound = true;
485 : }
486 :
487 : // Add fan to component sets array
488 12 : BranchNodeConnections::SetUpCompSets(state,
489 : CurrentModuleObject,
490 : unitHeat.Name,
491 4 : HVAC::fanTypeNamesUC[(int)unitHeat.fanType],
492 : unitHeat.FanName,
493 4 : state.dataLoopNodes->NodeID(unitHeat.AirInNode),
494 4 : state.dataLoopNodes->NodeID(unitHeat.FanOutletNode));
495 :
496 : // Add heating coil to component sets array
497 8 : BranchNodeConnections::SetUpCompSets(state,
498 : CurrentModuleObject,
499 : unitHeat.Name,
500 : unitHeat.HCoilTypeCh,
501 : unitHeat.HCoilName,
502 4 : state.dataLoopNodes->NodeID(unitHeat.FanOutletNode),
503 4 : state.dataLoopNodes->NodeID(unitHeat.AirOutNode));
504 :
505 : } // ...loop over all of the unit heaters found in the input file
506 :
507 4 : Alphas.deallocate();
508 4 : Numbers.deallocate();
509 4 : cAlphaFields.deallocate();
510 4 : cNumericFields.deallocate();
511 4 : lAlphaBlanks.deallocate();
512 4 : lNumericBlanks.deallocate();
513 :
514 4 : if (ErrorsFound) {
515 0 : ShowFatalError(state, format("{}Errors found in input", RoutineName));
516 : }
517 :
518 : // Setup Report variables for the Unit Heaters, CurrentModuleObject='ZoneHVAC:UnitHeater'
519 8 : for (int UnitHeatNum = 1; UnitHeatNum <= state.dataUnitHeaters->NumOfUnitHeats; ++UnitHeatNum) {
520 4 : auto &unitHeat = state.dataUnitHeaters->UnitHeat(UnitHeatNum);
521 8 : SetupOutputVariable(state,
522 : "Zone Unit Heater Heating Rate",
523 : Constant::Units::W,
524 4 : unitHeat.HeatPower,
525 : OutputProcessor::TimeStepType::System,
526 : OutputProcessor::StoreType::Average,
527 4 : unitHeat.Name);
528 8 : SetupOutputVariable(state,
529 : "Zone Unit Heater Heating Energy",
530 : Constant::Units::J,
531 4 : unitHeat.HeatEnergy,
532 : OutputProcessor::TimeStepType::System,
533 : OutputProcessor::StoreType::Sum,
534 4 : unitHeat.Name);
535 8 : SetupOutputVariable(state,
536 : "Zone Unit Heater Fan Electricity Rate",
537 : Constant::Units::W,
538 4 : unitHeat.ElecPower,
539 : OutputProcessor::TimeStepType::System,
540 : OutputProcessor::StoreType::Average,
541 4 : unitHeat.Name);
542 : // Note that the unit heater fan electric is NOT metered because this value is already metered through the fan component
543 8 : SetupOutputVariable(state,
544 : "Zone Unit Heater Fan Electricity Energy",
545 : Constant::Units::J,
546 4 : unitHeat.ElecEnergy,
547 : OutputProcessor::TimeStepType::System,
548 : OutputProcessor::StoreType::Sum,
549 4 : unitHeat.Name);
550 4 : SetupOutputVariable(state,
551 : "Zone Unit Heater Fan Availability Status",
552 : Constant::Units::None,
553 4 : (int &)unitHeat.availStatus,
554 : OutputProcessor::TimeStepType::System,
555 : OutputProcessor::StoreType::Average,
556 4 : unitHeat.Name);
557 4 : if (unitHeat.fanType == HVAC::FanType::OnOff) {
558 2 : SetupOutputVariable(state,
559 : "Zone Unit Heater Fan Part Load Ratio",
560 : Constant::Units::None,
561 1 : unitHeat.FanPartLoadRatio,
562 : OutputProcessor::TimeStepType::System,
563 : OutputProcessor::StoreType::Average,
564 1 : unitHeat.Name);
565 : }
566 4 : state.dataRptCoilSelection->coilSelectionReportObj->setCoilSupplyFanInfo(
567 4 : state, unitHeat.HCoilName, unitHeat.HCoilTypeCh, unitHeat.FanName, unitHeat.fanType, unitHeat.Fan_Index);
568 : }
569 4 : }
570 :
571 363 : void InitUnitHeater(EnergyPlusData &state,
572 : int const UnitHeatNum, // index for the current unit heater
573 : int const ZoneNum, // number of zone being served
574 : [[maybe_unused]] bool const FirstHVACIteration // TRUE if 1st HVAC simulation of system timestep
575 : )
576 : {
577 :
578 : // SUBROUTINE INFORMATION:
579 : // AUTHOR Rick Strand
580 : // DATE WRITTEN May 2000
581 : // MODIFIED Chandan Sharma, FSEC, March 2011: Added ZoneHVAC sys avail manager
582 :
583 : // PURPOSE OF THIS SUBROUTINE:
584 : // This subroutine initializes all of the data elements which are necessary
585 : // to simulate a unit heater.
586 :
587 : // METHODOLOGY EMPLOYED:
588 : // Uses the status flags to trigger initializations.
589 :
590 : // SUBROUTINE PARAMETER DEFINITIONS:
591 : static constexpr std::string_view RoutineName("InitUnitHeater");
592 :
593 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
594 : int InNode; // inlet node number in unit heater loop
595 : int OutNode; // outlet node number in unit heater loop
596 : Real64 RhoAir; // air density at InNode
597 : Real64 TempSteamIn;
598 : Real64 SteamDensity;
599 : Real64 rho; // local fluid density
600 :
601 : // Do the one time initializations
602 363 : if (state.dataUnitHeaters->InitUnitHeaterOneTimeFlag) {
603 :
604 3 : state.dataUnitHeaters->MyEnvrnFlag.allocate(state.dataUnitHeaters->NumOfUnitHeats);
605 3 : state.dataUnitHeaters->MySizeFlag.allocate(state.dataUnitHeaters->NumOfUnitHeats);
606 3 : state.dataUnitHeaters->MyPlantScanFlag.allocate(state.dataUnitHeaters->NumOfUnitHeats);
607 3 : state.dataUnitHeaters->MyZoneEqFlag.allocate(state.dataUnitHeaters->NumOfUnitHeats);
608 3 : state.dataUnitHeaters->MyEnvrnFlag = true;
609 3 : state.dataUnitHeaters->MySizeFlag = true;
610 3 : state.dataUnitHeaters->MyPlantScanFlag = true;
611 3 : state.dataUnitHeaters->MyZoneEqFlag = true;
612 3 : state.dataUnitHeaters->InitUnitHeaterOneTimeFlag = false;
613 : }
614 :
615 363 : if (allocated(state.dataAvail->ZoneComp)) {
616 361 : auto &availMgr = state.dataAvail->ZoneComp(DataZoneEquipment::ZoneEquipType::UnitHeater).ZoneCompAvailMgrs(UnitHeatNum);
617 361 : if (state.dataUnitHeaters->MyZoneEqFlag(UnitHeatNum)) { // initialize the name of each availability manager list and zone number
618 2 : availMgr.AvailManagerListName = state.dataUnitHeaters->UnitHeat(UnitHeatNum).AvailManagerListName;
619 2 : availMgr.ZoneNum = ZoneNum;
620 2 : state.dataUnitHeaters->MyZoneEqFlag(UnitHeatNum) = false;
621 : }
622 361 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).availStatus = availMgr.availStatus;
623 : }
624 :
625 363 : if (state.dataUnitHeaters->MyPlantScanFlag(UnitHeatNum) && allocated(state.dataPlnt->PlantLoop)) {
626 4 : if ((state.dataUnitHeaters->UnitHeat(UnitHeatNum).HeatingCoilType == DataPlant::PlantEquipmentType::CoilWaterSimpleHeating) ||
627 1 : (state.dataUnitHeaters->UnitHeat(UnitHeatNum).HeatingCoilType == DataPlant::PlantEquipmentType::CoilSteamAirHeating)) {
628 2 : bool errFlag = false;
629 4 : PlantUtilities::ScanPlantLoopsForObject(state,
630 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName,
631 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HeatingCoilType,
632 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc,
633 : errFlag,
634 : _,
635 : _,
636 : _,
637 : _,
638 : _);
639 2 : if (errFlag) {
640 0 : ShowContinueError(state,
641 0 : format("Reference Unit=\"{}\", type=ZoneHVAC:UnitHeater", state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name));
642 0 : ShowFatalError(state, "InitUnitHeater: Program terminated due to previous condition(s).");
643 : }
644 :
645 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotCoilOutNodeNum =
646 2 : DataPlant::CompData::getPlantComponent(state, state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc).NodeNumOut;
647 : }
648 3 : state.dataUnitHeaters->MyPlantScanFlag(UnitHeatNum) = false;
649 360 : } else if (state.dataUnitHeaters->MyPlantScanFlag(UnitHeatNum) && !state.dataGlobal->AnyPlantInModel) {
650 0 : state.dataUnitHeaters->MyPlantScanFlag(UnitHeatNum) = false;
651 : }
652 : // need to check all units to see if they are on Zone Equipment List or issue warning
653 363 : if (!state.dataUnitHeaters->ZoneEquipmentListChecked && state.dataZoneEquip->ZoneEquipInputsFilled) {
654 2 : state.dataUnitHeaters->ZoneEquipmentListChecked = true;
655 4 : for (int Loop = 1; Loop <= state.dataUnitHeaters->NumOfUnitHeats; ++Loop) {
656 2 : if (DataZoneEquipment::CheckZoneEquipmentList(state, "ZoneHVAC:UnitHeater", state.dataUnitHeaters->UnitHeat(Loop).Name)) {
657 2 : continue;
658 : }
659 0 : ShowSevereError(state,
660 0 : format("InitUnitHeater: Unit=[UNIT HEATER,{}] is not on any ZoneHVAC:EquipmentList. It will not be simulated.",
661 0 : state.dataUnitHeaters->UnitHeat(Loop).Name));
662 : }
663 : }
664 :
665 365 : if (!state.dataGlobal->SysSizingCalc && state.dataUnitHeaters->MySizeFlag(UnitHeatNum) &&
666 2 : !state.dataUnitHeaters->MyPlantScanFlag(UnitHeatNum)) {
667 :
668 2 : SizeUnitHeater(state, UnitHeatNum);
669 :
670 2 : state.dataUnitHeaters->MySizeFlag(UnitHeatNum) = false;
671 : } // Do the one time initializations
672 :
673 366 : if (state.dataGlobal->BeginEnvrnFlag && state.dataUnitHeaters->MyEnvrnFlag(UnitHeatNum) &&
674 3 : !state.dataUnitHeaters->MyPlantScanFlag(UnitHeatNum)) {
675 3 : InNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode;
676 3 : OutNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirOutNode;
677 3 : RhoAir = state.dataEnvrn->StdRhoAir;
678 :
679 : // set the mass flow rates from the input volume flow rates
680 3 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirMassFlow = RhoAir * state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirVolFlow;
681 :
682 : // set the node max and min mass flow rates
683 3 : state.dataLoopNodes->Node(OutNode).MassFlowRateMax = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirMassFlow;
684 3 : state.dataLoopNodes->Node(OutNode).MassFlowRateMin = 0.0;
685 :
686 3 : state.dataLoopNodes->Node(InNode).MassFlowRateMax = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirMassFlow;
687 3 : state.dataLoopNodes->Node(InNode).MassFlowRateMin = 0.0;
688 :
689 3 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type == HCoilType::WaterHeatingCoil) {
690 1 : rho = state.dataPlnt->PlantLoop(state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc.loopNum)
691 1 : .glycol->getDensity(state, Constant::HWInitConvTemp, RoutineName);
692 :
693 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxHotWaterFlow = rho * state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotWaterFlow;
694 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MinHotWaterFlow = rho * state.dataUnitHeaters->UnitHeat(UnitHeatNum).MinVolHotWaterFlow;
695 4 : PlantUtilities::InitComponentNodes(state,
696 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MinHotWaterFlow,
697 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxHotWaterFlow,
698 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlNode,
699 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotCoilOutNodeNum);
700 : }
701 3 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type == HCoilType::SteamCoil) {
702 0 : TempSteamIn = 100.00;
703 0 : SteamDensity = state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoil_fluid->getSatDensity(state, TempSteamIn, 1.0, RoutineName);
704 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxHotSteamFlow =
705 0 : SteamDensity * state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotSteamFlow;
706 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MinHotSteamFlow =
707 0 : SteamDensity * state.dataUnitHeaters->UnitHeat(UnitHeatNum).MinVolHotSteamFlow;
708 :
709 0 : PlantUtilities::InitComponentNodes(state,
710 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MinHotSteamFlow,
711 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxHotSteamFlow,
712 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlNode,
713 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotCoilOutNodeNum);
714 : }
715 :
716 3 : state.dataUnitHeaters->MyEnvrnFlag(UnitHeatNum) = false;
717 : } // ...end start of environment inits
718 :
719 363 : if (!state.dataGlobal->BeginEnvrnFlag) {
720 341 : state.dataUnitHeaters->MyEnvrnFlag(UnitHeatNum) = true;
721 : }
722 :
723 : // These initializations are done every iteration...
724 363 : InNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode;
725 363 : OutNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirOutNode;
726 :
727 363 : state.dataUnitHeaters->QZnReq = state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneNum).RemainingOutputReqToHeatSP; // zone load needed
728 363 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanOpModeSched != nullptr) {
729 0 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanOpModeSched->getCurrentVal() == 0.0 &&
730 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanType == HVAC::FanType::OnOff) {
731 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanOp = HVAC::FanOp::Cycling;
732 : } else {
733 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanOp = HVAC::FanOp::Continuous;
734 : }
735 0 : if ((state.dataUnitHeaters->QZnReq < HVAC::SmallLoad) || state.dataZoneEnergyDemand->CurDeadBandOrSetback(ZoneNum)) {
736 : // Unit is available, but there is no load on it or we are in setback/deadband
737 0 : if (!state.dataUnitHeaters->UnitHeat(UnitHeatNum).FanOffNoHeating &&
738 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanOpModeSched->getCurrentVal() > 0.0) {
739 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanOp = HVAC::FanOp::Continuous;
740 : }
741 : }
742 : }
743 :
744 363 : state.dataUnitHeaters->SetMassFlowRateToZero = false;
745 363 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).availSched->getCurrentVal() > 0) {
746 726 : if ((state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanAvailSched->getCurrentVal() > 0 || state.dataHVACGlobal->TurnFansOn) &&
747 363 : !state.dataHVACGlobal->TurnFansOff) {
748 726 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).FanOffNoHeating &&
749 363 : ((state.dataZoneEnergyDemand->ZoneSysEnergyDemand(ZoneNum).RemainingOutputReqToHeatSP < HVAC::SmallLoad) ||
750 3 : (state.dataZoneEnergyDemand->CurDeadBandOrSetback(ZoneNum)))) {
751 360 : state.dataUnitHeaters->SetMassFlowRateToZero = true;
752 : }
753 : } else {
754 0 : state.dataUnitHeaters->SetMassFlowRateToZero = true;
755 : }
756 : } else {
757 0 : state.dataUnitHeaters->SetMassFlowRateToZero = true;
758 : }
759 :
760 363 : if (state.dataUnitHeaters->SetMassFlowRateToZero) {
761 360 : state.dataLoopNodes->Node(InNode).MassFlowRate = 0.0;
762 360 : state.dataLoopNodes->Node(InNode).MassFlowRateMaxAvail = 0.0;
763 360 : state.dataLoopNodes->Node(InNode).MassFlowRateMinAvail = 0.0;
764 360 : state.dataLoopNodes->Node(OutNode).MassFlowRate = 0.0;
765 360 : state.dataLoopNodes->Node(OutNode).MassFlowRateMaxAvail = 0.0;
766 360 : state.dataLoopNodes->Node(OutNode).MassFlowRateMinAvail = 0.0;
767 : } else {
768 3 : state.dataLoopNodes->Node(InNode).MassFlowRate = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirMassFlow;
769 3 : state.dataLoopNodes->Node(InNode).MassFlowRateMaxAvail = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirMassFlow;
770 3 : state.dataLoopNodes->Node(InNode).MassFlowRateMinAvail = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirMassFlow;
771 3 : state.dataLoopNodes->Node(OutNode).MassFlowRate = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirMassFlow;
772 3 : state.dataLoopNodes->Node(OutNode).MassFlowRateMaxAvail = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirMassFlow;
773 3 : state.dataLoopNodes->Node(OutNode).MassFlowRateMinAvail = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirMassFlow;
774 : }
775 :
776 : // Just in case the unit is off and conditions do not get sent through
777 : // the unit for some reason, set the outlet conditions equal to the inlet
778 : // conditions of the unit heater
779 363 : state.dataLoopNodes->Node(OutNode).Temp = state.dataLoopNodes->Node(InNode).Temp;
780 363 : state.dataLoopNodes->Node(OutNode).Press = state.dataLoopNodes->Node(InNode).Press;
781 363 : state.dataLoopNodes->Node(OutNode).HumRat = state.dataLoopNodes->Node(InNode).HumRat;
782 363 : state.dataLoopNodes->Node(OutNode).Enthalpy = state.dataLoopNodes->Node(InNode).Enthalpy;
783 363 : }
784 :
785 2 : void SizeUnitHeater(EnergyPlusData &state, int const UnitHeatNum)
786 : {
787 :
788 : // SUBROUTINE INFORMATION:
789 : // AUTHOR Fred Buhl
790 : // DATE WRITTEN February 2002
791 : // MODIFIED August 2013 Daeho Kang, add component sizing table entries
792 : // July 2014, B. Nigusse, added scalable sizing
793 : // RE-ENGINEERED na
794 :
795 : // PURPOSE OF THIS SUBROUTINE:
796 : // This subroutine is for sizing Unit Heater components for which flow rates have not been
797 : // specified in the input.
798 :
799 : // METHODOLOGY EMPLOYED:
800 : // Obtains flow rates from the zone sizing arrays and plant sizing data.
801 :
802 : // SUBROUTINE PARAMETER DEFINITIONS:
803 : static constexpr std::string_view RoutineName("SizeUnitHeater");
804 :
805 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
806 : int PltSizHeatNum; // index of plant sizing object for 1st heating loop
807 : Real64 DesCoilLoad;
808 : Real64 TempSteamIn;
809 : Real64 EnthSteamInDry;
810 : Real64 EnthSteamOutWet;
811 : Real64 LatentHeatSteam;
812 : Real64 SteamDensity;
813 : Real64 Cp; // local temporary for fluid specific heat
814 : Real64 rho; // local temporary for fluid density
815 2 : std::string SizingString; // input field sizing description (e.g., Nominal Capacity)
816 : Real64 TempSize; // autosized value of coil input field
817 : bool PrintFlag; // TRUE when sizing information is reported in the eio file
818 : int zoneHVACIndex; // index of zoneHVAC equipment sizing specification
819 : Real64 WaterCoilSizDeltaT; // water coil deltaT for design water flow rate autosizing
820 :
821 2 : int &CurZoneEqNum = state.dataSize->CurZoneEqNum;
822 :
823 2 : bool ErrorsFound = false;
824 2 : Real64 MaxAirVolFlowDes = 0.0;
825 2 : Real64 MaxAirVolFlowUser = 0.0;
826 2 : Real64 MaxVolHotWaterFlowDes = 0.0;
827 2 : Real64 MaxVolHotWaterFlowUser = 0.0;
828 2 : Real64 MaxVolHotSteamFlowDes = 0.0;
829 2 : Real64 MaxVolHotSteamFlowUser = 0.0;
830 :
831 2 : state.dataSize->DataScalableSizingON = false;
832 2 : state.dataSize->DataScalableCapSizingON = false;
833 2 : state.dataSize->ZoneHeatingOnlyFan = true;
834 2 : std::string CompType = "ZoneHVAC:UnitHeater";
835 2 : std::string CompName = state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name;
836 2 : state.dataSize->DataZoneNumber = state.dataUnitHeaters->UnitHeat(UnitHeatNum).ZonePtr;
837 2 : state.dataSize->DataFanType = state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanType;
838 2 : state.dataSize->DataFanIndex = state.dataUnitHeaters->UnitHeat(UnitHeatNum).Fan_Index;
839 : // unit heater is always blow thru
840 2 : state.dataSize->DataFanPlacement = HVAC::FanPlace::BlowThru;
841 :
842 2 : if (CurZoneEqNum > 0) {
843 2 : auto &ZoneEqSizing = state.dataSize->ZoneEqSizing(CurZoneEqNum);
844 2 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).HVACSizingIndex > 0) {
845 0 : zoneHVACIndex = state.dataUnitHeaters->UnitHeat(UnitHeatNum).HVACSizingIndex;
846 0 : int SizingMethod = HVAC::HeatingAirflowSizing;
847 0 : int FieldNum = 1; // N1 , \field Maximum Supply Air Flow Rate
848 0 : PrintFlag = true;
849 0 : SizingString = state.dataUnitHeaters->UnitHeatNumericFields(UnitHeatNum).FieldNames(FieldNum) + " [m3/s]";
850 0 : int SAFMethod = state.dataSize->ZoneHVACSizing(zoneHVACIndex).HeatingSAFMethod;
851 0 : ZoneEqSizing.SizingMethod(SizingMethod) = SAFMethod;
852 0 : if (SAFMethod == DataSizing::None || SAFMethod == DataSizing::SupplyAirFlowRate || SAFMethod == DataSizing::FlowPerFloorArea ||
853 : SAFMethod == DataSizing::FractionOfAutosizedHeatingAirflow) {
854 0 : if (SAFMethod == DataSizing::SupplyAirFlowRate) {
855 0 : if (state.dataSize->ZoneHVACSizing(zoneHVACIndex).MaxHeatAirVolFlow > 0.0) {
856 0 : ZoneEqSizing.AirVolFlow = state.dataSize->ZoneHVACSizing(zoneHVACIndex).MaxHeatAirVolFlow;
857 0 : ZoneEqSizing.SystemAirFlow = true;
858 : }
859 0 : TempSize = state.dataSize->ZoneHVACSizing(zoneHVACIndex).MaxHeatAirVolFlow;
860 0 : } else if (SAFMethod == DataSizing::FlowPerFloorArea) {
861 0 : ZoneEqSizing.SystemAirFlow = true;
862 0 : ZoneEqSizing.AirVolFlow = state.dataSize->ZoneHVACSizing(zoneHVACIndex).MaxHeatAirVolFlow *
863 0 : state.dataHeatBal->Zone(state.dataSize->DataZoneNumber).FloorArea;
864 0 : TempSize = ZoneEqSizing.AirVolFlow;
865 0 : state.dataSize->DataScalableSizingON = true;
866 0 : } else if (SAFMethod == DataSizing::FractionOfAutosizedHeatingAirflow) {
867 0 : state.dataSize->DataFracOfAutosizedCoolingAirflow = state.dataSize->ZoneHVACSizing(zoneHVACIndex).MaxHeatAirVolFlow;
868 0 : TempSize = DataSizing::AutoSize;
869 0 : state.dataSize->DataScalableSizingON = true;
870 : } else {
871 0 : TempSize = state.dataSize->ZoneHVACSizing(zoneHVACIndex).MaxHeatAirVolFlow;
872 : }
873 0 : bool errorsFound = false;
874 0 : HeatingAirFlowSizer sizingHeatingAirFlow;
875 0 : sizingHeatingAirFlow.overrideSizingString(SizingString);
876 : // sizingHeatingAirFlow.setHVACSizingIndexData(FanCoil(FanCoilNum).HVACSizingIndex);
877 0 : sizingHeatingAirFlow.initializeWithinEP(state, CompType, CompName, PrintFlag, RoutineName);
878 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirVolFlow = sizingHeatingAirFlow.size(state, TempSize, errorsFound);
879 :
880 0 : } else if (SAFMethod == DataSizing::FlowPerHeatingCapacity) {
881 0 : TempSize = DataSizing::AutoSize;
882 0 : PrintFlag = false;
883 0 : state.dataSize->DataScalableSizingON = true;
884 0 : state.dataSize->DataFlowUsedForSizing = state.dataSize->FinalZoneSizing(CurZoneEqNum).DesHeatVolFlow;
885 0 : bool errorsFound = false;
886 0 : HeatingCapacitySizer sizerHeatingCapacity;
887 0 : sizerHeatingCapacity.overrideSizingString(SizingString);
888 0 : sizerHeatingCapacity.initializeWithinEP(state, CompType, CompName, PrintFlag, RoutineName);
889 0 : TempSize = sizerHeatingCapacity.size(state, TempSize, errorsFound);
890 0 : if (state.dataSize->ZoneHVACSizing(zoneHVACIndex).HeatingCapMethod == DataSizing::FractionOfAutosizedHeatingCapacity) {
891 0 : state.dataSize->DataFracOfAutosizedHeatingCapacity = state.dataSize->ZoneHVACSizing(zoneHVACIndex).ScaledHeatingCapacity;
892 : }
893 0 : state.dataSize->DataAutosizedHeatingCapacity = TempSize;
894 0 : state.dataSize->DataFlowPerHeatingCapacity = state.dataSize->ZoneHVACSizing(zoneHVACIndex).MaxHeatAirVolFlow;
895 0 : PrintFlag = true;
896 0 : TempSize = DataSizing::AutoSize;
897 0 : errorsFound = false;
898 0 : HeatingAirFlowSizer sizingHeatingAirFlow;
899 0 : sizingHeatingAirFlow.overrideSizingString(SizingString);
900 : // sizingHeatingAirFlow.setHVACSizingIndexData(FanCoil(FanCoilNum).HVACSizingIndex);
901 0 : sizingHeatingAirFlow.initializeWithinEP(state, CompType, CompName, PrintFlag, RoutineName);
902 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirVolFlow = sizingHeatingAirFlow.size(state, TempSize, errorsFound);
903 0 : }
904 0 : state.dataSize->DataScalableSizingON = false;
905 : } else {
906 : // no scalble sizing method has been specified. Sizing proceeds using the method
907 : // specified in the zoneHVAC object
908 2 : int FieldNum = 1; // N1 , \field Maximum Supply Air Flow Rate
909 2 : PrintFlag = true;
910 2 : SizingString = state.dataUnitHeaters->UnitHeatNumericFields(UnitHeatNum).FieldNames(FieldNum) + " [m3/s]";
911 2 : TempSize = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirVolFlow;
912 2 : bool errorsFound = false;
913 2 : HeatingAirFlowSizer sizingHeatingAirFlow;
914 2 : sizingHeatingAirFlow.overrideSizingString(SizingString);
915 : // sizingHeatingAirFlow.setHVACSizingIndexData(FanCoil(FanCoilNum).HVACSizingIndex);
916 2 : sizingHeatingAirFlow.initializeWithinEP(state, CompType, CompName, PrintFlag, RoutineName);
917 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirVolFlow = sizingHeatingAirFlow.size(state, TempSize, errorsFound);
918 2 : }
919 : }
920 :
921 2 : bool IsAutoSize = false;
922 2 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotWaterFlow == DataSizing::AutoSize) {
923 1 : IsAutoSize = true;
924 : }
925 :
926 2 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type == HCoilType::WaterHeatingCoil) {
927 :
928 1 : if (CurZoneEqNum > 0) {
929 1 : if (!IsAutoSize && !state.dataSize->ZoneSizingRunDone) { // Simulation continue
930 0 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotWaterFlow > 0.0) {
931 0 : BaseSizer::reportSizerOutput(state,
932 : "ZoneHVAC:UnitHeater",
933 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name,
934 : "User-Specified Maximum Hot Water Flow [m3/s]",
935 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotWaterFlow);
936 : }
937 : } else {
938 1 : CheckZoneSizing(state, "ZoneHVAC:UnitHeater", state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name);
939 :
940 1 : int CoilWaterInletNode = WaterCoils::GetCoilWaterInletNode(
941 1 : state, "Coil:Heating:Water", state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName, ErrorsFound);
942 1 : int CoilWaterOutletNode = WaterCoils::GetCoilWaterOutletNode(
943 1 : state, "Coil:Heating:Water", state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName, ErrorsFound);
944 1 : if (IsAutoSize) {
945 1 : bool DoWaterCoilSizing = false; // if TRUE do water coil sizing calculation
946 1 : PltSizHeatNum = PlantUtilities::MyPlantSizingIndex(state,
947 : "Coil:Heating:Water",
948 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName,
949 : CoilWaterInletNode,
950 : CoilWaterOutletNode,
951 : ErrorsFound);
952 1 : int CoilNum = WaterCoils::GetWaterCoilIndex(
953 1 : state, "COIL:HEATING:WATER", state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName, ErrorsFound);
954 1 : if (state.dataWaterCoils->WaterCoil(CoilNum).UseDesignWaterDeltaTemp) {
955 0 : WaterCoilSizDeltaT = state.dataWaterCoils->WaterCoil(CoilNum).DesignWaterDeltaTemp;
956 0 : DoWaterCoilSizing = true;
957 : } else {
958 1 : if (PltSizHeatNum > 0) {
959 1 : WaterCoilSizDeltaT = state.dataSize->PlantSizData(PltSizHeatNum).DeltaT;
960 1 : DoWaterCoilSizing = true;
961 : } else {
962 0 : DoWaterCoilSizing = false;
963 : // If there is no heating Plant Sizing object and autosizing was requested, issue fatal error message
964 0 : ShowSevereError(state, "Autosizing of water coil requires a heating loop Sizing:Plant object");
965 0 : ShowContinueError(
966 0 : state, format("Occurs in ZoneHVAC:UnitHeater Object={}", state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name));
967 0 : ErrorsFound = true;
968 : }
969 : }
970 :
971 1 : if (DoWaterCoilSizing) {
972 1 : auto &ZoneEqSizing = state.dataSize->ZoneEqSizing(CurZoneEqNum);
973 1 : int SizingMethod = HVAC::HeatingCapacitySizing;
974 1 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).HVACSizingIndex > 0) {
975 0 : zoneHVACIndex = state.dataUnitHeaters->UnitHeat(UnitHeatNum).HVACSizingIndex;
976 0 : int CapSizingMethod = state.dataSize->ZoneHVACSizing(zoneHVACIndex).HeatingCapMethod;
977 0 : ZoneEqSizing.SizingMethod(SizingMethod) = CapSizingMethod;
978 0 : if (CapSizingMethod == DataSizing::HeatingDesignCapacity || CapSizingMethod == DataSizing::CapacityPerFloorArea ||
979 : CapSizingMethod == DataSizing::FractionOfAutosizedHeatingCapacity) {
980 0 : if (CapSizingMethod == DataSizing::HeatingDesignCapacity) {
981 0 : if (state.dataSize->ZoneHVACSizing(zoneHVACIndex).ScaledHeatingCapacity == DataSizing::AutoSize) {
982 0 : ZoneEqSizing.DesHeatingLoad = state.dataSize->FinalZoneSizing(CurZoneEqNum).DesHeatLoad;
983 : } else {
984 0 : ZoneEqSizing.DesHeatingLoad = state.dataSize->ZoneHVACSizing(zoneHVACIndex).ScaledHeatingCapacity;
985 : }
986 0 : ZoneEqSizing.HeatingCapacity = true;
987 0 : TempSize = DataSizing::AutoSize;
988 0 : } else if (CapSizingMethod == DataSizing::CapacityPerFloorArea) {
989 0 : ZoneEqSizing.HeatingCapacity = true;
990 0 : ZoneEqSizing.DesHeatingLoad = state.dataSize->ZoneHVACSizing(zoneHVACIndex).ScaledHeatingCapacity *
991 0 : state.dataHeatBal->Zone(state.dataSize->DataZoneNumber).FloorArea;
992 0 : state.dataSize->DataScalableCapSizingON = true;
993 0 : } else if (CapSizingMethod == DataSizing::FractionOfAutosizedHeatingCapacity) {
994 0 : state.dataSize->DataFracOfAutosizedHeatingCapacity =
995 0 : state.dataSize->ZoneHVACSizing(zoneHVACIndex).ScaledHeatingCapacity;
996 0 : state.dataSize->DataScalableCapSizingON = true;
997 0 : TempSize = DataSizing::AutoSize;
998 : }
999 : }
1000 0 : PrintFlag = false;
1001 0 : bool errorsFound = false;
1002 0 : HeatingCapacitySizer sizerHeatingCapacity;
1003 0 : sizerHeatingCapacity.overrideSizingString(SizingString);
1004 0 : sizerHeatingCapacity.initializeWithinEP(state, CompType, CompName, PrintFlag, RoutineName);
1005 0 : DesCoilLoad = sizerHeatingCapacity.size(state, TempSize, errorsFound);
1006 0 : state.dataSize->DataScalableCapSizingON = false;
1007 0 : } else {
1008 1 : SizingString = "";
1009 1 : PrintFlag = false;
1010 1 : TempSize = DataSizing::AutoSize;
1011 1 : ZoneEqSizing.HeatingCapacity = true;
1012 1 : ZoneEqSizing.DesHeatingLoad = state.dataSize->FinalZoneSizing(CurZoneEqNum).DesHeatLoad;
1013 1 : bool errorsFound = false;
1014 1 : HeatingCapacitySizer sizerHeatingCapacity;
1015 1 : sizerHeatingCapacity.overrideSizingString(SizingString);
1016 1 : sizerHeatingCapacity.initializeWithinEP(state, CompType, CompName, PrintFlag, RoutineName);
1017 1 : DesCoilLoad = sizerHeatingCapacity.size(state, TempSize, errorsFound);
1018 1 : }
1019 :
1020 1 : if (DesCoilLoad >= HVAC::SmallLoad) {
1021 1 : rho = state.dataPlnt->PlantLoop(state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc.loopNum)
1022 1 : .glycol->getDensity(state, Constant::HWInitConvTemp, RoutineName);
1023 1 : Cp = state.dataPlnt->PlantLoop(state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc.loopNum)
1024 1 : .glycol->getSpecificHeat(state, Constant::HWInitConvTemp, RoutineName);
1025 1 : MaxVolHotWaterFlowDes = DesCoilLoad / (WaterCoilSizDeltaT * Cp * rho);
1026 : } else {
1027 0 : MaxVolHotWaterFlowDes = 0.0;
1028 : }
1029 : }
1030 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotWaterFlow = MaxVolHotWaterFlowDes;
1031 2 : BaseSizer::reportSizerOutput(state,
1032 : "ZoneHVAC:UnitHeater",
1033 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name,
1034 : "Design Size Maximum Hot Water Flow [m3/s]",
1035 : MaxVolHotWaterFlowDes);
1036 : } else {
1037 0 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotWaterFlow > 0.0 && MaxVolHotWaterFlowDes > 0.0) {
1038 0 : MaxVolHotWaterFlowUser = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotWaterFlow;
1039 0 : BaseSizer::reportSizerOutput(state,
1040 : "ZoneHVAC:UnitHeater",
1041 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name,
1042 : "Design Size Maximum Hot Water Flow [m3/s]",
1043 : MaxVolHotWaterFlowDes,
1044 : "User-Specified Maximum Hot Water Flow [m3/s]",
1045 : MaxVolHotWaterFlowUser);
1046 0 : if (state.dataGlobal->DisplayExtraWarnings) {
1047 0 : if ((std::abs(MaxVolHotWaterFlowDes - MaxVolHotWaterFlowUser) / MaxVolHotWaterFlowUser) >
1048 0 : state.dataSize->AutoVsHardSizingThreshold) {
1049 0 : ShowMessage(state,
1050 0 : format("SizeUnitHeater: Potential issue with equipment sizing for ZoneHVAC:UnitHeater {}",
1051 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name));
1052 0 : ShowContinueError(state,
1053 0 : format("User-Specified Maximum Hot Water Flow of {:.5R} [m3/s]", MaxVolHotWaterFlowUser));
1054 0 : ShowContinueError(
1055 0 : state, format("differs from Design Size Maximum Hot Water Flow of {:.5R} [m3/s]", MaxVolHotWaterFlowDes));
1056 0 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
1057 0 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
1058 : }
1059 : }
1060 : }
1061 : }
1062 : }
1063 : }
1064 : } else {
1065 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotWaterFlow = 0.0;
1066 : }
1067 :
1068 2 : IsAutoSize = false;
1069 2 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotSteamFlow == DataSizing::AutoSize) {
1070 1 : IsAutoSize = true;
1071 : }
1072 :
1073 2 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type == HCoilType::SteamCoil) {
1074 :
1075 0 : if (CurZoneEqNum > 0) {
1076 0 : if (!IsAutoSize && !state.dataSize->ZoneSizingRunDone) { // Simulation continue
1077 0 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotSteamFlow > 0.0) {
1078 0 : BaseSizer::reportSizerOutput(state,
1079 : "ZoneHVAC:UnitHeater",
1080 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name,
1081 : "User-Specified Maximum Steam Flow [m3/s]",
1082 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotSteamFlow);
1083 : }
1084 : } else {
1085 0 : auto &ZoneEqSizing = state.dataSize->ZoneEqSizing(CurZoneEqNum);
1086 0 : CheckZoneSizing(state, "ZoneHVAC:UnitHeater", state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name);
1087 :
1088 0 : int CoilSteamInletNode = SteamCoils::GetCoilSteamInletNode(
1089 0 : state, "Coil:Heating:Steam", state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName, ErrorsFound);
1090 0 : int CoilSteamOutletNode = SteamCoils::GetCoilSteamInletNode(
1091 0 : state, "Coil:Heating:Steam", state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName, ErrorsFound);
1092 0 : if (IsAutoSize) {
1093 0 : PltSizHeatNum = PlantUtilities::MyPlantSizingIndex(state,
1094 : "Coil:Heating:Steam",
1095 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName,
1096 : CoilSteamInletNode,
1097 : CoilSteamOutletNode,
1098 : ErrorsFound);
1099 0 : if (PltSizHeatNum > 0) {
1100 0 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).HVACSizingIndex > 0) {
1101 0 : zoneHVACIndex = state.dataUnitHeaters->UnitHeat(UnitHeatNum).HVACSizingIndex;
1102 0 : int SizingMethod = HVAC::HeatingCapacitySizing;
1103 0 : int CapSizingMethod = state.dataSize->ZoneHVACSizing(zoneHVACIndex).HeatingCapMethod;
1104 0 : ZoneEqSizing.SizingMethod(SizingMethod) = CapSizingMethod;
1105 0 : if (CapSizingMethod == DataSizing::HeatingDesignCapacity || CapSizingMethod == DataSizing::CapacityPerFloorArea ||
1106 : CapSizingMethod == DataSizing::FractionOfAutosizedHeatingCapacity) {
1107 0 : if (CapSizingMethod == DataSizing::HeatingDesignCapacity) {
1108 0 : if (state.dataSize->ZoneHVACSizing(zoneHVACIndex).ScaledHeatingCapacity == DataSizing::AutoSize) {
1109 0 : ZoneEqSizing.DesHeatingLoad = state.dataSize->FinalZoneSizing(CurZoneEqNum).DesHeatLoad;
1110 : } else {
1111 0 : ZoneEqSizing.DesHeatingLoad = state.dataSize->ZoneHVACSizing(zoneHVACIndex).ScaledHeatingCapacity;
1112 : }
1113 0 : ZoneEqSizing.HeatingCapacity = true;
1114 0 : TempSize = DataSizing::AutoSize;
1115 0 : } else if (CapSizingMethod == DataSizing::CapacityPerFloorArea) {
1116 0 : ZoneEqSizing.HeatingCapacity = true;
1117 0 : ZoneEqSizing.DesHeatingLoad = state.dataSize->ZoneHVACSizing(zoneHVACIndex).ScaledHeatingCapacity *
1118 0 : state.dataHeatBal->Zone(state.dataSize->DataZoneNumber).FloorArea;
1119 0 : state.dataSize->DataScalableCapSizingON = true;
1120 0 : } else if (CapSizingMethod == DataSizing::FractionOfAutosizedHeatingCapacity) {
1121 0 : state.dataSize->DataFracOfAutosizedHeatingCapacity =
1122 0 : state.dataSize->ZoneHVACSizing(zoneHVACIndex).ScaledHeatingCapacity;
1123 0 : TempSize = DataSizing::AutoSize;
1124 0 : state.dataSize->DataScalableCapSizingON = true;
1125 : }
1126 : }
1127 0 : PrintFlag = false;
1128 0 : bool errorsFound = false;
1129 0 : HeatingCapacitySizer sizerHeatingCapacity;
1130 0 : sizerHeatingCapacity.overrideSizingString(SizingString);
1131 0 : sizerHeatingCapacity.initializeWithinEP(state, CompType, CompName, PrintFlag, RoutineName);
1132 0 : DesCoilLoad = sizerHeatingCapacity.size(state, TempSize, errorsFound);
1133 0 : state.dataSize->DataScalableCapSizingON = false;
1134 0 : } else {
1135 0 : DesCoilLoad = state.dataSize->FinalZoneSizing(CurZoneEqNum).DesHeatLoad;
1136 : }
1137 0 : if (DesCoilLoad >= HVAC::SmallLoad) {
1138 0 : TempSteamIn = 100.00;
1139 0 : auto *steam = Fluid::GetSteam(state);
1140 0 : EnthSteamInDry = steam->getSatEnthalpy(state, TempSteamIn, 1.0, RoutineName);
1141 0 : EnthSteamOutWet = steam->getSatEnthalpy(state, TempSteamIn, 0.0, RoutineName);
1142 0 : LatentHeatSteam = EnthSteamInDry - EnthSteamOutWet;
1143 0 : SteamDensity = steam->getSatDensity(state, TempSteamIn, 1.0, RoutineName);
1144 0 : MaxVolHotSteamFlowDes =
1145 0 : DesCoilLoad / (SteamDensity * (LatentHeatSteam +
1146 0 : state.dataSize->PlantSizData(PltSizHeatNum).DeltaT *
1147 0 : Psychrometrics::CPHW(state.dataSize->PlantSizData(PltSizHeatNum).ExitTemp)));
1148 : } else {
1149 0 : MaxVolHotSteamFlowDes = 0.0;
1150 : }
1151 : } else {
1152 0 : ShowSevereError(state, "Autosizing of Steam flow requires a heating loop Sizing:Plant object");
1153 0 : ShowContinueError(state,
1154 0 : format("Occurs in ZoneHVAC:UnitHeater Object={}", state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name));
1155 0 : ErrorsFound = true;
1156 : }
1157 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotSteamFlow = MaxVolHotSteamFlowDes;
1158 0 : BaseSizer::reportSizerOutput(state,
1159 : "ZoneHVAC:UnitHeater",
1160 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name,
1161 : "Design Size Maximum Steam Flow [m3/s]",
1162 : MaxVolHotSteamFlowDes);
1163 : } else {
1164 0 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotSteamFlow > 0.0 && MaxVolHotSteamFlowDes > 0.0) {
1165 0 : MaxVolHotSteamFlowUser = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotSteamFlow;
1166 0 : BaseSizer::reportSizerOutput(state,
1167 : "ZoneHVAC:UnitHeater",
1168 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name,
1169 : "Design Size Maximum Steam Flow [m3/s]",
1170 : MaxVolHotSteamFlowDes,
1171 : "User-Specified Maximum Steam Flow [m3/s]",
1172 : MaxVolHotSteamFlowUser);
1173 0 : if (state.dataGlobal->DisplayExtraWarnings) {
1174 0 : if ((std::abs(MaxVolHotSteamFlowDes - MaxVolHotSteamFlowUser) / MaxVolHotSteamFlowUser) >
1175 0 : state.dataSize->AutoVsHardSizingThreshold) {
1176 0 : ShowMessage(state,
1177 0 : format("SizeUnitHeater: Potential issue with equipment sizing for ZoneHVAC:UnitHeater {}",
1178 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name));
1179 0 : ShowContinueError(state, format("User-Specified Maximum Steam Flow of {:.5R} [m3/s]", MaxVolHotSteamFlowUser));
1180 0 : ShowContinueError(state,
1181 0 : format("differs from Design Size Maximum Steam Flow of {:.5R} [m3/s]", MaxVolHotSteamFlowDes));
1182 0 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
1183 0 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
1184 : }
1185 : }
1186 : }
1187 : }
1188 : }
1189 : }
1190 : } else {
1191 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotSteamFlow = 0.0;
1192 : }
1193 :
1194 : // set the design air flow rate for the heating coil
1195 :
1196 2 : WaterCoils::SetCoilDesFlow(state,
1197 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilTypeCh,
1198 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName,
1199 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxAirVolFlow,
1200 : ErrorsFound);
1201 2 : if (CurZoneEqNum > 0) {
1202 2 : auto &ZoneEqSizing = state.dataSize->ZoneEqSizing(CurZoneEqNum);
1203 2 : ZoneEqSizing.MaxHWVolFlow = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxVolHotWaterFlow;
1204 : }
1205 :
1206 2 : if (ErrorsFound) {
1207 0 : ShowFatalError(state, "Preceding sizing errors cause program termination");
1208 : }
1209 2 : }
1210 :
1211 362 : void CalcUnitHeater(EnergyPlusData &state,
1212 : int &UnitHeatNum, // number of the current fan coil unit being simulated
1213 : int const ZoneNum, // number of zone being served
1214 : bool const FirstHVACIteration, // TRUE if 1st HVAC simulation of system timestep
1215 : Real64 &PowerMet, // Sensible power supplied (W)
1216 : Real64 &LatOutputProvided // Latent power supplied (kg/s), negative = dehumidification
1217 : )
1218 : {
1219 :
1220 : // SUBROUTINE INFORMATION:
1221 : // AUTHOR Rick Strand
1222 : // DATE WRITTEN May 2000
1223 : // MODIFIED Don Shirey, Aug 2009 (LatOutputProvided)
1224 : // July 2012, Chandan Sharma - FSEC: Added zone sys avail managers
1225 :
1226 : // PURPOSE OF THIS SUBROUTINE:
1227 : // This subroutine mainly controls the action of the unit heater
1228 : // based on the user input for controls and the defined controls
1229 : // algorithms. There are currently (at the initial creation of this
1230 : // subroutine) two control methods: on-off fan operation or continuous
1231 : // fan operation.
1232 :
1233 : // METHODOLOGY EMPLOYED:
1234 : // Unit is controlled based on user input and what is happening in the
1235 : // simulation. There are various cases to consider:
1236 : // 1. OFF: Unit is schedule off. All flow rates are set to zero and
1237 : // the temperatures are set to zone conditions.
1238 : // 2. NO LOAD OR COOLING/ON-OFF FAN CONTROL: Unit is available, but
1239 : // there is no heating load. All flow rates are set to zero and
1240 : // the temperatures are set to zone conditions.
1241 : // 3. NO LOAD OR COOLING/CONTINUOUS FAN CONTROL: Unit is available and
1242 : // the fan is running (if it is scheduled to be available also).
1243 : // No heating is provided, only circulation via the fan running.
1244 : // 4. HEATING: The unit is on/available and there is a heating load.
1245 : // The heating coil is modulated (constant fan speed) to meet the
1246 : // heating load.
1247 :
1248 : // REFERENCES:
1249 : // ASHRAE Systems and Equipment Handbook (SI), 1996. page 31.7
1250 :
1251 : // SUBROUTINE PARAMETER DEFINITIONS:
1252 362 : int constexpr MaxIter = 100; // maximum number of iterations
1253 :
1254 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1255 : Real64 SpecHumOut; // Specific humidity ratio of outlet air (kg moisture / kg moist air)
1256 : Real64 SpecHumIn; // Specific humidity ratio of inlet air (kg moisture / kg moist air)
1257 : Real64 mdot; // local temporary for fluid mass flow rate
1258 :
1259 : // initialize local variables
1260 362 : Real64 QUnitOut = 0.0;
1261 362 : Real64 NoOutput = 0.0;
1262 362 : Real64 FullOutput = 0.0;
1263 362 : Real64 LatentOutput = 0.0; // Latent (moisture) add/removal rate, negative is dehumidification [kg/s]
1264 362 : Real64 MaxWaterFlow = 0.0;
1265 362 : Real64 MinWaterFlow = 0.0;
1266 362 : Real64 PartLoadFrac = 0.0;
1267 362 : int InletNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode;
1268 362 : int OutletNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirOutNode;
1269 362 : int ControlNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlNode;
1270 362 : Real64 ControlOffset = state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlOffset;
1271 362 : HVAC::FanOp fanOp = state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanOp;
1272 :
1273 362 : if (fanOp != HVAC::FanOp::Cycling) {
1274 :
1275 9 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).availSched->getCurrentVal() <= 0 ||
1276 3 : ((state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanAvailSched->getCurrentVal() <= 0 && !state.dataHVACGlobal->TurnFansOn) ||
1277 3 : state.dataHVACGlobal->TurnFansOff)) {
1278 : // Case 1: OFF-->unit schedule says that it it not available
1279 : // OR child fan in not available OR child fan not being cycled ON by sys avail manager
1280 : // OR child fan being forced OFF by sys avail manager
1281 0 : state.dataUnitHeaters->HCoilOn = false;
1282 0 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type == HCoilType::WaterHeatingCoil) {
1283 0 : mdot = 0.0; // try to turn off
1284 :
1285 0 : PlantUtilities::SetComponentFlowRate(state,
1286 : mdot,
1287 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlNode,
1288 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotCoilOutNodeNum,
1289 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc);
1290 : }
1291 0 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type == HCoilType::SteamCoil) {
1292 0 : mdot = 0.0; // try to turn off
1293 :
1294 0 : PlantUtilities::SetComponentFlowRate(state,
1295 : mdot,
1296 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlNode,
1297 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotCoilOutNodeNum,
1298 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc);
1299 : }
1300 0 : CalcUnitHeaterComponents(state, UnitHeatNum, FirstHVACIteration, QUnitOut);
1301 :
1302 3 : } else if ((state.dataUnitHeaters->QZnReq < HVAC::SmallLoad) || state.dataZoneEnergyDemand->CurDeadBandOrSetback(ZoneNum)) {
1303 : // Unit is available, but there is no load on it or we are in setback/deadband
1304 1 : if (!state.dataUnitHeaters->UnitHeat(UnitHeatNum).FanOffNoHeating) {
1305 :
1306 : // Case 2: NO LOAD OR COOLING/ON-OFF FAN CONTROL-->turn everything off
1307 : // because there is no load on the unit heater
1308 0 : state.dataUnitHeaters->HCoilOn = false;
1309 0 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type == HCoilType::WaterHeatingCoil) {
1310 0 : mdot = 0.0; // try to turn off
1311 :
1312 0 : PlantUtilities::SetComponentFlowRate(state,
1313 : mdot,
1314 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlNode,
1315 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotCoilOutNodeNum,
1316 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc);
1317 : }
1318 0 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type == HCoilType::SteamCoil) {
1319 0 : mdot = 0.0; // try to turn off
1320 :
1321 0 : PlantUtilities::SetComponentFlowRate(state,
1322 : mdot,
1323 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlNode,
1324 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotCoilOutNodeNum,
1325 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc);
1326 : }
1327 0 : CalcUnitHeaterComponents(state, UnitHeatNum, FirstHVACIteration, QUnitOut);
1328 :
1329 : } else {
1330 : // Case 3: NO LOAD OR COOLING/CONTINUOUS FAN CONTROL-->let the fan
1331 : // continue to run even though there is no load (air circulation)
1332 : // Note that the flow rates were already set in the initialization routine
1333 : // so there is really nothing else left to do except call the components.
1334 :
1335 1 : state.dataUnitHeaters->HCoilOn = false;
1336 1 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type == HCoilType::WaterHeatingCoil) {
1337 1 : mdot = 0.0; // try to turn off
1338 :
1339 1 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc.loopNum > 0) {
1340 2 : PlantUtilities::SetComponentFlowRate(state,
1341 : mdot,
1342 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlNode,
1343 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotCoilOutNodeNum,
1344 1 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc);
1345 : }
1346 : }
1347 1 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type == HCoilType::SteamCoil) {
1348 0 : mdot = 0.0; // try to turn off
1349 0 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc.loopNum > 0) {
1350 0 : PlantUtilities::SetComponentFlowRate(state,
1351 : mdot,
1352 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlNode,
1353 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotCoilOutNodeNum,
1354 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc);
1355 : }
1356 : }
1357 :
1358 1 : CalcUnitHeaterComponents(state, UnitHeatNum, FirstHVACIteration, QUnitOut);
1359 : }
1360 :
1361 : } else { // Case 4: HEATING-->unit is available and there is a heating load
1362 :
1363 2 : switch (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type) {
1364 :
1365 2 : case HCoilType::WaterHeatingCoil: {
1366 :
1367 : // On the first HVAC iteration the system values are given to the controller, but after that
1368 : // the demand limits are in place and there needs to be feedback to the Zone Equipment
1369 2 : if (FirstHVACIteration) {
1370 2 : MaxWaterFlow = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxHotWaterFlow;
1371 2 : MinWaterFlow = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MinHotWaterFlow;
1372 : } else {
1373 0 : MaxWaterFlow = state.dataLoopNodes->Node(ControlNode).MassFlowRateMaxAvail;
1374 0 : MinWaterFlow = state.dataLoopNodes->Node(ControlNode).MassFlowRateMinAvail;
1375 : }
1376 : // control water flow to obtain output matching QZnReq
1377 6 : ControlCompOutput(state,
1378 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name,
1379 2 : state.dataUnitHeaters->cMO_UnitHeater,
1380 : UnitHeatNum,
1381 : FirstHVACIteration,
1382 2 : state.dataUnitHeaters->QZnReq,
1383 : ControlNode,
1384 : MaxWaterFlow,
1385 : MinWaterFlow,
1386 : ControlOffset,
1387 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).ControlCompTypeNum,
1388 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).CompErrIndex,
1389 : _,
1390 : _,
1391 : _,
1392 : _,
1393 : _,
1394 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc);
1395 2 : break;
1396 : }
1397 0 : case HCoilType::Electric:
1398 : case HCoilType::Gas:
1399 : case HCoilType::SteamCoil: {
1400 0 : state.dataUnitHeaters->HCoilOn = true;
1401 0 : CalcUnitHeaterComponents(state, UnitHeatNum, FirstHVACIteration, QUnitOut);
1402 0 : break;
1403 : }
1404 0 : default:
1405 0 : break;
1406 : }
1407 : }
1408 3 : if (state.dataLoopNodes->Node(InletNode).MassFlowRateMax > 0.0) {
1409 2 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).FanPartLoadRatio =
1410 2 : state.dataLoopNodes->Node(InletNode).MassFlowRate / state.dataLoopNodes->Node(InletNode).MassFlowRateMax;
1411 : }
1412 : } else { // OnOff fan and cycling
1413 361 : if ((state.dataUnitHeaters->QZnReq < HVAC::SmallLoad) || (state.dataZoneEnergyDemand->CurDeadBandOrSetback(ZoneNum)) ||
1414 362 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).availSched->getCurrentVal() <= 0 ||
1415 1 : ((state.dataUnitHeaters->UnitHeat(UnitHeatNum).fanAvailSched->getCurrentVal() <= 0 && !state.dataHVACGlobal->TurnFansOn) ||
1416 1 : state.dataHVACGlobal->TurnFansOff)) {
1417 : // Case 1: OFF-->unit schedule says that it it not available
1418 : // OR child fan in not available OR child fan not being cycled ON by sys avail manager
1419 : // OR child fan being forced OFF by sys avail manager
1420 358 : PartLoadFrac = 0.0;
1421 358 : state.dataUnitHeaters->HCoilOn = false;
1422 358 : CalcUnitHeaterComponents(state, UnitHeatNum, FirstHVACIteration, QUnitOut, fanOp, PartLoadFrac);
1423 :
1424 358 : if (state.dataLoopNodes->Node(InletNode).MassFlowRateMax > 0.0) {
1425 358 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).FanPartLoadRatio =
1426 358 : state.dataLoopNodes->Node(InletNode).MassFlowRate / state.dataLoopNodes->Node(InletNode).MassFlowRateMax;
1427 : }
1428 :
1429 : } else { // Case 4: HEATING-->unit is available and there is a heating load
1430 :
1431 1 : state.dataUnitHeaters->HCoilOn = true;
1432 :
1433 : // Find part load ratio of unit heater coils
1434 1 : PartLoadFrac = 0.0;
1435 1 : CalcUnitHeaterComponents(state, UnitHeatNum, FirstHVACIteration, NoOutput, fanOp, PartLoadFrac);
1436 1 : if ((NoOutput - state.dataUnitHeaters->QZnReq) < HVAC::SmallLoad) {
1437 : // Unit heater is unable to meet the load with coil off, set PLR = 1
1438 1 : PartLoadFrac = 1.0;
1439 1 : CalcUnitHeaterComponents(state, UnitHeatNum, FirstHVACIteration, FullOutput, fanOp, PartLoadFrac);
1440 1 : if ((FullOutput - state.dataUnitHeaters->QZnReq) > HVAC::SmallLoad) {
1441 : // Unit heater full load capacity is able to meet the load, Find PLR
1442 :
1443 0 : auto f = [&state, UnitHeatNum, FirstHVACIteration, fanOp](Real64 const PartLoadRatio) {
1444 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1445 : Real64 QUnitOut; // heating provided by unit heater [watts]
1446 :
1447 0 : CalcUnitHeaterComponents(state, UnitHeatNum, FirstHVACIteration, QUnitOut, fanOp, PartLoadRatio);
1448 :
1449 : // Calculate residual based on output calculation flag
1450 0 : if (state.dataUnitHeaters->QZnReq != 0.0) {
1451 0 : return (QUnitOut - state.dataUnitHeaters->QZnReq) / state.dataUnitHeaters->QZnReq;
1452 : } else {
1453 0 : return 0.0;
1454 : }
1455 0 : };
1456 :
1457 : // Tolerance is in fraction of load, MaxIter = 30, SolFalg = # of iterations or error as appropriate
1458 0 : int SolFlag = 0; // # of iterations IF positive, -1 means failed to converge, -2 means bounds are incorrect
1459 0 : General::SolveRoot(state, 0.001, MaxIter, SolFlag, PartLoadFrac, f, 0.0, 1.0);
1460 : }
1461 : }
1462 :
1463 1 : CalcUnitHeaterComponents(state, UnitHeatNum, FirstHVACIteration, QUnitOut, fanOp, PartLoadFrac);
1464 :
1465 : } // ...end of unit ON/OFF IF-THEN block
1466 359 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).PartLoadFrac = PartLoadFrac;
1467 359 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).FanPartLoadRatio = PartLoadFrac;
1468 359 : state.dataLoopNodes->Node(OutletNode).MassFlowRate = state.dataLoopNodes->Node(InletNode).MassFlowRate;
1469 : }
1470 :
1471 : // CR9155 Remove specific humidity calculations
1472 362 : SpecHumOut = state.dataLoopNodes->Node(OutletNode).HumRat;
1473 362 : SpecHumIn = state.dataLoopNodes->Node(InletNode).HumRat;
1474 362 : LatentOutput = state.dataLoopNodes->Node(OutletNode).MassFlowRate * (SpecHumOut - SpecHumIn); // Latent rate (kg/s), dehumid = negative
1475 :
1476 362 : QUnitOut = state.dataLoopNodes->Node(OutletNode).MassFlowRate *
1477 362 : (Psychrometrics::PsyHFnTdbW(state.dataLoopNodes->Node(OutletNode).Temp, state.dataLoopNodes->Node(InletNode).HumRat) -
1478 362 : Psychrometrics::PsyHFnTdbW(state.dataLoopNodes->Node(InletNode).Temp, state.dataLoopNodes->Node(InletNode).HumRat));
1479 :
1480 : // Report variables...
1481 362 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HeatPower = max(0.0, QUnitOut);
1482 362 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).ElecPower =
1483 362 : state.dataFans->fans(state.dataUnitHeaters->UnitHeat(UnitHeatNum).Fan_Index)->totalPower;
1484 :
1485 362 : PowerMet = QUnitOut;
1486 362 : LatOutputProvided = LatentOutput;
1487 362 : }
1488 :
1489 377 : void CalcUnitHeaterComponents(EnergyPlusData &state,
1490 : int const UnitHeatNum, // Unit index in unit heater array
1491 : bool const FirstHVACIteration, // flag for 1st HVAV iteration in the time step
1492 : Real64 &LoadMet, // load met by unit (watts)
1493 : HVAC::FanOp const fanOp, // fan operating mode
1494 : Real64 const PartLoadRatio // part-load ratio
1495 : )
1496 : {
1497 :
1498 : // SUBROUTINE INFORMATION:
1499 : // AUTHOR Rick Strand
1500 : // DATE WRITTEN May 2000
1501 : // MODIFIED July 2012, Chandan Sharma - FSEC: Added zone sys avail managers
1502 :
1503 : // PURPOSE OF THIS SUBROUTINE:
1504 : // This subroutine launches the individual component simulations.
1505 : // This is called either when the unit is off to carry null conditions
1506 : // through the unit or during control iterations to continue updating
1507 : // what is going on within the unit.
1508 :
1509 : // METHODOLOGY EMPLOYED:
1510 : // Simply calls the different components in order.
1511 :
1512 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1513 : Real64 AirMassFlow; // total mass flow through the unit
1514 : Real64 CpAirZn; // specific heat of dry air at zone conditions (zone conditions same as unit inlet)
1515 : int HCoilInAirNode; // inlet node number for fan exit/coil inlet
1516 : Real64 mdot; // local temporary for fluid mass flow rate
1517 :
1518 377 : int InletNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode;
1519 377 : int OutletNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirOutNode;
1520 377 : Real64 QCoilReq = 0.0;
1521 :
1522 377 : if (fanOp != HVAC::FanOp::Cycling) {
1523 16 : state.dataFans->fans(state.dataUnitHeaters->UnitHeat(UnitHeatNum).Fan_Index)->simulate(state, FirstHVACIteration, _, _);
1524 :
1525 16 : switch (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type) {
1526 :
1527 16 : case HCoilType::WaterHeatingCoil: {
1528 :
1529 32 : WaterCoils::SimulateWaterCoilComponents(state,
1530 16 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName,
1531 : FirstHVACIteration,
1532 16 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoil_Index);
1533 16 : break;
1534 : }
1535 0 : case HCoilType::SteamCoil: {
1536 :
1537 0 : if (!state.dataUnitHeaters->HCoilOn) {
1538 0 : QCoilReq = 0.0;
1539 : } else {
1540 0 : HCoilInAirNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).FanOutletNode;
1541 0 : CpAirZn = Psychrometrics::PsyCpAirFnW(state.dataLoopNodes->Node(state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode).HumRat);
1542 0 : QCoilReq =
1543 0 : state.dataUnitHeaters->QZnReq - state.dataLoopNodes->Node(HCoilInAirNode).MassFlowRate * CpAirZn *
1544 0 : (state.dataLoopNodes->Node(HCoilInAirNode).Temp -
1545 0 : state.dataLoopNodes->Node(state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode).Temp);
1546 : }
1547 0 : if (QCoilReq < 0.0) {
1548 0 : QCoilReq = 0.0; // a heating coil can only heat, not cool
1549 : }
1550 0 : SteamCoils::SimulateSteamCoilComponents(state,
1551 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName,
1552 : FirstHVACIteration,
1553 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoil_Index,
1554 : QCoilReq);
1555 0 : break;
1556 : }
1557 0 : case HCoilType::Electric:
1558 : case HCoilType::Gas: {
1559 :
1560 0 : if (!state.dataUnitHeaters->HCoilOn) {
1561 0 : QCoilReq = 0.0;
1562 : } else {
1563 0 : HCoilInAirNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).FanOutletNode;
1564 0 : CpAirZn = Psychrometrics::PsyCpAirFnW(state.dataLoopNodes->Node(state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode).HumRat);
1565 0 : QCoilReq =
1566 0 : state.dataUnitHeaters->QZnReq - state.dataLoopNodes->Node(HCoilInAirNode).MassFlowRate * CpAirZn *
1567 0 : (state.dataLoopNodes->Node(HCoilInAirNode).Temp -
1568 0 : state.dataLoopNodes->Node(state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode).Temp);
1569 : }
1570 0 : if (QCoilReq < 0.0) {
1571 0 : QCoilReq = 0.0; // a heating coil can only heat, not cool
1572 : }
1573 0 : HeatingCoils::SimulateHeatingCoilComponents(state,
1574 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName,
1575 : FirstHVACIteration,
1576 : QCoilReq,
1577 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoil_Index);
1578 0 : break;
1579 : }
1580 0 : default:
1581 0 : break;
1582 : }
1583 :
1584 16 : AirMassFlow = state.dataLoopNodes->Node(OutletNode).MassFlowRate;
1585 :
1586 16 : state.dataLoopNodes->Node(InletNode).MassFlowRate =
1587 16 : state.dataLoopNodes->Node(OutletNode).MassFlowRate; // maintain continuity through unit heater
1588 :
1589 : } else { // OnOff fan cycling
1590 :
1591 361 : state.dataLoopNodes->Node(InletNode).MassFlowRate = state.dataLoopNodes->Node(InletNode).MassFlowRateMax * PartLoadRatio;
1592 361 : AirMassFlow = state.dataLoopNodes->Node(InletNode).MassFlowRate;
1593 : // Set the fan inlet node maximum available mass flow rates for cycling fans
1594 361 : state.dataLoopNodes->Node(InletNode).MassFlowRateMaxAvail = AirMassFlow;
1595 :
1596 361 : if (QCoilReq < 0.0) {
1597 0 : QCoilReq = 0.0; // a heating coil can only heat, not cool
1598 : }
1599 361 : state.dataFans->fans(state.dataUnitHeaters->UnitHeat(UnitHeatNum).Fan_Index)->simulate(state, FirstHVACIteration, _, _);
1600 :
1601 361 : switch (state.dataUnitHeaters->UnitHeat(UnitHeatNum).Type) {
1602 :
1603 0 : case HCoilType::WaterHeatingCoil: {
1604 :
1605 0 : if (!state.dataUnitHeaters->HCoilOn) {
1606 0 : mdot = 0.0;
1607 0 : QCoilReq = 0.0;
1608 : } else {
1609 0 : HCoilInAirNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).FanOutletNode;
1610 0 : CpAirZn = Psychrometrics::PsyCpAirFnW(state.dataLoopNodes->Node(state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode).HumRat);
1611 0 : QCoilReq =
1612 0 : state.dataUnitHeaters->QZnReq - state.dataLoopNodes->Node(HCoilInAirNode).MassFlowRate * CpAirZn *
1613 0 : (state.dataLoopNodes->Node(HCoilInAirNode).Temp -
1614 0 : state.dataLoopNodes->Node(state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode).Temp);
1615 0 : mdot = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxHotWaterFlow * PartLoadRatio;
1616 : }
1617 0 : if (QCoilReq < 0.0) {
1618 0 : QCoilReq = 0.0; // a heating coil can only heat, not cool
1619 : }
1620 0 : PlantUtilities::SetComponentFlowRate(state,
1621 : mdot,
1622 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlNode,
1623 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotCoilOutNodeNum,
1624 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc);
1625 0 : WaterCoils::SimulateWaterCoilComponents(state,
1626 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName,
1627 : FirstHVACIteration,
1628 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoil_Index,
1629 : QCoilReq,
1630 : fanOp,
1631 : PartLoadRatio);
1632 0 : break;
1633 : }
1634 0 : case HCoilType::SteamCoil: {
1635 0 : if (!state.dataUnitHeaters->HCoilOn) {
1636 0 : mdot = 0.0;
1637 0 : QCoilReq = 0.0;
1638 : } else {
1639 0 : HCoilInAirNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).FanOutletNode;
1640 0 : CpAirZn = Psychrometrics::PsyCpAirFnW(state.dataLoopNodes->Node(state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode).HumRat);
1641 0 : QCoilReq =
1642 0 : state.dataUnitHeaters->QZnReq - state.dataLoopNodes->Node(HCoilInAirNode).MassFlowRate * CpAirZn *
1643 0 : (state.dataLoopNodes->Node(HCoilInAirNode).Temp -
1644 0 : state.dataLoopNodes->Node(state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode).Temp);
1645 0 : mdot = state.dataUnitHeaters->UnitHeat(UnitHeatNum).MaxHotSteamFlow * PartLoadRatio;
1646 : }
1647 0 : if (QCoilReq < 0.0) {
1648 0 : QCoilReq = 0.0; // a heating coil can only heat, not cool
1649 : }
1650 0 : PlantUtilities::SetComponentFlowRate(state,
1651 : mdot,
1652 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotControlNode,
1653 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HotCoilOutNodeNum,
1654 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HWplantLoc);
1655 0 : SteamCoils::SimulateSteamCoilComponents(state,
1656 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName,
1657 : FirstHVACIteration,
1658 0 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoil_Index,
1659 : QCoilReq,
1660 : _,
1661 : fanOp,
1662 : PartLoadRatio);
1663 0 : break;
1664 : }
1665 361 : case HCoilType::Electric:
1666 : case HCoilType::Gas: {
1667 :
1668 361 : if (!state.dataUnitHeaters->HCoilOn) {
1669 358 : QCoilReq = 0.0;
1670 : } else {
1671 3 : HCoilInAirNode = state.dataUnitHeaters->UnitHeat(UnitHeatNum).FanOutletNode;
1672 3 : CpAirZn = Psychrometrics::PsyCpAirFnW(state.dataLoopNodes->Node(state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode).HumRat);
1673 3 : QCoilReq =
1674 3 : state.dataUnitHeaters->QZnReq - state.dataLoopNodes->Node(HCoilInAirNode).MassFlowRate * CpAirZn *
1675 3 : (state.dataLoopNodes->Node(HCoilInAirNode).Temp -
1676 3 : state.dataLoopNodes->Node(state.dataUnitHeaters->UnitHeat(UnitHeatNum).AirInNode).Temp);
1677 : }
1678 361 : if (QCoilReq < 0.0) {
1679 0 : QCoilReq = 0.0; // a heating coil can only heat, not cool
1680 : }
1681 1083 : HeatingCoils::SimulateHeatingCoilComponents(state,
1682 361 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoilName,
1683 : FirstHVACIteration,
1684 : QCoilReq,
1685 361 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HCoil_Index,
1686 : _,
1687 : _,
1688 : fanOp,
1689 : PartLoadRatio);
1690 361 : break;
1691 : }
1692 0 : default:
1693 0 : break;
1694 : }
1695 361 : state.dataLoopNodes->Node(OutletNode).MassFlowRate =
1696 361 : state.dataLoopNodes->Node(InletNode).MassFlowRate; // maintain continuity through unit heater
1697 : }
1698 377 : LoadMet = AirMassFlow * (Psychrometrics::PsyHFnTdbW(state.dataLoopNodes->Node(OutletNode).Temp, state.dataLoopNodes->Node(InletNode).HumRat) -
1699 377 : Psychrometrics::PsyHFnTdbW(state.dataLoopNodes->Node(InletNode).Temp, state.dataLoopNodes->Node(InletNode).HumRat));
1700 377 : }
1701 :
1702 : // SUBROUTINE UpdateUnitHeater
1703 :
1704 : // No update routine needed in this module since all of the updates happen on
1705 : // the Node derived type directly and these updates are done by other routines.
1706 :
1707 : // END SUBROUTINE UpdateUnitHeater
1708 :
1709 362 : void ReportUnitHeater(EnergyPlusData &state, int const UnitHeatNum) // Unit index in unit heater array
1710 : {
1711 :
1712 : // SUBROUTINE INFORMATION:
1713 : // AUTHOR Rick Strand
1714 : // DATE WRITTEN May 2000
1715 :
1716 : // PURPOSE OF THIS SUBROUTINE:
1717 : // This subroutine needs a description.
1718 :
1719 : // METHODOLOGY EMPLOYED:
1720 : // Needs description, as appropriate.
1721 :
1722 : // Using/Aliasing
1723 362 : Real64 TimeStepSysSec = state.dataHVACGlobal->TimeStepSysSec;
1724 :
1725 362 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).HeatEnergy = state.dataUnitHeaters->UnitHeat(UnitHeatNum).HeatPower * TimeStepSysSec;
1726 362 : state.dataUnitHeaters->UnitHeat(UnitHeatNum).ElecEnergy = state.dataUnitHeaters->UnitHeat(UnitHeatNum).ElecPower * TimeStepSysSec;
1727 :
1728 362 : if (state.dataUnitHeaters->UnitHeat(UnitHeatNum).FirstPass) { // reset sizing flags so other zone equipment can size normally
1729 4 : if (!state.dataGlobal->SysSizingCalc) {
1730 1 : DataSizing::resetHVACSizingGlobals(state, state.dataSize->CurZoneEqNum, 0, state.dataUnitHeaters->UnitHeat(UnitHeatNum).FirstPass);
1731 : }
1732 : }
1733 362 : }
1734 :
1735 1 : int getUnitHeaterIndex(EnergyPlusData &state, std::string_view CompName)
1736 : {
1737 1 : if (state.dataUnitHeaters->GetUnitHeaterInputFlag) {
1738 0 : GetUnitHeaterInput(state);
1739 0 : state.dataUnitHeaters->GetUnitHeaterInputFlag = false;
1740 : }
1741 :
1742 1 : for (int UnitHeatNum = 1; UnitHeatNum <= state.dataUnitHeaters->NumOfUnitHeats; ++UnitHeatNum) {
1743 1 : if (Util::SameString(state.dataUnitHeaters->UnitHeat(UnitHeatNum).Name, CompName)) {
1744 1 : return UnitHeatNum;
1745 : }
1746 : }
1747 :
1748 0 : return 0;
1749 : }
1750 :
1751 : } // namespace UnitHeater
1752 :
1753 : } // namespace EnergyPlus
|