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/Base.hh>
56 : #include <EnergyPlus/Boilers.hh>
57 : #include <EnergyPlus/BranchNodeConnections.hh>
58 : #include <EnergyPlus/CurveManager.hh>
59 : #include <EnergyPlus/Data/EnergyPlusData.hh>
60 : #include <EnergyPlus/DataBranchAirLoopPlant.hh>
61 : #include <EnergyPlus/DataGlobalConstants.hh>
62 : #include <EnergyPlus/DataHVACGlobals.hh>
63 : #include <EnergyPlus/DataIPShortCuts.hh>
64 : #include <EnergyPlus/DataLoopNode.hh>
65 : #include <EnergyPlus/DataSizing.hh>
66 : #include <EnergyPlus/EMSManager.hh>
67 : #include <EnergyPlus/FaultsManager.hh>
68 : #include <EnergyPlus/FluidProperties.hh>
69 : #include <EnergyPlus/GlobalNames.hh>
70 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
71 : #include <EnergyPlus/NodeInputManager.hh>
72 : #include <EnergyPlus/OutputProcessor.hh>
73 : #include <EnergyPlus/OutputReportPredefined.hh>
74 : #include <EnergyPlus/Plant/DataPlant.hh>
75 : #include <EnergyPlus/Plant/PlantLocation.hh>
76 : #include <EnergyPlus/PlantUtilities.hh>
77 : #include <EnergyPlus/UtilityRoutines.hh>
78 :
79 : namespace EnergyPlus::Boilers {
80 :
81 : // Module containing the routines dealing with the Boilers
82 :
83 : // MODULE INFORMATION:
84 : // AUTHOR Dan Fisher, Taecheol Kim
85 : // DATE WRITTEN 1998, 2000
86 :
87 : // PURPOSE OF THIS MODULE:
88 : // Perform boiler simulation for plant simulation
89 :
90 : // METHODOLOGY EMPLOYED:
91 : // The BLAST/DOE-2 empirical model based on mfg. data
92 :
93 4 : BoilerSpecs *BoilerSpecs::factory(EnergyPlusData &state, std::string const &objectName)
94 : {
95 : // Process the input data for boilers if it hasn't been done already
96 4 : if (state.dataBoilers->getBoilerInputFlag) {
97 2 : GetBoilerInput(state);
98 2 : state.dataBoilers->getBoilerInputFlag = false;
99 : }
100 : // Now look for this particular boiler in the list
101 4 : auto myBoiler = std::find_if(state.dataBoilers->Boiler.begin(), state.dataBoilers->Boiler.end(), [&objectName](const BoilerSpecs &boiler) {
102 7 : return boiler.Name == objectName;
103 : });
104 4 : if (myBoiler != state.dataBoilers->Boiler.end()) {
105 4 : return myBoiler;
106 : }
107 :
108 : // If we didn't find it, fatal
109 : ShowFatalError(state, format("LocalBoilerFactory: Error getting inputs for boiler named: {}", objectName)); // LCOV_EXCL_LINE
110 : // Shut up the compiler
111 : return nullptr; // LCOV_EXCL_LINE
112 : }
113 :
114 28908 : void BoilerSpecs::simulate(EnergyPlusData &state,
115 : [[maybe_unused]] const PlantLocation &calledFromLocation,
116 : [[maybe_unused]] bool const FirstHVACIteration,
117 : Real64 &CurLoad,
118 : bool const RunFlag)
119 : {
120 28908 : auto &sim_component(DataPlant::CompData::getPlantComponent(state, this->plantLoc));
121 28908 : this->InitBoiler(state);
122 28908 : this->CalcBoilerModel(state, CurLoad, RunFlag, sim_component.FlowCtrl);
123 28908 : this->UpdateBoilerRecords(state, CurLoad, RunFlag);
124 28908 : }
125 :
126 11 : void BoilerSpecs::getDesignCapacities([[maybe_unused]] EnergyPlusData &state,
127 : [[maybe_unused]] const PlantLocation &calledFromLocation,
128 : Real64 &MaxLoad,
129 : Real64 &MinLoad,
130 : Real64 &OptLoad)
131 : {
132 11 : MinLoad = this->NomCap * this->MinPartLoadRat;
133 11 : MaxLoad = this->NomCap * this->MaxPartLoadRat;
134 11 : OptLoad = this->NomCap * this->OptPartLoadRat;
135 11 : }
136 :
137 2 : void BoilerSpecs::getSizingFactor(Real64 &SizFactor)
138 : {
139 2 : SizFactor = this->SizFac;
140 2 : }
141 :
142 10 : void BoilerSpecs::onInitLoopEquip(EnergyPlusData &state, [[maybe_unused]] const PlantLocation &calledFromLocation)
143 : {
144 10 : this->InitBoiler(state);
145 10 : this->SizeBoiler(state);
146 10 : }
147 :
148 4 : void GetBoilerInput(EnergyPlusData &state)
149 : {
150 : // SUBROUTINE INFORMATION:
151 : // AUTHOR: Dan Fisher
152 : // DATE WRITTEN: April 1998
153 : // MODIFIED: R. Raustad - FSEC, June 2008: added boiler efficiency curve object
154 :
155 : // PURPOSE OF THIS SUBROUTINE:
156 : // get all boiler data from input file
157 :
158 : // METHODOLOGY EMPLOYED:
159 : // standard EnergyPlus input retrieval using input Processor
160 :
161 : // Locals
162 : static constexpr std::string_view RoutineName("GetBoilerInput: ");
163 : static constexpr std::string_view routineName = "GetBoilerInput";
164 :
165 4 : auto &s_ipsc = state.dataIPShortCut;
166 :
167 : // LOCAL VARIABLES
168 4 : bool ErrorsFound(false); // Flag to show errors were found during GetInput
169 :
170 : // GET NUMBER OF ALL EQUIPMENT
171 4 : s_ipsc->cCurrentModuleObject = "Boiler:HotWater";
172 4 : int numBoilers = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, s_ipsc->cCurrentModuleObject);
173 :
174 4 : if (numBoilers <= 0) {
175 0 : ShowSevereError(state, format("No {} Equipment specified in input file", s_ipsc->cCurrentModuleObject));
176 0 : ErrorsFound = true;
177 : }
178 :
179 : // See if load distribution manager has already gotten the input
180 4 : if (allocated(state.dataBoilers->Boiler)) {
181 0 : return;
182 : }
183 :
184 4 : state.dataBoilers->Boiler.allocate(numBoilers);
185 :
186 : // LOAD ARRAYS WITH CURVE FIT Boiler DATA
187 :
188 8 : for (int BoilerNum = 1; BoilerNum <= numBoilers; ++BoilerNum) {
189 : int NumAlphas; // Number of elements in the alpha array
190 : int NumNums; // Number of elements in the numeric array
191 : int IOStat; // IO Status when calling get input subroutine
192 8 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
193 4 : s_ipsc->cCurrentModuleObject,
194 : BoilerNum,
195 4 : s_ipsc->cAlphaArgs,
196 : NumAlphas,
197 4 : s_ipsc->rNumericArgs,
198 : NumNums,
199 : IOStat,
200 4 : s_ipsc->lNumericFieldBlanks,
201 4 : s_ipsc->lAlphaFieldBlanks,
202 4 : s_ipsc->cAlphaFieldNames,
203 4 : s_ipsc->cNumericFieldNames);
204 :
205 4 : ErrorObjectHeader eoh{routineName, s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)};
206 :
207 : // ErrorsFound will be set to True if problem was found, left untouched otherwise
208 4 : GlobalNames::VerifyUniqueBoilerName(
209 4 : state, s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1), ErrorsFound, s_ipsc->cCurrentModuleObject + " Name");
210 4 : auto &thisBoiler = state.dataBoilers->Boiler(BoilerNum);
211 4 : thisBoiler.Name = s_ipsc->cAlphaArgs(1);
212 4 : thisBoiler.Type = DataPlant::PlantEquipmentType::Boiler_Simple;
213 :
214 : // Validate fuel type input
215 4 : thisBoiler.FuelType = static_cast<Constant::eFuel>(getEnumValue(Constant::eFuelNamesUC, s_ipsc->cAlphaArgs(2)));
216 :
217 4 : thisBoiler.NomCap = s_ipsc->rNumericArgs(1);
218 4 : if (s_ipsc->rNumericArgs(1) == 0.0) {
219 0 : ShowSevereError(state, fmt::format("{}{}=\"{}\",", RoutineName, s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
220 0 : ShowContinueError(state, format("Invalid {}={:.2R}", s_ipsc->cNumericFieldNames(1), s_ipsc->rNumericArgs(1)));
221 0 : ShowContinueError(state, format("...{} must be greater than 0.0", s_ipsc->cNumericFieldNames(1)));
222 0 : ErrorsFound = true;
223 : }
224 4 : if (thisBoiler.NomCap == DataSizing::AutoSize) {
225 1 : thisBoiler.NomCapWasAutoSized = true;
226 : }
227 :
228 4 : thisBoiler.NomEffic = s_ipsc->rNumericArgs(2);
229 4 : if (s_ipsc->rNumericArgs(2) == 0.0) {
230 0 : ShowSevereError(state, fmt::format("{}{}=\"{}\",", RoutineName, s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
231 0 : ShowContinueError(state, format("Invalid {}={:.3R}", s_ipsc->cNumericFieldNames(2), s_ipsc->rNumericArgs(2)));
232 0 : ShowContinueError(state, format("...{} must be greater than 0.0", s_ipsc->cNumericFieldNames(2)));
233 0 : ErrorsFound = true;
234 4 : } else if (s_ipsc->rNumericArgs(2) > 1.0) {
235 0 : ShowWarningError(state,
236 0 : fmt::format("{} = {}: {}={} should not typically be greater than 1.",
237 0 : s_ipsc->cCurrentModuleObject,
238 0 : s_ipsc->cAlphaArgs(1),
239 0 : s_ipsc->cNumericFieldNames(2),
240 0 : s_ipsc->rNumericArgs(2)));
241 : }
242 :
243 4 : if (s_ipsc->cAlphaArgs(3) == "ENTERINGBOILER") {
244 0 : thisBoiler.CurveTempMode = TempMode::ENTERINGBOILERTEMP;
245 4 : } else if (s_ipsc->cAlphaArgs(3) == "LEAVINGBOILER") {
246 3 : thisBoiler.CurveTempMode = TempMode::LEAVINGBOILERTEMP;
247 : } else {
248 1 : thisBoiler.CurveTempMode = TempMode::NOTSET;
249 : }
250 :
251 4 : if (s_ipsc->lAlphaFieldBlanks(4)) {
252 : // Ok if this is empty?
253 3 : } else if ((thisBoiler.EfficiencyCurve = Curve::GetCurve(state, s_ipsc->cAlphaArgs(4))) == nullptr) {
254 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(4), s_ipsc->cAlphaArgs(4));
255 0 : ErrorsFound = true;
256 3 : } else if (thisBoiler.EfficiencyCurve->numDims != 1 && thisBoiler.EfficiencyCurve->numDims != 2) {
257 0 : Curve::ShowSevereCurveDims(state, eoh, s_ipsc->cAlphaFieldNames(4), s_ipsc->cAlphaArgs(4), "1 or 2", thisBoiler.EfficiencyCurve->numDims);
258 0 : ErrorsFound = true;
259 : } else
260 : // if curve uses temperature, make sure water temp mode has been set
261 3 : if (thisBoiler.EfficiencyCurve->numDims == 2) { // curve uses water temperature
262 0 : if (thisBoiler.CurveTempMode == TempMode::NOTSET) { // throw error
263 0 : if (!s_ipsc->lAlphaFieldBlanks(3)) {
264 0 : ShowSevereError(state, fmt::format("{}{}=\"{}\"", RoutineName, s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
265 0 : ShowContinueError(state, format("Invalid {}={}", s_ipsc->cAlphaFieldNames(3), s_ipsc->cAlphaArgs(3)));
266 0 : ShowContinueError(state,
267 0 : format("boilers.Boiler using curve type of {} must specify {}",
268 0 : Curve::objectNames[(int)thisBoiler.EfficiencyCurve->curveType],
269 0 : s_ipsc->cAlphaFieldNames(3)));
270 0 : ShowContinueError(state, "Available choices are EnteringBoiler or LeavingBoiler");
271 : } else {
272 0 : ShowSevereError(state, fmt::format("{}{}=\"{}\"", RoutineName, s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
273 0 : ShowContinueError(state, format("Field {} is blank", s_ipsc->cAlphaFieldNames(3)));
274 0 : ShowContinueError(state,
275 0 : format("boilers.Boiler using curve type of {} must specify either EnteringBoiler or LeavingBoiler",
276 0 : Curve::objectNames[(int)thisBoiler.EfficiencyCurve->curveType]));
277 : }
278 0 : ErrorsFound = true;
279 : }
280 : }
281 :
282 4 : thisBoiler.VolFlowRate = s_ipsc->rNumericArgs(3);
283 4 : if (thisBoiler.VolFlowRate == DataSizing::AutoSize) {
284 4 : thisBoiler.VolFlowRateWasAutoSized = true;
285 : }
286 4 : thisBoiler.MinPartLoadRat = s_ipsc->rNumericArgs(4);
287 4 : thisBoiler.MaxPartLoadRat = s_ipsc->rNumericArgs(5);
288 4 : thisBoiler.OptPartLoadRat = s_ipsc->rNumericArgs(6);
289 :
290 4 : thisBoiler.TempUpLimitBoilerOut = s_ipsc->rNumericArgs(7);
291 : // default to 99.9C if upper temperature limit is left blank.
292 4 : if (thisBoiler.TempUpLimitBoilerOut <= 0.0) {
293 0 : thisBoiler.TempUpLimitBoilerOut = 99.9;
294 : }
295 :
296 4 : thisBoiler.ParasiticElecLoad = s_ipsc->rNumericArgs(8);
297 4 : thisBoiler.ParasiticFuelCapacity = s_ipsc->rNumericArgs(10);
298 4 : if (thisBoiler.FuelType == Constant::eFuel::Electricity && thisBoiler.ParasiticFuelCapacity > 0) {
299 0 : ShowWarningError(state, fmt::format("{}{}=\"{}\"", RoutineName, s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
300 0 : ShowContinueError(state, format("{} should be zero when the fuel type is electricity.", s_ipsc->cNumericFieldNames(10)));
301 0 : ShowContinueError(state, "It will be ignored and the simulation continues.");
302 0 : thisBoiler.ParasiticFuelCapacity = 0.0;
303 : }
304 :
305 4 : thisBoiler.SizFac = s_ipsc->rNumericArgs(9);
306 4 : if (thisBoiler.SizFac == 0.0) {
307 0 : thisBoiler.SizFac = 1.0;
308 : }
309 :
310 4 : thisBoiler.BoilerInletNodeNum = NodeInputManager::GetOnlySingleNode(state,
311 4 : s_ipsc->cAlphaArgs(5),
312 : ErrorsFound,
313 : DataLoopNode::ConnectionObjectType::BoilerHotWater,
314 4 : s_ipsc->cAlphaArgs(1),
315 : DataLoopNode::NodeFluidType::Water,
316 : DataLoopNode::ConnectionType::Inlet,
317 : NodeInputManager::CompFluidStream::Primary,
318 : DataLoopNode::ObjectIsNotParent);
319 8 : thisBoiler.BoilerOutletNodeNum = NodeInputManager::GetOnlySingleNode(state,
320 4 : s_ipsc->cAlphaArgs(6),
321 : ErrorsFound,
322 : DataLoopNode::ConnectionObjectType::BoilerHotWater,
323 4 : s_ipsc->cAlphaArgs(1),
324 : DataLoopNode::NodeFluidType::Water,
325 : DataLoopNode::ConnectionType::Outlet,
326 : NodeInputManager::CompFluidStream::Primary,
327 : DataLoopNode::ObjectIsNotParent);
328 8 : BranchNodeConnections::TestCompSet(
329 4 : state, s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1), s_ipsc->cAlphaArgs(5), s_ipsc->cAlphaArgs(6), "Hot Water Nodes");
330 :
331 4 : if (s_ipsc->cAlphaArgs(7) == "CONSTANTFLOW") {
332 0 : thisBoiler.FlowMode = DataPlant::FlowMode::Constant;
333 4 : } else if (s_ipsc->cAlphaArgs(7) == "LEAVINGSETPOINTMODULATED") {
334 1 : thisBoiler.FlowMode = DataPlant::FlowMode::LeavingSetpointModulated;
335 3 : } else if (s_ipsc->cAlphaArgs(7) == "NOTMODULATED") {
336 3 : thisBoiler.FlowMode = DataPlant::FlowMode::NotModulated;
337 : } else {
338 0 : ShowSevereError(state, fmt::format("{}{}=\"{}\"", RoutineName, s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
339 0 : ShowContinueError(state, format("Invalid {}={}", s_ipsc->cAlphaFieldNames(7), s_ipsc->cAlphaArgs(7)));
340 0 : ShowContinueError(state, "Available choices are ConstantFlow, NotModulated, or LeavingSetpointModulated");
341 0 : ShowContinueError(state, "Flow mode NotModulated is assumed and the simulation continues.");
342 : // We will assume variable flow if not specified
343 0 : thisBoiler.FlowMode = DataPlant::FlowMode::NotModulated;
344 : }
345 :
346 4 : if (NumAlphas > 7) {
347 0 : thisBoiler.EndUseSubcategory = s_ipsc->cAlphaArgs(8);
348 : } else {
349 4 : thisBoiler.EndUseSubcategory = "Boiler"; // leave this as "boiler" instead of "general" like other end use subcategories since
350 : // it appears this way in existing output files.
351 : }
352 : }
353 :
354 4 : if (ErrorsFound) {
355 0 : ShowFatalError(state, format("{}{}", RoutineName, "Errors found in processing " + s_ipsc->cCurrentModuleObject + " input."));
356 : }
357 : }
358 :
359 3 : void BoilerSpecs::SetupOutputVars(EnergyPlusData &state)
360 : {
361 3 : std::string_view const sFuelType = Constant::eFuelNames[static_cast<int>(this->FuelType)];
362 6 : SetupOutputVariable(state,
363 : "Boiler Heating Rate",
364 : Constant::Units::W,
365 3 : this->BoilerLoad,
366 : OutputProcessor::TimeStepType::System,
367 : OutputProcessor::StoreType::Average,
368 3 : this->Name);
369 6 : SetupOutputVariable(state,
370 : "Boiler Heating Energy",
371 : Constant::Units::J,
372 3 : this->BoilerEnergy,
373 : OutputProcessor::TimeStepType::System,
374 : OutputProcessor::StoreType::Sum,
375 3 : this->Name,
376 : Constant::eResource::EnergyTransfer,
377 : OutputProcessor::Group::Plant,
378 : OutputProcessor::EndUseCat::Boilers);
379 9 : SetupOutputVariable(state,
380 6 : format("Boiler {} Rate", sFuelType),
381 : Constant::Units::W,
382 3 : this->FuelUsed,
383 : OutputProcessor::TimeStepType::System,
384 : OutputProcessor::StoreType::Average,
385 3 : this->Name);
386 9 : SetupOutputVariable(state,
387 6 : format("Boiler {} Energy", sFuelType),
388 : Constant::Units::J,
389 3 : this->FuelConsumed,
390 : OutputProcessor::TimeStepType::System,
391 : OutputProcessor::StoreType::Sum,
392 3 : this->Name,
393 3 : Constant::eFuel2eResource[(int)this->FuelType],
394 : OutputProcessor::Group::Plant,
395 : OutputProcessor::EndUseCat::Heating,
396 : this->EndUseSubcategory);
397 6 : SetupOutputVariable(state,
398 : "Boiler Inlet Temperature",
399 : Constant::Units::C,
400 3 : this->BoilerInletTemp,
401 : OutputProcessor::TimeStepType::System,
402 : OutputProcessor::StoreType::Average,
403 3 : this->Name);
404 6 : SetupOutputVariable(state,
405 : "Boiler Outlet Temperature",
406 : Constant::Units::C,
407 3 : this->BoilerOutletTemp,
408 : OutputProcessor::TimeStepType::System,
409 : OutputProcessor::StoreType::Average,
410 3 : this->Name);
411 6 : SetupOutputVariable(state,
412 : "Boiler Mass Flow Rate",
413 : Constant::Units::kg_s,
414 3 : this->BoilerMassFlowRate,
415 : OutputProcessor::TimeStepType::System,
416 : OutputProcessor::StoreType::Average,
417 3 : this->Name);
418 6 : SetupOutputVariable(state,
419 : "Boiler Ancillary Electricity Rate",
420 : Constant::Units::W,
421 3 : this->ParasiticElecPower,
422 : OutputProcessor::TimeStepType::System,
423 : OutputProcessor::StoreType::Average,
424 3 : this->Name);
425 6 : SetupOutputVariable(state,
426 : "Boiler Ancillary Electricity Energy",
427 : Constant::Units::J,
428 3 : this->ParasiticElecConsumption,
429 : OutputProcessor::TimeStepType::System,
430 : OutputProcessor::StoreType::Sum,
431 3 : this->Name,
432 : Constant::eResource::Electricity,
433 : OutputProcessor::Group::Plant,
434 : OutputProcessor::EndUseCat::Heating,
435 : "Boiler Parasitic");
436 3 : if (this->FuelType != Constant::eFuel::Electricity) {
437 9 : SetupOutputVariable(state,
438 6 : format("Boiler Ancillary {} Rate", sFuelType),
439 : Constant::Units::W,
440 3 : this->ParasiticFuelRate,
441 : OutputProcessor::TimeStepType::System,
442 : OutputProcessor::StoreType::Average,
443 3 : this->Name);
444 9 : SetupOutputVariable(state,
445 6 : format("Boiler Ancillary {} Energy", sFuelType),
446 : Constant::Units::J,
447 3 : this->ParasiticFuelConsumption,
448 : OutputProcessor::TimeStepType::System,
449 : OutputProcessor::StoreType::Sum,
450 3 : this->Name,
451 3 : Constant::eFuel2eResource[(int)this->FuelType],
452 : OutputProcessor::Group::Plant,
453 : OutputProcessor::EndUseCat::Heating,
454 : "Boiler Parasitic");
455 : }
456 6 : SetupOutputVariable(state,
457 : "Boiler Part Load Ratio",
458 : Constant::Units::None,
459 3 : this->BoilerPLR,
460 : OutputProcessor::TimeStepType::System,
461 : OutputProcessor::StoreType::Average,
462 3 : this->Name);
463 6 : SetupOutputVariable(state,
464 : "Boiler Efficiency",
465 : Constant::Units::None,
466 3 : this->BoilerEff,
467 : OutputProcessor::TimeStepType::System,
468 : OutputProcessor::StoreType::Average,
469 3 : this->Name);
470 3 : if (state.dataGlobal->AnyEnergyManagementSystemInModel) {
471 0 : SetupEMSInternalVariable(state, "Boiler Nominal Capacity", this->Name, "[W]", this->NomCap);
472 : }
473 3 : }
474 :
475 3 : void BoilerSpecs::oneTimeInit(EnergyPlusData &state)
476 : {
477 : // Locate the boilers on the plant loops for later usage
478 3 : bool errFlag = false;
479 9 : PlantUtilities::ScanPlantLoopsForObject(
480 6 : state, this->Name, DataPlant::PlantEquipmentType::Boiler_Simple, this->plantLoc, errFlag, _, this->TempUpLimitBoilerOut, _, _, _);
481 3 : if (errFlag) {
482 0 : ShowFatalError(state, "InitBoiler: Program terminated due to previous condition(s).");
483 : }
484 :
485 3 : if ((this->FlowMode == DataPlant::FlowMode::LeavingSetpointModulated) || (this->FlowMode == DataPlant::FlowMode::Constant)) {
486 : // reset flow priority
487 1 : DataPlant::CompData::getPlantComponent(state, this->plantLoc).FlowPriority = DataPlant::LoopFlowStatus::NeedyIfLoopOn;
488 : }
489 3 : }
490 :
491 9 : void BoilerSpecs::initEachEnvironment(EnergyPlusData &state)
492 : {
493 : static constexpr std::string_view RoutineName("BoilerSpecs::initEachEnvironment");
494 9 : Real64 const rho = state.dataPlnt->PlantLoop(this->plantLoc.loopNum).glycol->getDensity(state, Constant::HWInitConvTemp, RoutineName);
495 9 : this->DesMassFlowRate = this->VolFlowRate * rho;
496 :
497 9 : PlantUtilities::InitComponentNodes(state, 0.0, this->DesMassFlowRate, this->BoilerInletNodeNum, this->BoilerOutletNodeNum);
498 :
499 9 : if (this->FlowMode == DataPlant::FlowMode::LeavingSetpointModulated) { // check if setpoint on outlet node
500 4 : if ((state.dataLoopNodes->Node(this->BoilerOutletNodeNum).TempSetPoint == DataLoopNode::SensedNodeFlagValue) &&
501 0 : (state.dataLoopNodes->Node(this->BoilerOutletNodeNum).TempSetPointLo == DataLoopNode::SensedNodeFlagValue)) {
502 0 : if (!state.dataGlobal->AnyEnergyManagementSystemInModel) {
503 0 : if (!this->ModulatedFlowErrDone) {
504 0 : ShowWarningError(state, format("Missing temperature setpoint for LeavingSetpointModulated mode Boiler named {}", this->Name));
505 0 : ShowContinueError(
506 : state, " A temperature setpoint is needed at the outlet node of a boiler in variable flow mode, use a SetpointManager");
507 0 : ShowContinueError(state, " The overall loop setpoint will be assumed for Boiler. The simulation continues ... ");
508 0 : this->ModulatedFlowErrDone = true;
509 : }
510 : } else {
511 : // need call to EMS to check node
512 0 : bool FatalError = false; // but not really fatal yet, but should be.
513 0 : EMSManager::CheckIfNodeSetPointManagedByEMS(state, this->BoilerOutletNodeNum, HVAC::CtrlVarType::Temp, FatalError);
514 0 : state.dataLoopNodes->NodeSetpointCheck(this->BoilerOutletNodeNum).needsSetpointChecking = false;
515 0 : if (FatalError) {
516 0 : if (!this->ModulatedFlowErrDone) {
517 0 : ShowWarningError(state, format("Missing temperature setpoint for LeavingSetpointModulated mode Boiler named {}", this->Name));
518 0 : ShowContinueError(state, " A temperature setpoint is needed at the outlet node of a boiler in variable flow mode");
519 0 : ShowContinueError(state, " use a Setpoint Manager to establish a setpoint at the boiler outlet node ");
520 0 : ShowContinueError(state, " or use an EMS actuator to establish a setpoint at the boiler outlet node ");
521 0 : ShowContinueError(state, " The overall loop setpoint will be assumed for Boiler. The simulation continues ... ");
522 0 : this->ModulatedFlowErrDone = true;
523 : }
524 : }
525 : }
526 0 : this->ModulatedFlowSetToLoop = true; // this is for backward compatibility and could be removed
527 : }
528 : }
529 9 : }
530 :
531 28920 : void BoilerSpecs::InitBoiler(EnergyPlusData &state) // number of the current boiler being simulated
532 : {
533 :
534 : // SUBROUTINE INFORMATION:
535 : // AUTHOR Fred Buhl
536 : // DATE WRITTEN April 2002
537 : // RE-ENGINEERED Brent Griffith, rework for plant upgrade
538 :
539 : // PURPOSE OF THIS SUBROUTINE:
540 : // This subroutine is for initializations of the Boiler components
541 :
542 : // METHODOLOGY EMPLOYED:
543 : // Uses the status flags to trigger initializations.
544 :
545 : // Init more variables
546 28920 : if (this->MyFlag) {
547 3 : this->SetupOutputVars(state);
548 3 : this->oneTimeInit(state);
549 3 : this->MyFlag = false;
550 : }
551 :
552 28920 : if (this->MyEnvrnFlag && state.dataGlobal->BeginEnvrnFlag && (state.dataPlnt->PlantFirstSizesOkayToFinalize)) {
553 9 : this->initEachEnvironment(state);
554 9 : this->MyEnvrnFlag = false;
555 : }
556 :
557 28920 : if (!state.dataGlobal->BeginEnvrnFlag) {
558 28577 : this->MyEnvrnFlag = true;
559 : }
560 :
561 : // every iteration inits. (most in calc routine)
562 :
563 28920 : if ((this->FlowMode == DataPlant::FlowMode::LeavingSetpointModulated) && this->ModulatedFlowSetToLoop) {
564 : // fix for clumsy old input that worked because loop setpoint was spread.
565 : // could be removed with transition, testing , model change, period of being obsolete.
566 0 : if (state.dataPlnt->PlantLoop(this->plantLoc.loopNum).LoopDemandCalcScheme == DataPlant::LoopDemandCalcScheme::SingleSetPoint) {
567 0 : state.dataLoopNodes->Node(this->BoilerOutletNodeNum).TempSetPoint =
568 0 : state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(this->plantLoc.loopNum).TempSetPointNodeNum).TempSetPoint;
569 : } else { // DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand
570 0 : state.dataLoopNodes->Node(this->BoilerOutletNodeNum).TempSetPointLo =
571 0 : state.dataLoopNodes->Node(state.dataPlnt->PlantLoop(this->plantLoc.loopNum).TempSetPointNodeNum).TempSetPointLo;
572 : }
573 : }
574 28920 : }
575 :
576 14 : void BoilerSpecs::SizeBoiler(EnergyPlusData &state)
577 : {
578 :
579 : // SUBROUTINE INFORMATION:
580 : // AUTHOR Fred Buhl
581 : // DATE WRITTEN April 2002
582 : // MODIFIED November 2013 Daeho Kang, add component sizing table entries
583 :
584 : // PURPOSE OF THIS SUBROUTINE:
585 : // This subroutine is for sizing Boiler Components for which capacities and flow rates
586 : // have not been specified in the input.
587 :
588 : // METHODOLOGY EMPLOYED:
589 : // Obtains hot water flow rate from the plant sizing array. Calculates nominal capacity from
590 : // the hot water flow rate and the hot water loop design delta T.
591 :
592 : // SUBROUTINE PARAMETER DEFINITIONS:
593 : static constexpr std::string_view RoutineName("SizeBoiler");
594 :
595 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
596 14 : bool ErrorsFound(false); // If errors detected in input
597 :
598 : // grab some initial values for capacity and flow rate
599 14 : Real64 tmpNomCap = this->NomCap; // local nominal capacity cooling power
600 14 : Real64 tmpBoilerVolFlowRate = this->VolFlowRate; // local boiler design volume flow rate
601 :
602 14 : int const PltSizNum = state.dataPlnt->PlantLoop(this->plantLoc.loopNum).PlantSizNum; // Plant Sizing index corresponding to CurLoopNum
603 :
604 14 : if (PltSizNum > 0) {
605 14 : if (state.dataSize->PlantSizData(PltSizNum).DesVolFlowRate >= HVAC::SmallWaterVolFlow) {
606 :
607 12 : Real64 const rho = state.dataPlnt->PlantLoop(this->plantLoc.loopNum).glycol->getDensity(state, Constant::HWInitConvTemp, RoutineName);
608 12 : Real64 const Cp = state.dataPlnt->PlantLoop(this->plantLoc.loopNum).glycol->getSpecificHeat(state, Constant::HWInitConvTemp, RoutineName);
609 12 : tmpNomCap =
610 12 : Cp * rho * this->SizFac * state.dataSize->PlantSizData(PltSizNum).DeltaT * state.dataSize->PlantSizData(PltSizNum).DesVolFlowRate;
611 : } else {
612 2 : if (this->NomCapWasAutoSized) {
613 0 : tmpNomCap = 0.0;
614 : }
615 : }
616 14 : if (state.dataPlnt->PlantFirstSizesOkayToFinalize) {
617 6 : if (this->NomCapWasAutoSized) {
618 3 : this->NomCap = tmpNomCap;
619 3 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
620 1 : BaseSizer::reportSizerOutput(state, "Boiler:HotWater", this->Name, "Design Size Nominal Capacity [W]", tmpNomCap);
621 : }
622 3 : if (state.dataPlnt->PlantFirstSizesOkayToReport) {
623 1 : BaseSizer::reportSizerOutput(state, "Boiler:HotWater", this->Name, "Initial Design Size Nominal Capacity [W]", tmpNomCap);
624 : }
625 : } else { // Hard-sized with sizing data
626 3 : if (this->NomCap > 0.0 && tmpNomCap > 0.0) {
627 3 : Real64 const NomCapUser = this->NomCap; // Hardsized nominal capacity for reporting
628 3 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
629 2 : BaseSizer::reportSizerOutput(state,
630 : "Boiler:HotWater",
631 : this->Name,
632 : "Design Size Nominal Capacity [W]",
633 : tmpNomCap,
634 : "User-Specified Nominal Capacity [W]",
635 : NomCapUser);
636 2 : if (state.dataGlobal->DisplayExtraWarnings) {
637 1 : if ((std::abs(tmpNomCap - NomCapUser) / NomCapUser) > state.dataSize->AutoVsHardSizingThreshold) {
638 1 : ShowMessage(state, format("SizeBoilerHotWater: Potential issue with equipment sizing for {}", this->Name));
639 1 : ShowContinueError(state, format("User-Specified Nominal Capacity of {:.2R} [W]", NomCapUser));
640 1 : ShowContinueError(state, format("differs from Design Size Nominal Capacity of {:.2R} [W]", tmpNomCap));
641 2 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
642 3 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
643 : }
644 : }
645 : }
646 : }
647 : }
648 : }
649 : } else {
650 0 : if (this->NomCapWasAutoSized && state.dataPlnt->PlantFirstSizesOkayToFinalize) {
651 0 : ShowSevereError(state, "Autosizing of Boiler nominal capacity requires a loop Sizing:Plant object");
652 0 : ShowContinueError(state, format("Occurs in Boiler object={}", this->Name));
653 0 : ErrorsFound = true;
654 : }
655 0 : if (!this->NomCapWasAutoSized && state.dataPlnt->PlantFinalSizesOkayToReport && (this->NomCap > 0.0)) { // Hard-sized with no sizing data
656 0 : BaseSizer::reportSizerOutput(state, "Boiler:HotWater", this->Name, "User-Specified Nominal Capacity [W]", this->NomCap);
657 : }
658 : }
659 :
660 14 : if (PltSizNum > 0) {
661 14 : if (state.dataSize->PlantSizData(PltSizNum).DesVolFlowRate >= HVAC::SmallWaterVolFlow) {
662 12 : tmpBoilerVolFlowRate = state.dataSize->PlantSizData(PltSizNum).DesVolFlowRate * this->SizFac;
663 : } else {
664 2 : if (this->VolFlowRateWasAutoSized) {
665 2 : tmpBoilerVolFlowRate = 0.0;
666 : }
667 : }
668 14 : if (state.dataPlnt->PlantFirstSizesOkayToFinalize) {
669 6 : if (this->VolFlowRateWasAutoSized) {
670 5 : this->VolFlowRate = tmpBoilerVolFlowRate;
671 5 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
672 3 : BaseSizer::reportSizerOutput(
673 : state, "Boiler:HotWater", this->Name, "Design Size Design Water Flow Rate [m3/s]", tmpBoilerVolFlowRate);
674 : }
675 5 : if (state.dataPlnt->PlantFirstSizesOkayToReport) {
676 1 : BaseSizer::reportSizerOutput(
677 : state, "Boiler:HotWater", this->Name, "Initial Design Size Design Water Flow Rate [m3/s]", tmpBoilerVolFlowRate);
678 : }
679 : } else {
680 1 : if (this->VolFlowRate > 0.0 && tmpBoilerVolFlowRate > 0.0) {
681 1 : Real64 VolFlowRateUser = this->VolFlowRate; // Hardsized volume flow for reporting
682 1 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
683 0 : BaseSizer::reportSizerOutput(state,
684 : "Boiler:HotWater",
685 : this->Name,
686 : "Design Size Design Water Flow Rate [m3/s]",
687 : tmpBoilerVolFlowRate,
688 : "User-Specified Design Water Flow Rate [m3/s]",
689 : VolFlowRateUser);
690 0 : if (state.dataGlobal->DisplayExtraWarnings) {
691 0 : if ((std::abs(tmpBoilerVolFlowRate - VolFlowRateUser) / VolFlowRateUser) > state.dataSize->AutoVsHardSizingThreshold) {
692 0 : ShowMessage(state, format("SizeBoilerHotWater: Potential issue with equipment sizing for {}", this->Name));
693 0 : ShowContinueError(state, format("User-Specified Design Water Flow Rate of {:.2R} [m3/s]", VolFlowRateUser));
694 0 : ShowContinueError(state,
695 0 : format("differs from Design Size Design Water Flow Rate of {:.2R} [m3/s]", tmpBoilerVolFlowRate));
696 0 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
697 0 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
698 : }
699 : }
700 : }
701 1 : tmpBoilerVolFlowRate = VolFlowRateUser;
702 : }
703 : }
704 : }
705 : } else {
706 0 : if (this->VolFlowRateWasAutoSized && state.dataPlnt->PlantFirstSizesOkayToFinalize) {
707 0 : ShowSevereError(state, "Autosizing of Boiler design flow rate requires a loop Sizing:Plant object");
708 0 : ShowContinueError(state, format("Occurs in Boiler object={}", this->Name));
709 0 : ErrorsFound = true;
710 : }
711 0 : if (!this->VolFlowRateWasAutoSized && state.dataPlnt->PlantFinalSizesOkayToReport &&
712 0 : (this->VolFlowRate > 0.0)) { // Hard-sized with no sizing data
713 0 : BaseSizer::reportSizerOutput(state, "Boiler:HotWater", this->Name, "User-Specified Design Water Flow Rate [m3/s]", this->VolFlowRate);
714 : }
715 : }
716 :
717 14 : PlantUtilities::RegisterPlantCompDesignFlow(state, this->BoilerInletNodeNum, tmpBoilerVolFlowRate);
718 :
719 14 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
720 : // create predefined report
721 3 : std::string const equipName = this->Name;
722 3 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchMechType, equipName, "Boiler:HotWater");
723 3 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchMechNomEff, equipName, this->NomEffic);
724 3 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchMechNomCap, equipName, this->NomCap);
725 :
726 : // Std 229 Boilers new report table
727 3 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchBoilerType, equipName, "Boiler:HotWater");
728 3 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchBoilerRefCap, equipName, this->NomCap);
729 3 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchBoilerRefEff, equipName, this->NomEffic);
730 3 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchBoilerRatedCap, equipName, this->NomCap);
731 3 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchBoilerRatedEff, equipName, this->NomEffic);
732 6 : OutputReportPredefined::PreDefTableEntry(state,
733 3 : state.dataOutRptPredefined->pdchBoilerPlantloopName,
734 : equipName,
735 6 : this->plantLoc.loopNum > 0 ? state.dataPlnt->PlantLoop(this->plantLoc.loopNum).Name : "N/A");
736 6 : OutputReportPredefined::PreDefTableEntry(
737 : state,
738 3 : state.dataOutRptPredefined->pdchBoilerPlantloopBranchName,
739 : equipName,
740 3 : this->plantLoc.loopNum > 0
741 6 : ? state.dataPlnt->PlantLoop(this->plantLoc.loopNum).LoopSide(this->plantLoc.loopSideNum).Branch(this->plantLoc.branchNum).Name
742 : : "N/A");
743 3 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchBoilerMinPLR, equipName, this->MinPartLoadRat);
744 6 : OutputReportPredefined::PreDefTableEntry(
745 6 : state, state.dataOutRptPredefined->pdchBoilerFuelType, equipName, Constant::eFuelNames[static_cast<int>(this->FuelType)]);
746 3 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchBoilerParaElecLoad, equipName, this->ParasiticElecLoad);
747 3 : }
748 :
749 14 : if (ErrorsFound) {
750 0 : ShowFatalError(state, "Preceding sizing errors cause program termination");
751 : }
752 14 : }
753 :
754 28909 : void BoilerSpecs::CalcBoilerModel(EnergyPlusData &state,
755 : Real64 const MyLoad, // W - hot water demand to be met by boiler
756 : bool const RunFlag, // TRUE if boiler operating
757 : DataBranchAirLoopPlant::ControlType const EquipFlowCtrl // Flow control mode for the equipment
758 : )
759 : {
760 : // SUBROUTINE INFORMATION:
761 : // AUTHOR Dan Fisher
762 : // DATE WRITTEN April 1999
763 : // MODIFIED Taecheol Kim,May 2000
764 : // Jun. 2008, R. Raustad, FSEC. Added boiler efficiency curve object
765 : // Aug. 2011, B. Griffith, NREL. Added switch for temperature to use in curve
766 : // Nov. 2016, R. Zhang, LBNL. Applied the boiler fouling fault model
767 :
768 : // PURPOSE OF THIS SUBROUTINE:
769 : // This subroutine calculates the boiler fuel consumption and the associated
770 : // hot water demand met by the boiler
771 :
772 : // METHODOLOGY EMPLOYED:
773 : // The model is based on a single combustion efficiency (=1 for electric)
774 : // and a second order polynomial fit of performance data to obtain part
775 : // load performance
776 :
777 : // SUBROUTINE PARAMETER DEFINITIONS:
778 : static constexpr std::string_view RoutineName("CalcBoilerModel");
779 :
780 : // clean up some operating conditions, may not be necessary
781 28909 : this->BoilerLoad = 0.0;
782 28909 : this->ParasiticElecPower = 0.0;
783 28909 : this->BoilerMassFlowRate = 0.0;
784 :
785 28909 : int const BoilerInletNode = this->BoilerInletNodeNum;
786 28909 : int const BoilerOutletNode = this->BoilerOutletNodeNum;
787 28909 : Real64 BoilerNomCap = this->NomCap; // W - boiler nominal capacity
788 28909 : Real64 const BoilerMaxPLR = this->MaxPartLoadRat; // boiler maximum part load ratio
789 28909 : Real64 const BoilerMinPLR = this->MinPartLoadRat; // boiler minimum part load ratio
790 28909 : Real64 BoilerNomEff = this->NomEffic; // boiler efficiency
791 28909 : Real64 const TempUpLimitBout = this->TempUpLimitBoilerOut; // C - boiler high temperature limit
792 28909 : Real64 const BoilerMassFlowRateMax = this->DesMassFlowRate; // Max Design Boiler Mass Flow Rate converted from Volume Flow Rate
793 :
794 28909 : Real64 Cp = state.dataPlnt->PlantLoop(this->plantLoc.loopNum)
795 28909 : .glycol->getSpecificHeat(state, state.dataLoopNodes->Node(BoilerInletNode).Temp, RoutineName);
796 :
797 : // If the specified load is 0.0 or the boiler should not run then we leave this subroutine. Before leaving
798 : // if the component control is SERIESACTIVE we set the component flow to inlet flow so that flow resolver
799 : // will not shut down the branch
800 28909 : if (MyLoad <= 0.0 || !RunFlag) {
801 20066 : if (EquipFlowCtrl == DataBranchAirLoopPlant::ControlType::SeriesActive) {
802 0 : this->BoilerMassFlowRate = state.dataLoopNodes->Node(BoilerInletNode).MassFlowRate;
803 : }
804 20066 : return;
805 : }
806 :
807 : // If there is a fault of boiler fouling
808 8843 : if (this->FaultyBoilerFoulingFlag && (!state.dataGlobal->WarmupFlag) && (!state.dataGlobal->DoingSizing) &&
809 0 : (!state.dataGlobal->KickOffSimulation)) {
810 0 : int FaultIndex = this->FaultyBoilerFoulingIndex;
811 0 : Real64 NomCap_ff = BoilerNomCap;
812 0 : Real64 BoilerNomEff_ff = BoilerNomEff;
813 :
814 : // calculate the Faulty Boiler Fouling Factor using fault information
815 0 : this->FaultyBoilerFoulingFactor = state.dataFaultsMgr->FaultsBoilerFouling(FaultIndex).CalFoulingFactor(state);
816 :
817 : // update the boiler nominal capacity at faulty cases
818 0 : BoilerNomCap = NomCap_ff * this->FaultyBoilerFoulingFactor;
819 0 : BoilerNomEff = BoilerNomEff_ff * this->FaultyBoilerFoulingFactor;
820 : }
821 :
822 : // Set the current load equal to the boiler load
823 8843 : this->BoilerLoad = MyLoad;
824 :
825 : // Initialize the delta temperature to zero
826 : Real64 BoilerDeltaTemp; // C - boiler inlet to outlet temperature difference, set in all necessary code paths so no initialization required
827 :
828 8843 : if (state.dataPlnt->PlantLoop(this->plantLoc.loopNum).LoopSide(this->plantLoc.loopSideNum).FlowLock == DataPlant::FlowLock::Unlocked) {
829 : // Either set the flow to the Constant value or calculate the flow for the variable volume
830 4422 : if ((this->FlowMode == DataPlant::FlowMode::Constant) || (this->FlowMode == DataPlant::FlowMode::NotModulated)) {
831 : // Then find the flow rate and outlet temp
832 4134 : this->BoilerMassFlowRate = BoilerMassFlowRateMax;
833 4134 : PlantUtilities::SetComponentFlowRate(state, this->BoilerMassFlowRate, BoilerInletNode, BoilerOutletNode, this->plantLoc);
834 :
835 4134 : if ((this->BoilerMassFlowRate != 0.0) && (MyLoad > 0.0)) {
836 4134 : BoilerDeltaTemp = this->BoilerLoad / this->BoilerMassFlowRate / Cp;
837 : } else {
838 0 : BoilerDeltaTemp = 0.0;
839 : }
840 4134 : this->BoilerOutletTemp = BoilerDeltaTemp + state.dataLoopNodes->Node(BoilerInletNode).Temp;
841 :
842 288 : } else if (this->FlowMode == DataPlant::FlowMode::LeavingSetpointModulated) {
843 : // Calculate the Delta Temp from the inlet temp to the boiler outlet setpoint
844 : // Then find the flow rate and outlet temp
845 :
846 288 : if (state.dataPlnt->PlantLoop(this->plantLoc.loopNum).LoopDemandCalcScheme == DataPlant::LoopDemandCalcScheme::SingleSetPoint) {
847 288 : BoilerDeltaTemp = state.dataLoopNodes->Node(BoilerOutletNode).TempSetPoint - state.dataLoopNodes->Node(BoilerInletNode).Temp;
848 : } else { // DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand
849 0 : BoilerDeltaTemp = state.dataLoopNodes->Node(BoilerOutletNode).TempSetPointLo - state.dataLoopNodes->Node(BoilerInletNode).Temp;
850 : }
851 :
852 288 : this->BoilerOutletTemp = BoilerDeltaTemp + state.dataLoopNodes->Node(BoilerInletNode).Temp;
853 :
854 288 : if ((BoilerDeltaTemp > 0.0) && (this->BoilerLoad > 0.0)) {
855 288 : this->BoilerMassFlowRate = this->BoilerLoad / Cp / BoilerDeltaTemp;
856 288 : this->BoilerMassFlowRate = std::min(BoilerMassFlowRateMax, this->BoilerMassFlowRate);
857 : } else {
858 0 : this->BoilerMassFlowRate = 0.0;
859 : }
860 288 : PlantUtilities::SetComponentFlowRate(state, this->BoilerMassFlowRate, BoilerInletNode, BoilerOutletNode, this->plantLoc);
861 :
862 : } // End of Constant/Variable Flow If Block
863 :
864 : } else { // If FlowLock is True
865 : // Set the boiler flow rate from inlet node and then check performance
866 4421 : this->BoilerMassFlowRate = state.dataLoopNodes->Node(BoilerInletNode).MassFlowRate;
867 :
868 4421 : if ((MyLoad > 0.0) && (this->BoilerMassFlowRate > 0.0)) { // this boiler has a heat load
869 4421 : this->BoilerLoad = MyLoad;
870 4421 : if (this->BoilerLoad > BoilerNomCap * BoilerMaxPLR) {
871 0 : this->BoilerLoad = BoilerNomCap * BoilerMaxPLR;
872 : }
873 4421 : if (this->BoilerLoad < BoilerNomCap * BoilerMinPLR) {
874 0 : this->BoilerLoad = BoilerNomCap * BoilerMinPLR;
875 : }
876 4421 : this->BoilerOutletTemp = state.dataLoopNodes->Node(BoilerInletNode).Temp + this->BoilerLoad / (this->BoilerMassFlowRate * Cp);
877 : } else {
878 0 : this->BoilerLoad = 0.0;
879 0 : this->BoilerOutletTemp = state.dataLoopNodes->Node(BoilerInletNode).Temp;
880 : }
881 : }
882 :
883 : // Limit BoilerOutletTemp. If > max temp, trip boiler off
884 8843 : if (this->BoilerOutletTemp > TempUpLimitBout) {
885 0 : this->BoilerLoad = 0.0;
886 0 : this->BoilerOutletTemp = state.dataLoopNodes->Node(BoilerInletNode).Temp;
887 : }
888 8843 : this->BoilerPLR = this->BoilerLoad / BoilerNomCap; // operating part load ratio
889 8843 : this->BoilerPLR = std::min(this->BoilerPLR, BoilerMaxPLR);
890 8843 : this->BoilerPLR = std::max(this->BoilerPLR, BoilerMinPLR);
891 :
892 : // calculate theoretical fuel use based on nominal thermal efficiency
893 8843 : Real64 const TheorFuelUse = this->BoilerLoad / BoilerNomEff; // Theoretical (stoichiometric) fuel use
894 8843 : Real64 EffCurveOutput = 1.0; // Output of boiler efficiency curve
895 :
896 : // calculate normalized efficiency based on curve object type
897 8843 : if (this->EfficiencyCurve != nullptr) {
898 8843 : if (this->EfficiencyCurve->numDims == 2) {
899 0 : if (this->CurveTempMode == TempMode::ENTERINGBOILERTEMP) {
900 0 : EffCurveOutput = this->EfficiencyCurve->value(state, this->BoilerPLR, state.dataLoopNodes->Node(BoilerInletNode).Temp);
901 0 : } else if (this->CurveTempMode == TempMode::LEAVINGBOILERTEMP) {
902 0 : EffCurveOutput = this->EfficiencyCurve->value(state, this->BoilerPLR, this->BoilerOutletTemp);
903 : }
904 : } else {
905 8843 : EffCurveOutput = this->EfficiencyCurve->value(state, this->BoilerPLR);
906 : }
907 : }
908 8843 : BoilerEff = EffCurveOutput * BoilerNomEff;
909 :
910 : // warn if efficiency curve produces zero or negative results
911 8843 : if (!state.dataGlobal->WarmupFlag && EffCurveOutput <= 0.0) {
912 0 : if (this->BoilerLoad > 0.0) {
913 0 : if (this->EffCurveOutputError < 1) {
914 0 : ++this->EffCurveOutputError;
915 0 : ShowWarningError(state, format("Boiler:HotWater \"{}\"", this->Name));
916 0 : ShowContinueError(state, "...Normalized Boiler Efficiency Curve output is less than or equal to 0.");
917 0 : ShowContinueError(state, format("...Curve input x value (PLR) = {:.5T}", this->BoilerPLR));
918 0 : if (this->EfficiencyCurve->numDims == 2) {
919 0 : if (this->CurveTempMode == TempMode::ENTERINGBOILERTEMP) {
920 0 : ShowContinueError(state, format("...Curve input y value (Tinlet) = {:.2T}", state.dataLoopNodes->Node(BoilerInletNode).Temp));
921 0 : } else if (this->CurveTempMode == TempMode::LEAVINGBOILERTEMP) {
922 0 : ShowContinueError(state, format("...Curve input y value (Toutlet) = {:.2T}", this->BoilerOutletTemp));
923 : }
924 : }
925 0 : ShowContinueError(state, format("...Curve output (normalized eff) = {:.5T}", EffCurveOutput));
926 0 : ShowContinueError(state,
927 0 : format("...Calculated Boiler efficiency = {:.5T} (Boiler efficiency = Nominal Thermal Efficiency * Normalized "
928 : "Boiler Efficiency Curve output)",
929 0 : BoilerEff));
930 0 : ShowContinueErrorTimeStamp(state, "...Curve output reset to 0.01 and simulation continues.");
931 : } else {
932 0 : ShowRecurringWarningErrorAtEnd(state,
933 0 : "Boiler:HotWater \"" + this->Name +
934 : "\": Boiler Efficiency Curve output is less than or equal to 0 warning continues...",
935 0 : this->EffCurveOutputIndex,
936 : EffCurveOutput,
937 : EffCurveOutput);
938 : }
939 : }
940 0 : EffCurveOutput = 0.01;
941 : }
942 :
943 : // warn if overall efficiency greater than 1.1
944 8843 : if (!state.dataGlobal->WarmupFlag && BoilerEff > 1.1) {
945 0 : if (this->BoilerLoad > 0.0 && this->EfficiencyCurve != nullptr &&
946 0 : NomEffic <= 1.0) { // NomEffic > 1 warning occurs elsewhere; avoid cascading warnings
947 0 : if (this->CalculatedEffError < 1) {
948 0 : ++this->CalculatedEffError;
949 0 : ShowWarningError(state, format("Boiler:HotWater \"{}\"", this->Name));
950 0 : ShowContinueError(state, "...Calculated Boiler Efficiency is greater than 1.1.");
951 0 : ShowContinueError(state, "...Boiler Efficiency calculations shown below.");
952 0 : ShowContinueError(state, format("...Curve input x value (PLR) = {:.5T}", this->BoilerPLR));
953 0 : if (this->EfficiencyCurve->numDims == 2) {
954 0 : if (this->CurveTempMode == TempMode::ENTERINGBOILERTEMP) {
955 0 : ShowContinueError(state, format("...Curve input y value (Tinlet) = {:.2T}", state.dataLoopNodes->Node(BoilerInletNode).Temp));
956 0 : } else if (this->CurveTempMode == TempMode::LEAVINGBOILERTEMP) {
957 0 : ShowContinueError(state, format("...Curve input y value (Toutlet) = {:.2T}", this->BoilerOutletTemp));
958 : }
959 : }
960 0 : ShowContinueError(state, format("...Curve output (normalized eff) = {:.5T}", EffCurveOutput));
961 0 : ShowContinueError(state,
962 0 : format("...Calculated Boiler efficiency = {:.5T} (Boiler efficiency = Nominal Thermal Efficiency * Normalized "
963 : "Boiler Efficiency Curve output)",
964 0 : BoilerEff));
965 0 : ShowContinueErrorTimeStamp(state, "...Curve output reset to 1.1 and simulation continues.");
966 : } else {
967 0 : ShowRecurringWarningErrorAtEnd(state,
968 0 : "Boiler:HotWater \"" + this->Name +
969 : "\": Calculated Boiler Efficiency is greater than 1.1 warning continues...",
970 0 : this->CalculatedEffIndex,
971 0 : BoilerEff,
972 0 : BoilerEff);
973 : }
974 : }
975 0 : EffCurveOutput = 1.1;
976 : }
977 :
978 : // calculate fuel used based on normalized boiler efficiency curve (=1 when no curve used)
979 8843 : this->FuelUsed = TheorFuelUse / EffCurveOutput;
980 8843 : if (this->BoilerLoad > 0.0) {
981 8843 : this->ParasiticElecPower = this->ParasiticElecLoad * this->BoilerPLR;
982 : }
983 8843 : this->ParasiticFuelRate = this->ParasiticFuelCapacity * (1.0 - this->BoilerPLR);
984 : }
985 :
986 28908 : void BoilerSpecs::UpdateBoilerRecords(EnergyPlusData &state,
987 : Real64 const MyLoad, // boiler operating load
988 : bool const RunFlag // boiler on when TRUE
989 : )
990 : {
991 : // SUBROUTINE INFORMATION:
992 : // AUTHOR: Dan Fisher
993 : // DATE WRITTEN: October 1998
994 :
995 : // PURPOSE OF THIS SUBROUTINE:
996 : // boiler simulation reporting
997 :
998 28908 : Real64 const ReportingConstant = state.dataHVACGlobal->TimeStepSysSec;
999 28908 : int const BoilerInletNode = this->BoilerInletNodeNum;
1000 28908 : int const BoilerOutletNode = this->BoilerOutletNodeNum;
1001 :
1002 28908 : if (MyLoad <= 0 || !RunFlag) {
1003 20066 : PlantUtilities::SafeCopyPlantNode(state, BoilerInletNode, BoilerOutletNode);
1004 20066 : state.dataLoopNodes->Node(BoilerOutletNode).Temp = state.dataLoopNodes->Node(BoilerInletNode).Temp;
1005 20066 : this->BoilerOutletTemp = state.dataLoopNodes->Node(BoilerInletNode).Temp;
1006 20066 : this->BoilerLoad = 0.0;
1007 20066 : this->FuelUsed = 0.0;
1008 20066 : this->ParasiticElecPower = 0.0;
1009 20066 : this->BoilerPLR = 0.0;
1010 20066 : this->BoilerEff = 0.0;
1011 : } else {
1012 8842 : PlantUtilities::SafeCopyPlantNode(state, BoilerInletNode, BoilerOutletNode);
1013 8842 : state.dataLoopNodes->Node(BoilerOutletNode).Temp = this->BoilerOutletTemp;
1014 : }
1015 :
1016 28908 : this->BoilerInletTemp = state.dataLoopNodes->Node(BoilerInletNode).Temp;
1017 28908 : this->BoilerMassFlowRate = state.dataLoopNodes->Node(BoilerOutletNode).MassFlowRate;
1018 28908 : this->BoilerEnergy = this->BoilerLoad * ReportingConstant;
1019 28908 : this->FuelConsumed = this->FuelUsed * ReportingConstant;
1020 28908 : this->ParasiticElecConsumption = this->ParasiticElecPower * ReportingConstant;
1021 28908 : this->ParasiticFuelConsumption = this->ParasiticFuelRate * ReportingConstant;
1022 28908 : }
1023 :
1024 : } // namespace EnergyPlus::Boilers
|