Line data Source code
1 : // EnergyPlus, Copyright (c) 1996-2025, The Board of Trustees of the University of Illinois,
2 : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
3 : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
4 : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
5 : // contributors. All rights reserved.
6 : //
7 : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
8 : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
9 : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
10 : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
11 : // derivative works, and perform publicly and display publicly, and to permit others to do so.
12 : //
13 : // Redistribution and use in source and binary forms, with or without modification, are permitted
14 : // provided that the following conditions are met:
15 : //
16 : // (1) Redistributions of source code must retain the above copyright notice, this list of
17 : // conditions and the following disclaimer.
18 : //
19 : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
20 : // conditions and the following disclaimer in the documentation and/or other materials
21 : // provided with the distribution.
22 : //
23 : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
24 : // the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
25 : // used to endorse or promote products derived from this software without specific prior
26 : // written permission.
27 : //
28 : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
29 : // without changes from the version obtained under this License, or (ii) Licensee makes a
30 : // reference solely to the software portion of its product, Licensee must refer to the
31 : // software as "EnergyPlus version X" software, where "X" is the version number Licensee
32 : // obtained under this License and may not use a different name for the software. Except as
33 : // specifically required in this Section (4), Licensee shall not use in a company name, a
34 : // product name, in advertising, publicity, or other promotional activities any name, trade
35 : // name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
36 : // similar designation, without the U.S. Department of Energy's prior written consent.
37 : //
38 : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
39 : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
40 : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
41 : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
43 : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44 : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
45 : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46 : // POSSIBILITY OF SUCH DAMAGE.
47 :
48 : // C++ Headers
49 : #include <cmath>
50 :
51 : // ObjexxFCL Headers
52 : #include <ObjexxFCL/Array.functions.hh>
53 : // #include <ObjexxFCL/Fmath.hh>
54 :
55 : // EnergyPlus Headers
56 : #include <EnergyPlus/Autosizing/Base.hh>
57 : #include <EnergyPlus/BoilerSteam.hh>
58 : #include <EnergyPlus/BranchNodeConnections.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/FluidProperties.hh>
68 : #include <EnergyPlus/GlobalNames.hh>
69 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
70 : #include <EnergyPlus/NodeInputManager.hh>
71 : #include <EnergyPlus/OutputProcessor.hh>
72 : #include <EnergyPlus/OutputReportPredefined.hh>
73 : #include <EnergyPlus/Plant/DataPlant.hh>
74 : #include <EnergyPlus/PlantUtilities.hh>
75 : #include <EnergyPlus/UtilityRoutines.hh>
76 :
77 : namespace EnergyPlus {
78 :
79 : namespace BoilerSteam {
80 :
81 : // Module containing the routines dealing with the Boilers
82 :
83 : // MODULE INFORMATION:
84 : // AUTHOR Rahul Chillar
85 : // DATE WRITTEN Dec 2004
86 :
87 : // PURPOSE OF THIS MODULE:
88 : // Performs steam boiler simulation for plant simulation
89 :
90 1 : BoilerSpecs *BoilerSpecs::factory(EnergyPlusData &state, std::string const &objectName)
91 : {
92 : // Process the input data for boilers if it hasn't been done already
93 1 : if (state.dataBoilerSteam->getSteamBoilerInput) {
94 1 : GetBoilerInput(state);
95 1 : state.dataBoilerSteam->getSteamBoilerInput = false;
96 : }
97 :
98 : // Now look for this particular pipe in the list
99 1 : auto myBoiler = std::find_if(state.dataBoilerSteam->Boiler.begin(),
100 1 : state.dataBoilerSteam->Boiler.end(),
101 1 : [&objectName](const BoilerSpecs &boiler) { return boiler.Name == objectName; });
102 1 : if (myBoiler != state.dataBoilerSteam->Boiler.end()) return myBoiler;
103 :
104 : // If we didn't find it, fatal
105 : ShowFatalError(state, format("LocalBoilerSteamFactory: Error getting inputs for steam boiler named: {}", objectName)); // LCOV_EXCL_LINE
106 : // Shut up the compiler
107 : return nullptr; // LCOV_EXCL_LINE
108 : }
109 :
110 1 : void BoilerSpecs::simulate(
111 : EnergyPlusData &state, [[maybe_unused]] const PlantLocation &calledFromLocation, bool FirstHVACIteration, Real64 &CurLoad, bool RunFlag)
112 : {
113 1 : this->initialize(state);
114 1 : auto &sim_component(DataPlant::CompData::getPlantComponent(state, this->plantLoc));
115 1 : this->calculate(state, CurLoad, RunFlag, sim_component.FlowCtrl);
116 1 : this->update(state, CurLoad, RunFlag, FirstHVACIteration);
117 1 : }
118 :
119 : void
120 1 : BoilerSpecs::getDesignCapacities([[maybe_unused]] EnergyPlusData &state, const PlantLocation &, Real64 &MaxLoad, Real64 &MinLoad, Real64 &OptLoad)
121 : {
122 1 : MinLoad = this->NomCap * this->MinPartLoadRat;
123 1 : MaxLoad = this->NomCap * this->MaxPartLoadRat;
124 1 : OptLoad = this->NomCap * this->OptPartLoadRat;
125 1 : }
126 :
127 0 : void BoilerSpecs::getSizingFactor(Real64 &sizFac)
128 : {
129 0 : sizFac = this->SizFac;
130 0 : }
131 :
132 0 : void BoilerSpecs::onInitLoopEquip(EnergyPlusData &state, const PlantLocation &)
133 : {
134 0 : this->initialize(state);
135 0 : this->autosize(state);
136 0 : }
137 :
138 3 : void GetBoilerInput(EnergyPlusData &state)
139 : {
140 : // SUBROUTINE INFORMATION:
141 : // AUTHOR Rahul Chillar
142 : // DATE WRITTEN Dec 2004
143 :
144 : // PURPOSE OF THIS SUBROUTINE:
145 : // Get all boiler data from input file
146 :
147 : // Locals
148 : static constexpr std::string_view RoutineName("GetBoilerInput: ");
149 : static constexpr std::string_view routineName = "GetBoilerInput";
150 :
151 : // LOCAL VARIABLES
152 : int BoilerNum; // boiler identifier
153 : int NumAlphas; // Number of elements in the alpha array
154 : int NumNums; // Number of elements in the numeric array
155 : int IOStat; // IO Status when calling get input subroutine
156 3 : bool ErrorsFound(false);
157 :
158 3 : state.dataIPShortCut->cCurrentModuleObject = "Boiler:Steam";
159 3 : int numBoilers = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, state.dataIPShortCut->cCurrentModuleObject);
160 :
161 3 : if (numBoilers <= 0) {
162 0 : ShowSevereError(state, format("No {} equipment specified in input file", state.dataIPShortCut->cCurrentModuleObject));
163 0 : ErrorsFound = true;
164 : }
165 :
166 : // See if load distribution manager has already gotten the input
167 3 : if (allocated(state.dataBoilerSteam->Boiler)) return;
168 :
169 : // Boiler will have fuel input to it , that is it !
170 3 : state.dataBoilerSteam->Boiler.allocate(numBoilers);
171 :
172 : // LOAD ARRAYS WITH CURVE FIT Boiler DATA
173 6 : for (BoilerNum = 1; BoilerNum <= numBoilers; ++BoilerNum) {
174 9 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
175 3 : state.dataIPShortCut->cCurrentModuleObject,
176 : BoilerNum,
177 3 : state.dataIPShortCut->cAlphaArgs,
178 : NumAlphas,
179 3 : state.dataIPShortCut->rNumericArgs,
180 : NumNums,
181 : IOStat,
182 : _,
183 : _,
184 3 : state.dataIPShortCut->cAlphaFieldNames,
185 3 : state.dataIPShortCut->cNumericFieldNames);
186 :
187 : // ErrorsFound will be set to True if problem was found, left untouched otherwise
188 3 : GlobalNames::VerifyUniqueBoilerName(state,
189 3 : state.dataIPShortCut->cCurrentModuleObject,
190 3 : state.dataIPShortCut->cAlphaArgs(1),
191 : ErrorsFound,
192 6 : state.dataIPShortCut->cCurrentModuleObject + " Name");
193 3 : auto &thisBoiler = state.dataBoilerSteam->Boiler(BoilerNum);
194 3 : thisBoiler.Name = state.dataIPShortCut->cAlphaArgs(1);
195 :
196 : // Validate fuel type input
197 3 : thisBoiler.FuelType = static_cast<Constant::eFuel>(getEnumValue(Constant::eFuelNamesUC, state.dataIPShortCut->cAlphaArgs(2)));
198 :
199 : // INPUTS from the IDF file
200 3 : thisBoiler.BoilerMaxOperPress = state.dataIPShortCut->rNumericArgs(1);
201 3 : if (thisBoiler.BoilerMaxOperPress < 1e5) {
202 0 : ShowWarningMessage(state, format("{}=\"{}\"", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
203 0 : ShowContinueError(state, "Field: Maximum Operation Pressure units are Pa. Verify units.");
204 : }
205 3 : thisBoiler.NomEffic = state.dataIPShortCut->rNumericArgs(2);
206 3 : thisBoiler.TempUpLimitBoilerOut = state.dataIPShortCut->rNumericArgs(3);
207 3 : thisBoiler.NomCap = state.dataIPShortCut->rNumericArgs(4);
208 3 : if (thisBoiler.NomCap == DataSizing::AutoSize) {
209 2 : thisBoiler.NomCapWasAutoSized = true;
210 : }
211 3 : thisBoiler.MinPartLoadRat = state.dataIPShortCut->rNumericArgs(5);
212 3 : thisBoiler.MaxPartLoadRat = state.dataIPShortCut->rNumericArgs(6);
213 3 : thisBoiler.OptPartLoadRat = state.dataIPShortCut->rNumericArgs(7);
214 3 : thisBoiler.FullLoadCoef[0] = state.dataIPShortCut->rNumericArgs(8);
215 3 : thisBoiler.FullLoadCoef[1] = state.dataIPShortCut->rNumericArgs(9);
216 3 : thisBoiler.FullLoadCoef[2] = state.dataIPShortCut->rNumericArgs(10);
217 3 : thisBoiler.SizFac = state.dataIPShortCut->rNumericArgs(11);
218 3 : if (thisBoiler.SizFac <= 0.0) thisBoiler.SizFac = 1.0;
219 :
220 3 : if ((state.dataIPShortCut->rNumericArgs(8) + state.dataIPShortCut->rNumericArgs(9) + state.dataIPShortCut->rNumericArgs(10)) == 0.0) {
221 0 : ShowSevereError(state,
222 0 : format("{}{}=\"{}\",", RoutineName, state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
223 0 : ShowContinueError(state, " Sum of fuel use curve coefficients = 0.0");
224 0 : ErrorsFound = true;
225 : }
226 :
227 3 : if (state.dataIPShortCut->rNumericArgs(5) < 0.0) {
228 0 : ShowSevereError(state,
229 0 : format("{}{}=\"{}\",", RoutineName, state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
230 0 : ShowContinueError(state,
231 0 : format("Invalid {}={:.3R}", state.dataIPShortCut->cNumericFieldNames(5), state.dataIPShortCut->rNumericArgs(5)));
232 0 : ErrorsFound = true;
233 : }
234 :
235 3 : if (state.dataIPShortCut->rNumericArgs(3) == 0.0) {
236 0 : ShowSevereError(state,
237 0 : format("{}{}=\"{}\",", RoutineName, state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
238 0 : ShowContinueError(state,
239 0 : format("Invalid {}={:.3R}", state.dataIPShortCut->cNumericFieldNames(3), state.dataIPShortCut->rNumericArgs(3)));
240 0 : ErrorsFound = true;
241 : }
242 3 : thisBoiler.BoilerInletNodeNum = NodeInputManager::GetOnlySingleNode(state,
243 3 : state.dataIPShortCut->cAlphaArgs(3),
244 : ErrorsFound,
245 : DataLoopNode::ConnectionObjectType::BoilerSteam,
246 3 : state.dataIPShortCut->cAlphaArgs(1),
247 : DataLoopNode::NodeFluidType::Steam,
248 : DataLoopNode::ConnectionType::Inlet,
249 : NodeInputManager::CompFluidStream::Primary,
250 : DataLoopNode::ObjectIsNotParent);
251 6 : thisBoiler.BoilerOutletNodeNum = NodeInputManager::GetOnlySingleNode(state,
252 3 : state.dataIPShortCut->cAlphaArgs(4),
253 : ErrorsFound,
254 : DataLoopNode::ConnectionObjectType::BoilerSteam,
255 3 : state.dataIPShortCut->cAlphaArgs(1),
256 : DataLoopNode::NodeFluidType::Steam,
257 : DataLoopNode::ConnectionType::Outlet,
258 : NodeInputManager::CompFluidStream::Primary,
259 : DataLoopNode::ObjectIsNotParent);
260 6 : BranchNodeConnections::TestCompSet(state,
261 3 : state.dataIPShortCut->cCurrentModuleObject,
262 3 : state.dataIPShortCut->cAlphaArgs(1),
263 3 : state.dataIPShortCut->cAlphaArgs(3),
264 3 : state.dataIPShortCut->cAlphaArgs(4),
265 : "Hot Steam Nodes");
266 :
267 3 : thisBoiler.fluid = Fluid::GetSteam(state);
268 3 : if (thisBoiler.fluid == nullptr && BoilerNum == 1) {
269 0 : ShowSevereError(state, "Fluid Properties for STEAM not found.");
270 0 : ErrorsFound = true;
271 : }
272 :
273 3 : if (NumAlphas > 4) {
274 0 : thisBoiler.EndUseSubcategory = state.dataIPShortCut->cAlphaArgs(5);
275 : } else {
276 3 : thisBoiler.EndUseSubcategory = "General";
277 : }
278 : }
279 :
280 3 : if (ErrorsFound) {
281 0 : ShowFatalError(state, format("{}Errors found in processing {} input.", RoutineName, state.dataIPShortCut->cCurrentModuleObject));
282 : }
283 : }
284 :
285 2 : void BoilerSpecs::oneTimeInit(EnergyPlusData &state)
286 : {
287 2 : bool errFlag = false;
288 4 : PlantUtilities::ScanPlantLoopsForObject(
289 2 : state, this->Name, DataPlant::PlantEquipmentType::Boiler_Steam, this->plantLoc, errFlag, _, _, _, _, _);
290 2 : if (errFlag) {
291 0 : ShowFatalError(state, "InitBoiler: Program terminated due to previous condition(s).");
292 : }
293 2 : }
294 :
295 1 : void BoilerSpecs::initEachEnvironment(EnergyPlusData &state)
296 : {
297 : static constexpr std::string_view RoutineName("BoilerSpecs::initEachEnvironment");
298 :
299 1 : int BoilerInletNode = this->BoilerInletNodeNum;
300 :
301 1 : Real64 EnthSteamOutDry = this->fluid->getSatEnthalpy(state, this->TempUpLimitBoilerOut, 1.0, RoutineName);
302 1 : Real64 EnthSteamOutWet = this->fluid->getSatEnthalpy(state, this->TempUpLimitBoilerOut, 0.0, RoutineName);
303 1 : Real64 LatentEnthSteam = EnthSteamOutDry - EnthSteamOutWet;
304 :
305 1 : Real64 CpWater = this->fluid->getSatSpecificHeat(state, this->TempUpLimitBoilerOut, 0.0, RoutineName);
306 :
307 1 : this->DesMassFlowRate =
308 1 : this->NomCap / (LatentEnthSteam + CpWater * (this->TempUpLimitBoilerOut - state.dataLoopNodes->Node(BoilerInletNode).Temp));
309 :
310 1 : PlantUtilities::InitComponentNodes(state, 0.0, this->DesMassFlowRate, this->BoilerInletNodeNum, this->BoilerOutletNodeNum);
311 :
312 1 : this->BoilerPressCheck = 0.0;
313 1 : this->FuelUsed = 0.0;
314 1 : this->BoilerLoad = 0.0;
315 1 : this->BoilerEff = 0.0;
316 1 : this->BoilerOutletTemp = 0.0;
317 :
318 2 : if ((state.dataLoopNodes->Node(this->BoilerOutletNodeNum).TempSetPoint == DataLoopNode::SensedNodeFlagValue) &&
319 1 : (state.dataLoopNodes->Node(this->BoilerOutletNodeNum).TempSetPointLo == DataLoopNode::SensedNodeFlagValue)) {
320 1 : if (!state.dataGlobal->AnyEnergyManagementSystemInModel) {
321 1 : if (!this->MissingSetPointErrDone) {
322 1 : ShowWarningError(state, format("Missing temperature setpoint for Boiler:Steam = {}", this->Name));
323 2 : ShowContinueError(state, " A temperature setpoint is needed at the outlet node of the boiler, use a SetpointManager");
324 2 : ShowContinueError(state, " The overall loop setpoint will be assumed for this boiler. The simulation continues ...");
325 1 : this->MissingSetPointErrDone = true;
326 : }
327 : } else {
328 : // need call to EMS to check node
329 0 : bool FatalError = false; // but not really fatal yet, but should be.
330 0 : EMSManager::CheckIfNodeSetPointManagedByEMS(state, this->BoilerOutletNodeNum, HVAC::CtrlVarType::Temp, FatalError);
331 0 : state.dataLoopNodes->NodeSetpointCheck(this->BoilerOutletNodeNum).needsSetpointChecking = false;
332 0 : if (FatalError) {
333 0 : if (!this->MissingSetPointErrDone) {
334 0 : ShowWarningError(state, format("Missing temperature setpoint for LeavingSetpointModulated mode Boiler named {}", this->Name));
335 0 : ShowContinueError(state, " A temperature setpoint is needed at the outlet node of the boiler.");
336 0 : ShowContinueError(state, " Use a Setpoint Manager to establish a setpoint at the boiler outlet node ");
337 0 : ShowContinueError(state, " or use an EMS actuator to establish a setpoint at the boiler outlet node.");
338 0 : ShowContinueError(state, " The overall loop setpoint will be assumed for this boiler. The simulation continues...");
339 0 : this->MissingSetPointErrDone = true;
340 : }
341 : }
342 : }
343 1 : this->UseLoopSetPoint = true; // this is for backward compatibility and could be removed
344 : }
345 1 : }
346 :
347 2 : void BoilerSpecs::initialize(EnergyPlusData &state) // number of the current electric chiller being simulated
348 : {
349 : // SUBROUTINE INFORMATION:
350 : // AUTHOR Rahul Chillar
351 : // DATE WRITTEN Dec 2004
352 : // RE-ENGINEERED D. Shirey, rework for plant upgrade
353 :
354 : // PURPOSE OF THIS SUBROUTINE:
355 : // This subroutine is for initializations of the Boiler components
356 :
357 : // METHODOLOGY EMPLOYED:
358 : // Uses the status flags to trigger initializations.
359 :
360 : // Init more variables
361 2 : if (this->myFlag) {
362 2 : this->setupOutputVars(state);
363 2 : this->oneTimeInit(state);
364 2 : this->myFlag = false;
365 : }
366 :
367 2 : if (state.dataGlobal->BeginEnvrnFlag && this->myEnvrnFlag && (state.dataPlnt->PlantFirstSizesOkayToFinalize)) {
368 1 : this->initEachEnvironment(state);
369 1 : this->myEnvrnFlag = false;
370 : }
371 2 : if (!state.dataGlobal->BeginEnvrnFlag) {
372 1 : this->myEnvrnFlag = true;
373 : }
374 :
375 2 : if (this->UseLoopSetPoint) {
376 : // At some point, need to circle back and get from plant data structure instead of node
377 : // fix for clumsy old input that worked because loop setpoint was spread.
378 : // could be removed with transition, testing , model change, period of being obsolete.
379 1 : int BoilerOutletNode = this->BoilerOutletNodeNum;
380 1 : switch (this->plantLoc.loop->LoopDemandCalcScheme) {
381 :
382 0 : case DataPlant::LoopDemandCalcScheme::SingleSetPoint:
383 0 : state.dataLoopNodes->Node(BoilerOutletNode).TempSetPoint =
384 0 : state.dataLoopNodes->Node(this->plantLoc.loop->TempSetPointNodeNum).TempSetPoint;
385 0 : break;
386 0 : case DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand:
387 0 : state.dataLoopNodes->Node(BoilerOutletNode).TempSetPointLo =
388 0 : state.dataLoopNodes->Node(this->plantLoc.loop->TempSetPointNodeNum).TempSetPointLo;
389 0 : break;
390 1 : default:
391 1 : break;
392 : }
393 : }
394 2 : }
395 :
396 2 : void BoilerSpecs::setupOutputVars(EnergyPlusData &state)
397 : {
398 2 : std::string_view sFuelType = Constant::eFuelNames[static_cast<int>(this->FuelType)];
399 4 : SetupOutputVariable(state,
400 : "Boiler Heating Rate",
401 : Constant::Units::W,
402 2 : this->BoilerLoad,
403 : OutputProcessor::TimeStepType::System,
404 : OutputProcessor::StoreType::Average,
405 2 : this->Name);
406 :
407 4 : SetupOutputVariable(state,
408 : "Boiler Heating Energy",
409 : Constant::Units::J,
410 2 : this->BoilerEnergy,
411 : OutputProcessor::TimeStepType::System,
412 : OutputProcessor::StoreType::Sum,
413 2 : this->Name,
414 : Constant::eResource::EnergyTransfer,
415 : OutputProcessor::Group::Plant,
416 : OutputProcessor::EndUseCat::Boilers);
417 6 : SetupOutputVariable(state,
418 4 : format("Boiler {} Rate", sFuelType),
419 : Constant::Units::W,
420 2 : this->FuelUsed,
421 : OutputProcessor::TimeStepType::System,
422 : OutputProcessor::StoreType::Average,
423 2 : this->Name);
424 6 : SetupOutputVariable(state,
425 4 : format("Boiler {} Energy", sFuelType),
426 : Constant::Units::J,
427 2 : this->FuelConsumed,
428 : OutputProcessor::TimeStepType::System,
429 : OutputProcessor::StoreType::Sum,
430 2 : this->Name,
431 2 : Constant::eFuel2eResource[(int)this->FuelType],
432 : OutputProcessor::Group::Plant,
433 : OutputProcessor::EndUseCat::Heating,
434 : this->EndUseSubcategory);
435 4 : SetupOutputVariable(state,
436 : "Boiler Steam Efficiency",
437 : Constant::Units::None,
438 2 : this->BoilerEff,
439 : OutputProcessor::TimeStepType::System,
440 : OutputProcessor::StoreType::Average,
441 2 : this->Name);
442 4 : SetupOutputVariable(state,
443 : "Boiler Steam Inlet Temperature",
444 : Constant::Units::C,
445 2 : this->BoilerInletTemp,
446 : OutputProcessor::TimeStepType::System,
447 : OutputProcessor::StoreType::Average,
448 2 : this->Name);
449 4 : SetupOutputVariable(state,
450 : "Boiler Steam Outlet Temperature",
451 : Constant::Units::C,
452 2 : this->BoilerOutletTemp,
453 : OutputProcessor::TimeStepType::System,
454 : OutputProcessor::StoreType::Average,
455 2 : this->Name);
456 4 : SetupOutputVariable(state,
457 : "Boiler Steam Mass Flow Rate",
458 : Constant::Units::kg_s,
459 2 : this->BoilerMassFlowRate,
460 : OutputProcessor::TimeStepType::System,
461 : OutputProcessor::StoreType::Average,
462 2 : this->Name);
463 2 : }
464 :
465 0 : void BoilerSpecs::autosize(EnergyPlusData &state)
466 : {
467 :
468 : // SUBROUTINE INFORMATION:
469 : // AUTHOR Rahul Chillar
470 : // DATE WRITTEN Dec 2004
471 : // MODIFIED November 2013 Daeho Kang, add component sizing table entries
472 :
473 : // PURPOSE OF THIS SUBROUTINE:
474 : // This subroutine is for sizing Boiler Components for which capacities and flow rates
475 : // have not been specified in the input.
476 :
477 : // METHODOLOGY EMPLOYED:
478 : // Obtains Steam flow rate from the plant sizing array. Calculates nominal capacity from
479 : // the hot water flow rate and the hot water loop design delta T.
480 :
481 : // SUBROUTINE PARAMETER DEFINITIONS:
482 : static constexpr std::string_view RoutineName("SizeBoiler");
483 :
484 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
485 0 : bool ErrorsFound(false); // If errors detected in input
486 0 : Real64 tmpNomCap = this->NomCap;
487 0 : int PltSizNum = this->plantLoc.loop->PlantSizNum;
488 :
489 0 : if (PltSizNum > 0) {
490 0 : if (state.dataSize->PlantSizData(PltSizNum).DesVolFlowRate >= HVAC::SmallWaterVolFlow) {
491 0 : Real64 SizingTemp = this->TempUpLimitBoilerOut;
492 0 : Real64 SteamDensity = this->fluid->getSatDensity(state, SizingTemp, 1.0, RoutineName);
493 0 : Real64 EnthSteamOutDry = this->fluid->getSatEnthalpy(state, SizingTemp, 1.0, RoutineName);
494 0 : Real64 EnthSteamOutWet = this->fluid->getSatEnthalpy(state, SizingTemp, 0.0, RoutineName);
495 0 : Real64 LatentEnthSteam = EnthSteamOutDry - EnthSteamOutWet;
496 0 : Real64 CpWater = this->fluid->getSatSpecificHeat(state, SizingTemp, 0.0, RoutineName);
497 0 : tmpNomCap = (CpWater * SteamDensity * this->SizFac * state.dataSize->PlantSizData(PltSizNum).DeltaT *
498 0 : state.dataSize->PlantSizData(PltSizNum).DesVolFlowRate +
499 0 : state.dataSize->PlantSizData(PltSizNum).DesVolFlowRate * SteamDensity * LatentEnthSteam);
500 : } else {
501 0 : if (this->NomCapWasAutoSized) tmpNomCap = 0.0;
502 : }
503 0 : if (state.dataPlnt->PlantFirstSizesOkayToFinalize) {
504 0 : if (this->NomCapWasAutoSized) {
505 0 : this->NomCap = tmpNomCap;
506 0 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
507 0 : BaseSizer::reportSizerOutput(state, "Boiler:Steam", this->Name, "Design Size Nominal Capacity [W]", tmpNomCap);
508 :
509 : // Std 229 Boilers new report table
510 0 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchBoilerType, this->Name, "Boiler:Steam");
511 0 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchBoilerRefCap, this->Name, this->NomCap);
512 0 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchBoilerRefEff, this->Name, this->NomEffic);
513 0 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchBoilerRatedCap, this->Name, this->NomCap);
514 0 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchBoilerRatedEff, this->Name, this->NomEffic);
515 0 : OutputReportPredefined::PreDefTableEntry(state,
516 0 : state.dataOutRptPredefined->pdchBoilerPlantloopName,
517 : this->Name,
518 0 : (this->plantLoc.loop != nullptr) ? this->plantLoc.loop->Name : "N/A");
519 0 : OutputReportPredefined::PreDefTableEntry(state,
520 0 : state.dataOutRptPredefined->pdchBoilerPlantloopBranchName,
521 : this->Name,
522 0 : (this->plantLoc.branch != nullptr) ? this->plantLoc.branch->Name : "N/A");
523 0 : OutputReportPredefined::PreDefTableEntry(
524 0 : state, state.dataOutRptPredefined->pdchBoilerMinPLR, this->Name, this->MinPartLoadRat);
525 0 : OutputReportPredefined::PreDefTableEntry(state,
526 0 : state.dataOutRptPredefined->pdchBoilerFuelType,
527 : this->Name,
528 0 : Constant::eFuelNames[static_cast<int>(this->FuelType)]);
529 0 : OutputReportPredefined::PreDefTableEntry(
530 0 : state, state.dataOutRptPredefined->pdchBoilerParaElecLoad, this->Name, "Not Applicable");
531 : }
532 0 : if (state.dataPlnt->PlantFirstSizesOkayToReport) {
533 0 : BaseSizer::reportSizerOutput(state, "Boiler:Steam", this->Name, "Initial Design Size Nominal Capacity [W]", tmpNomCap);
534 : }
535 : } else { // Hard-sized with sizing data
536 0 : if (this->NomCap > 0.0 && tmpNomCap > 0.0) {
537 0 : Real64 NomCapUser = this->NomCap;
538 0 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
539 0 : BaseSizer::reportSizerOutput(state,
540 : "Boiler:Steam",
541 : this->Name,
542 : "Design Size Nominal Capacity [W]",
543 : tmpNomCap,
544 : "User-Specified Nominal Capacity [W]",
545 : NomCapUser);
546 :
547 : // Std 229 Boilers new report table
548 0 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchBoilerType, this->Name, "Boiler:Steam");
549 0 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchBoilerRefCap, this->Name, this->NomCap);
550 0 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchBoilerRefEff, this->Name, this->NomEffic);
551 0 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchBoilerRatedCap, this->Name, this->NomCap);
552 0 : OutputReportPredefined::PreDefTableEntry(
553 0 : state, state.dataOutRptPredefined->pdchBoilerRatedEff, this->Name, this->NomEffic);
554 0 : OutputReportPredefined::PreDefTableEntry(state,
555 0 : state.dataOutRptPredefined->pdchBoilerPlantloopName,
556 : this->Name,
557 0 : (this->plantLoc.loop != nullptr) ? this->plantLoc.loop->Name : "N/A");
558 0 : OutputReportPredefined::PreDefTableEntry(state,
559 0 : state.dataOutRptPredefined->pdchBoilerPlantloopBranchName,
560 : this->Name,
561 0 : (this->plantLoc.branch != nullptr) ? this->plantLoc.branch->Name : "N/A");
562 0 : OutputReportPredefined::PreDefTableEntry(
563 0 : state, state.dataOutRptPredefined->pdchBoilerMinPLR, this->Name, this->MinPartLoadRat);
564 0 : OutputReportPredefined::PreDefTableEntry(state,
565 0 : state.dataOutRptPredefined->pdchBoilerFuelType,
566 : this->Name,
567 0 : Constant::eFuelNames[static_cast<int>(this->FuelType)]);
568 0 : OutputReportPredefined::PreDefTableEntry(
569 0 : state, state.dataOutRptPredefined->pdchBoilerParaElecLoad, this->Name, "Not Applicable");
570 :
571 0 : if (state.dataGlobal->DisplayExtraWarnings) {
572 0 : if ((std::abs(tmpNomCap - NomCapUser) / NomCapUser) > state.dataSize->AutoVsHardSizingThreshold) {
573 0 : ShowMessage(state, format("SizePump: Potential issue with equipment sizing for {}", this->Name));
574 0 : ShowContinueError(state, format("User-Specified Nominal Capacity of {:.2R} [W]", NomCapUser));
575 0 : ShowContinueError(state, format("differs from Design Size Nominal Capacity of {:.2R} [W]", tmpNomCap));
576 0 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
577 0 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
578 : }
579 : }
580 : }
581 : }
582 : }
583 : }
584 : } else {
585 0 : if (this->NomCapWasAutoSized && state.dataPlnt->PlantFirstSizesOkayToFinalize) {
586 0 : ShowSevereError(state, "Autosizing of Boiler nominal capacity requires a loop Sizing:Plant object");
587 0 : ShowContinueError(state, format("Occurs in Boiler:Steam object={}", this->Name));
588 0 : ErrorsFound = true;
589 : }
590 0 : if (!this->NomCapWasAutoSized && this->NomCap > 0.0 && state.dataPlnt->PlantFinalSizesOkayToReport) {
591 0 : BaseSizer::reportSizerOutput(state, "Boiler:Steam", this->Name, "User-Specified Nominal Capacity [W]", this->NomCap);
592 : }
593 : }
594 :
595 0 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
596 : // create predefined report
597 0 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchMechType, this->Name, "Boiler:Steam");
598 0 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchMechNomEff, this->Name, this->NomEffic);
599 0 : OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchMechNomCap, this->Name, this->NomCap);
600 : }
601 :
602 0 : if (ErrorsFound) {
603 0 : ShowFatalError(state, "Preceding sizing errors cause program termination");
604 : }
605 0 : }
606 :
607 2 : void BoilerSpecs::calculate(EnergyPlusData &state,
608 : Real64 &MyLoad, // W - hot water demand to be met by boiler
609 : bool const RunFlag, // TRUE if boiler operating
610 : DataBranchAirLoopPlant::ControlType const EquipFlowCtrl // Flow control mode for the equipment
611 : )
612 : {
613 : // SUBROUTINE INFORMATION:
614 : // AUTHOR Rahul Chillar
615 : // DATE WRITTEN Dec 2004
616 :
617 : // PURPOSE OF THIS SUBROUTINE:
618 : // This subroutine calculates the boiler fuel consumption and the associated
619 : // hot water demand met by the boiler
620 :
621 : // METHODOLOGY EMPLOYED:
622 : // The model is based on a single combustion efficiency (=1 for electric)
623 : // and a second order polynomial fit of performance data to obtain part
624 : // load performance
625 :
626 : // SUBROUTINE PARAMETER DEFINITIONS:
627 : static constexpr std::string_view RoutineName("CalcBoilerModel");
628 :
629 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
630 2 : Real64 BoilerDeltaTemp(0.0); // C - boiler inlet to outlet temperature difference
631 : Real64 CpWater; // Heat capacity of condensed steam
632 :
633 : // Loading the variables derived type in to local variables
634 2 : this->BoilerLoad = 0.0;
635 2 : this->BoilerMassFlowRate = 0.0;
636 :
637 2 : switch (this->plantLoc.loop->LoopDemandCalcScheme) {
638 1 : case DataPlant::LoopDemandCalcScheme::SingleSetPoint: {
639 1 : this->BoilerOutletTemp = state.dataLoopNodes->Node(this->BoilerOutletNodeNum).TempSetPoint;
640 1 : } break;
641 0 : case DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand: {
642 0 : this->BoilerOutletTemp = state.dataLoopNodes->Node(this->BoilerOutletNodeNum).TempSetPointLo;
643 0 : } break;
644 1 : default:
645 1 : break;
646 : }
647 : // If the specified load is 0.0 or the boiler should not run then we leave this subroutine.Before leaving
648 : // if the component control is SERIESACTIVE we set the component flow to inlet flow so that flow resolver
649 : // will not shut down the branch
650 2 : if (MyLoad <= 0.0 || !RunFlag) {
651 0 : if (EquipFlowCtrl == DataBranchAirLoopPlant::ControlType::SeriesActive)
652 0 : this->BoilerMassFlowRate = state.dataLoopNodes->Node(this->BoilerInletNodeNum).MassFlowRate;
653 0 : return;
654 : }
655 :
656 : // Set the current load equal to the boiler load
657 2 : this->BoilerLoad = MyLoad;
658 :
659 2 : this->BoilerPressCheck = this->fluid->getSatPressure(state, this->BoilerOutletTemp, RoutineName);
660 :
661 2 : if ((this->BoilerPressCheck) > this->BoilerMaxOperPress) {
662 0 : if (this->PressErrIndex == 0) {
663 0 : ShowSevereError(state, format("Boiler:Steam=\"{}\", Saturation Pressure is greater than Maximum Operating Pressure,", this->Name));
664 0 : ShowContinueError(state, "Lower Input Temperature");
665 0 : ShowContinueError(state, format("Steam temperature=[{:.2R}] C", this->BoilerOutletTemp));
666 0 : ShowContinueError(state, format("Refrigerant Saturation Pressure =[{:.0R}] Pa", this->BoilerPressCheck));
667 : }
668 0 : ShowRecurringSevereErrorAtEnd(state,
669 0 : "Boiler:Steam=\"" + this->Name +
670 : "\", Saturation Pressure is greater than Maximum Operating Pressure..continues",
671 0 : this->PressErrIndex,
672 0 : this->BoilerPressCheck,
673 0 : this->BoilerPressCheck,
674 : _,
675 : "[Pa]",
676 : "[Pa]");
677 : }
678 :
679 2 : CpWater = this->fluid->getSatSpecificHeat(state, state.dataLoopNodes->Node(this->BoilerInletNodeNum).Temp, 0.0, RoutineName);
680 :
681 2 : if (this->plantLoc.side->FlowLock == DataPlant::FlowLock::Unlocked) { // TODO: Components shouldn't check FlowLock
682 : // Calculate the flow for the boiler
683 :
684 2 : switch (this->plantLoc.loop->LoopDemandCalcScheme) {
685 1 : case DataPlant::LoopDemandCalcScheme::SingleSetPoint: {
686 1 : BoilerDeltaTemp =
687 1 : state.dataLoopNodes->Node(this->BoilerOutletNodeNum).TempSetPoint - state.dataLoopNodes->Node(this->BoilerInletNodeNum).Temp;
688 1 : } break;
689 1 : default: { // DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand
690 1 : BoilerDeltaTemp =
691 1 : state.dataLoopNodes->Node(this->BoilerOutletNodeNum).TempSetPointLo - state.dataLoopNodes->Node(this->BoilerInletNodeNum).Temp;
692 1 : } break;
693 : }
694 2 : this->BoilerOutletTemp = BoilerDeltaTemp + state.dataLoopNodes->Node(this->BoilerInletNodeNum).Temp;
695 :
696 2 : Real64 const EnthSteamOutDry = this->fluid->getSatEnthalpy(state, this->BoilerOutletTemp, 1.0, RoutineName);
697 2 : Real64 const EnthSteamOutWet = this->fluid->getSatEnthalpy(state, this->BoilerOutletTemp, 0.0, RoutineName);
698 2 : Real64 const LatentEnthSteam = EnthSteamOutDry - EnthSteamOutWet;
699 2 : this->BoilerMassFlowRate = this->BoilerLoad / (LatentEnthSteam + (CpWater * BoilerDeltaTemp));
700 :
701 2 : PlantUtilities::SetComponentFlowRate(
702 2 : state, this->BoilerMassFlowRate, this->BoilerInletNodeNum, this->BoilerOutletNodeNum, this->plantLoc);
703 :
704 : } else { // If FlowLock is True
705 : // Set the boiler flow rate from inlet node and then check performance
706 0 : this->BoilerMassFlowRate = state.dataLoopNodes->Node(this->BoilerInletNodeNum).MassFlowRate;
707 : // Assume that it can meet the setpoint
708 0 : switch (this->plantLoc.loop->LoopDemandCalcScheme) {
709 0 : case DataPlant::LoopDemandCalcScheme::SingleSetPoint: {
710 0 : BoilerDeltaTemp =
711 0 : state.dataLoopNodes->Node(this->BoilerOutletNodeNum).TempSetPoint - state.dataLoopNodes->Node(this->BoilerInletNodeNum).Temp;
712 0 : } break;
713 0 : case DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand: {
714 0 : BoilerDeltaTemp =
715 0 : state.dataLoopNodes->Node(this->BoilerOutletNodeNum).TempSetPointLo - state.dataLoopNodes->Node(this->BoilerInletNodeNum).Temp;
716 0 : } break;
717 0 : default:
718 0 : break;
719 : }
720 : // If boiler outlet temp is already greater than setpoint than it does not need to operate this iteration
721 0 : if (BoilerDeltaTemp < 0.0) {
722 0 : switch (this->plantLoc.loop->LoopDemandCalcScheme) {
723 0 : case DataPlant::LoopDemandCalcScheme::SingleSetPoint: {
724 0 : this->BoilerOutletTemp = state.dataLoopNodes->Node(this->BoilerOutletNodeNum).TempSetPoint;
725 0 : } break;
726 0 : case DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand: {
727 0 : this->BoilerOutletTemp = state.dataLoopNodes->Node(this->BoilerOutletNodeNum).TempSetPointLo;
728 0 : } break;
729 0 : default:
730 0 : break;
731 : }
732 0 : Real64 const EnthSteamOutDry = this->fluid->getSatEnthalpy(state, this->BoilerOutletTemp, 1.0, RoutineName);
733 0 : Real64 const EnthSteamOutWet = this->fluid->getSatEnthalpy(state, this->BoilerOutletTemp, 0.0, RoutineName);
734 0 : Real64 const LatentEnthSteam = EnthSteamOutDry - EnthSteamOutWet;
735 0 : this->BoilerLoad = (this->BoilerMassFlowRate * LatentEnthSteam);
736 :
737 : } else {
738 0 : switch (this->plantLoc.loop->LoopDemandCalcScheme) {
739 0 : case DataPlant::LoopDemandCalcScheme::SingleSetPoint: {
740 0 : this->BoilerOutletTemp = state.dataLoopNodes->Node(this->BoilerOutletNodeNum).TempSetPoint;
741 0 : } break;
742 0 : case DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand: {
743 0 : this->BoilerOutletTemp = state.dataLoopNodes->Node(this->BoilerOutletNodeNum).TempSetPointLo;
744 0 : } break;
745 0 : default:
746 0 : break;
747 : }
748 :
749 0 : Real64 const EnthSteamOutDry = this->fluid->getSatEnthalpy(state, this->BoilerOutletTemp, 1.0, RoutineName);
750 0 : Real64 const EnthSteamOutWet = this->fluid->getSatEnthalpy(state, this->BoilerOutletTemp, 0.0, RoutineName);
751 0 : Real64 const LatentEnthSteam = EnthSteamOutDry - EnthSteamOutWet;
752 0 : this->BoilerLoad =
753 0 : std::abs(this->BoilerMassFlowRate * LatentEnthSteam) + std::abs(this->BoilerMassFlowRate * CpWater * BoilerDeltaTemp);
754 : }
755 :
756 : // If load exceeds the distributed load set to the distributed load
757 0 : if (this->BoilerLoad > MyLoad) {
758 0 : this->BoilerLoad = MyLoad;
759 :
760 : // Reset later , here just for calculating latent heat
761 0 : switch (this->plantLoc.loop->LoopDemandCalcScheme) {
762 0 : case DataPlant::LoopDemandCalcScheme::SingleSetPoint: {
763 0 : this->BoilerOutletTemp = state.dataLoopNodes->Node(this->BoilerOutletNodeNum).TempSetPoint;
764 0 : } break;
765 0 : case DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand: {
766 0 : this->BoilerOutletTemp = state.dataLoopNodes->Node(this->BoilerOutletNodeNum).TempSetPointLo;
767 0 : } break;
768 0 : default:
769 0 : break;
770 : }
771 :
772 0 : Real64 const EnthSteamOutDry = this->fluid->getSatEnthalpy(state, this->BoilerOutletTemp, 1.0, RoutineName);
773 0 : Real64 const EnthSteamOutWet = this->fluid->getSatEnthalpy(state, this->BoilerOutletTemp, 0.0, RoutineName);
774 0 : Real64 const LatentEnthSteam = EnthSteamOutDry - EnthSteamOutWet;
775 0 : BoilerDeltaTemp = this->BoilerOutletTemp - state.dataLoopNodes->Node(this->BoilerInletNodeNum).Temp;
776 0 : this->BoilerMassFlowRate = this->BoilerLoad / (LatentEnthSteam + CpWater * BoilerDeltaTemp);
777 :
778 0 : PlantUtilities::SetComponentFlowRate(
779 0 : state, this->BoilerMassFlowRate, this->BoilerInletNodeNum, this->BoilerOutletNodeNum, this->plantLoc);
780 : }
781 :
782 : // Checks Boiler Load on the basis of the machine limits.
783 0 : if (this->BoilerLoad > this->NomCap) {
784 0 : if (this->BoilerMassFlowRate > DataBranchAirLoopPlant::MassFlowTolerance) {
785 0 : this->BoilerLoad = this->NomCap;
786 :
787 0 : Real64 const EnthSteamOutDry = this->fluid->getSatEnthalpy(state, this->BoilerOutletTemp, 1.0, RoutineName);
788 0 : Real64 const EnthSteamOutWet = this->fluid->getSatEnthalpy(state, this->BoilerOutletTemp, 0.0, RoutineName);
789 0 : Real64 const LatentEnthSteam = EnthSteamOutDry - EnthSteamOutWet;
790 0 : BoilerDeltaTemp = this->BoilerOutletTemp - state.dataLoopNodes->Node(this->BoilerInletNodeNum).Temp;
791 0 : this->BoilerMassFlowRate = this->BoilerLoad / (LatentEnthSteam + CpWater * BoilerDeltaTemp);
792 :
793 0 : PlantUtilities::SetComponentFlowRate(
794 0 : state, this->BoilerMassFlowRate, this->BoilerInletNodeNum, this->BoilerOutletNodeNum, this->plantLoc);
795 : } else {
796 0 : this->BoilerLoad = 0.0;
797 0 : this->BoilerOutletTemp = state.dataLoopNodes->Node(this->BoilerInletNodeNum).Temp;
798 : }
799 : }
800 :
801 : } // End of the FlowLock If block
802 :
803 : // Limit BoilerOutletTemp. If > max temp, trip boiler.
804 2 : if (this->BoilerOutletTemp > this->TempUpLimitBoilerOut) {
805 0 : this->BoilerLoad = 0.0;
806 0 : this->BoilerOutletTemp = state.dataLoopNodes->Node(this->BoilerInletNodeNum).Temp;
807 : // Does BoilerMassFlowRate need to be set????
808 : }
809 :
810 2 : Real64 OperPLR = this->BoilerLoad / this->NomCap;
811 2 : OperPLR = min(OperPLR, this->MaxPartLoadRat);
812 2 : OperPLR = max(OperPLR, this->MinPartLoadRat);
813 2 : Real64 TheorFuelUse = this->BoilerLoad / this->NomEffic;
814 :
815 : // Calculate fuel used
816 2 : this->FuelUsed = TheorFuelUse / (this->FullLoadCoef[0] + this->FullLoadCoef[1] * OperPLR + this->FullLoadCoef[2] * pow_2(OperPLR));
817 : // Calculate boiler efficiency
818 2 : this->BoilerEff = this->BoilerLoad / this->FuelUsed;
819 : }
820 :
821 : // Beginning of Record Keeping subroutines for the BOILER:SIMPLE Module
822 :
823 1 : void BoilerSpecs::update(EnergyPlusData &state,
824 : Real64 const MyLoad, // boiler operating load
825 : bool const RunFlag, // boiler on when TRUE
826 : [[maybe_unused]] bool const FirstHVACIteration // TRUE if First iteration of simulation
827 : )
828 : {
829 : // SUBROUTINE INFORMATION:
830 : // AUTHOR Rahul Chillar
831 : // DATE WRITTEN Dec 2004
832 :
833 : // PURPOSE OF THIS SUBROUTINE:
834 : // Boiler simulation reporting
835 :
836 1 : Real64 ReportingConstant = state.dataHVACGlobal->TimeStepSysSec;
837 1 : int BoilerInletNode = this->BoilerInletNodeNum;
838 1 : int BoilerOutletNode = this->BoilerOutletNodeNum;
839 :
840 1 : if (MyLoad <= 0.0 || !RunFlag) {
841 : // set node temperatures
842 0 : PlantUtilities::SafeCopyPlantNode(state, BoilerInletNode, BoilerOutletNode);
843 0 : state.dataLoopNodes->Node(BoilerOutletNode).Temp = state.dataLoopNodes->Node(BoilerInletNode).Temp;
844 0 : this->BoilerOutletTemp = state.dataLoopNodes->Node(BoilerInletNode).Temp;
845 0 : this->BoilerLoad = 0.0;
846 0 : this->FuelUsed = 0.0;
847 0 : this->BoilerEff = 0.0;
848 0 : state.dataLoopNodes->Node(BoilerInletNode).Press = this->BoilerPressCheck;
849 0 : state.dataLoopNodes->Node(BoilerOutletNode).Press = state.dataLoopNodes->Node(BoilerInletNode).Press;
850 0 : state.dataLoopNodes->Node(BoilerInletNode).Quality = 0.0;
851 0 : state.dataLoopNodes->Node(BoilerOutletNode).Quality = state.dataLoopNodes->Node(BoilerInletNode).Quality;
852 :
853 : } else {
854 : // set node temperatures
855 1 : PlantUtilities::SafeCopyPlantNode(state, BoilerInletNode, BoilerOutletNode);
856 1 : state.dataLoopNodes->Node(BoilerOutletNode).Temp = this->BoilerOutletTemp;
857 1 : state.dataLoopNodes->Node(BoilerInletNode).Press = this->BoilerPressCheck; //???
858 1 : state.dataLoopNodes->Node(BoilerOutletNode).Press = state.dataLoopNodes->Node(BoilerInletNode).Press;
859 1 : state.dataLoopNodes->Node(BoilerOutletNode).Quality = 1.0; // Model assumes saturated steam exiting the boiler
860 : }
861 :
862 1 : this->BoilerInletTemp = state.dataLoopNodes->Node(BoilerInletNode).Temp;
863 1 : this->BoilerMassFlowRate = state.dataLoopNodes->Node(BoilerOutletNode).MassFlowRate;
864 1 : this->BoilerEnergy = this->BoilerLoad * ReportingConstant;
865 1 : this->FuelConsumed = this->FuelUsed * ReportingConstant;
866 1 : }
867 :
868 : } // namespace BoilerSteam
869 :
870 : } // namespace EnergyPlus
|