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