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/BranchNodeConnections.hh>
57 : #include <EnergyPlus/CurveManager.hh>
58 : #include <EnergyPlus/Data/EnergyPlusData.hh>
59 : #include <EnergyPlus/DataGlobalConstants.hh>
60 : #include <EnergyPlus/DataHVACGlobals.hh>
61 : #include <EnergyPlus/DataIPShortCuts.hh>
62 : #include <EnergyPlus/DataLoopNode.hh>
63 : #include <EnergyPlus/FluidProperties.hh>
64 : #include <EnergyPlus/General.hh>
65 : #include <EnergyPlus/ICEngineElectricGenerator.hh>
66 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
67 : #include <EnergyPlus/NodeInputManager.hh>
68 : #include <EnergyPlus/OutputProcessor.hh>
69 : #include <EnergyPlus/Plant/DataPlant.hh>
70 : #include <EnergyPlus/PlantUtilities.hh>
71 : #include <EnergyPlus/UtilityRoutines.hh>
72 :
73 : namespace EnergyPlus {
74 :
75 : namespace ICEngineElectricGenerator {
76 :
77 : // MODULE INFORMATION:
78 : // AUTHOR Dan Fisher
79 : // DATE WRITTEN Sept. 2000
80 : // MODIFIED na
81 : // RE-ENGINEERED na
82 :
83 : // PURPOSE OF THIS MODULE:
84 : // This module simulates the operation of IC ENGINE Generators.
85 :
86 : // METHODOLOGY EMPLOYED:
87 : // Once the ElectricPowerManager determines that the IC ENGINE Generator
88 : // is available to meet an electric load demand, it calls SimICEngineGenerator
89 : // which in turn calls the ICEngine Generator model.
90 :
91 0 : PlantComponent *ICEngineGeneratorSpecs::factory(EnergyPlusData &state, std::string const &objectName)
92 : {
93 : // Process the input data for ICEGen if it hasn't been done already
94 0 : if (state.dataICEngElectGen->getICEInput) {
95 0 : GetICEngineGeneratorInput(state);
96 0 : state.dataICEngElectGen->getICEInput = false;
97 : }
98 :
99 : // Now look for this particular generator in the list
100 0 : for (auto &thisICE : state.dataICEngElectGen->ICEngineGenerator) {
101 0 : if (thisICE.Name == objectName) {
102 0 : return &thisICE;
103 : }
104 : }
105 : // If we didn't find it, fatal
106 0 : ShowFatalError(state,
107 0 : format("LocalICEngineGeneratorFactory: Error getting inputs for internal combustion engine generator named: {}",
108 : objectName)); // LCOV_EXCL_LINE
109 : // Shut up the compiler
110 : return nullptr; // LCOV_EXCL_LINE
111 : }
112 :
113 1 : void GetICEngineGeneratorInput(EnergyPlusData &state)
114 : {
115 : // SUBROUTINE INFORMATION:
116 : // AUTHOR: Dan Fisher
117 : // DATE WRITTEN: Sept. 2000
118 :
119 : // PURPOSE OF THIS SUBROUTINE:
120 : // This routine will get the input
121 : // required by the IC ENGINE Generator models.
122 : static constexpr std::string_view routineName = "GetICEngineGeneratorInput";
123 :
124 : int genNum; // Generator counter
125 : int NumAlphas; // Number of elements in the alpha array
126 : int NumNums; // Number of elements in the numeric array
127 : int IOStat; // IO Status when calling get input subroutine
128 1 : Array1D_string AlphArray(10); // character string data
129 1 : Array1D<Real64> NumArray(11); // numeric data
130 1 : bool ErrorsFound(false); // error flag
131 :
132 1 : auto &s_ipsc = state.dataIPShortCut;
133 :
134 1 : auto &ICEngineGenerator(state.dataICEngElectGen->ICEngineGenerator);
135 :
136 1 : s_ipsc->cCurrentModuleObject = "Generator:InternalCombustionEngine";
137 2 : state.dataICEngElectGen->NumICEngineGenerators =
138 1 : state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, s_ipsc->cCurrentModuleObject);
139 :
140 1 : if (state.dataICEngElectGen->NumICEngineGenerators <= 0) {
141 0 : ShowSevereError(state, format("No {} equipment specified in input file", s_ipsc->cCurrentModuleObject));
142 0 : ErrorsFound = true;
143 : }
144 :
145 : // ALLOCATE ARRAYS
146 1 : ICEngineGenerator.allocate(state.dataICEngElectGen->NumICEngineGenerators);
147 :
148 : // LOAD ARRAYS WITH IC ENGINE Generator CURVE FIT DATA
149 2 : for (genNum = 1; genNum <= state.dataICEngElectGen->NumICEngineGenerators; ++genNum) {
150 3 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
151 1 : s_ipsc->cCurrentModuleObject,
152 : genNum,
153 : AlphArray,
154 : NumAlphas,
155 : NumArray,
156 : NumNums,
157 : IOStat,
158 : _,
159 1 : s_ipsc->lAlphaFieldBlanks,
160 1 : s_ipsc->cAlphaFieldNames,
161 1 : s_ipsc->cNumericFieldNames);
162 :
163 1 : ErrorObjectHeader eoh{routineName, s_ipsc->cCurrentModuleObject, AlphArray(1)};
164 :
165 1 : auto &iceGen = state.dataICEngElectGen->ICEngineGenerator(genNum);
166 :
167 1 : iceGen.Name = AlphArray(1);
168 :
169 1 : iceGen.RatedPowerOutput = NumArray(1);
170 1 : if (NumArray(1) == 0.0) {
171 0 : ShowSevereError(state, format("Invalid {}={:.2R}", s_ipsc->cNumericFieldNames(1), NumArray(1)));
172 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, AlphArray(1)));
173 0 : ErrorsFound = true;
174 : }
175 :
176 : // Not sure what to do with electric nodes, so do not use optional arguments
177 1 : iceGen.ElectricCircuitNode = NodeInputManager::GetOnlySingleNode(state,
178 1 : AlphArray(2),
179 : ErrorsFound,
180 : DataLoopNode::ConnectionObjectType::GeneratorInternalCombustionEngine,
181 1 : AlphArray(1),
182 : DataLoopNode::NodeFluidType::Electric,
183 : DataLoopNode::ConnectionType::Electric,
184 : NodeInputManager::CompFluidStream::Primary,
185 : DataLoopNode::ObjectIsNotParent);
186 :
187 1 : iceGen.MinPartLoadRat = NumArray(2);
188 1 : iceGen.MaxPartLoadRat = NumArray(3);
189 1 : iceGen.OptPartLoadRat = NumArray(4);
190 :
191 : // Load Special IC ENGINE Generator Curve Fit Inputs
192 1 : if (s_ipsc->lAlphaFieldBlanks(3)) {
193 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(3));
194 0 : ErrorsFound = true;
195 1 : } else if ((iceGen.ElecOutputFuelCurve = Curve::GetCurve(state, AlphArray(3))) == nullptr) {
196 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(3), AlphArray(3));
197 0 : ErrorsFound = true;
198 : }
199 :
200 1 : if (s_ipsc->lAlphaFieldBlanks(4)) {
201 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(4));
202 0 : ErrorsFound = true;
203 1 : } else if ((iceGen.RecJacHeattoFuelCurve = Curve::GetCurve(state, AlphArray(4))) == nullptr) {
204 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(4), AlphArray(4));
205 0 : ErrorsFound = true;
206 : }
207 :
208 1 : if (s_ipsc->lAlphaFieldBlanks(5)) {
209 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(5));
210 0 : ErrorsFound = true;
211 1 : } else if ((iceGen.RecLubeHeattoFuelCurve = Curve::GetCurve(state, AlphArray(5))) == nullptr) {
212 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(5), AlphArray(5));
213 0 : ErrorsFound = true;
214 : }
215 :
216 1 : if (s_ipsc->lAlphaFieldBlanks(6)) {
217 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(6));
218 0 : ErrorsFound = true;
219 1 : } else if ((iceGen.TotExhausttoFuelCurve = Curve::GetCurve(state, AlphArray(6))) == nullptr) {
220 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(6), AlphArray(6));
221 0 : ErrorsFound = true;
222 : }
223 :
224 1 : if (s_ipsc->lAlphaFieldBlanks(7)) {
225 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(7));
226 0 : ErrorsFound = true;
227 1 : } else if ((iceGen.ExhaustTempCurve = Curve::GetCurve(state, AlphArray(7))) == nullptr) {
228 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(7), AlphArray(7));
229 0 : ErrorsFound = true;
230 : } else {
231 1 : Real64 xValue = iceGen.ExhaustTempCurve->value(state, 1.0);
232 1 : if (xValue < ReferenceTemp) {
233 0 : ShowSevereError(state, format("GetICEngineGeneratorInput: {} output has very low value.", s_ipsc->cAlphaFieldNames(7)));
234 0 : ShowContinueError(state, format("...curve generates [{:.3R} C] at PLR=1.0", xValue));
235 0 : ShowContinueError(state,
236 0 : format("...this is less than the Reference Temperature [{:.2R} C] and may cause errors.", ReferenceTemp));
237 : }
238 : }
239 :
240 1 : iceGen.UACoef(1) = NumArray(5);
241 1 : iceGen.UACoef(2) = NumArray(6);
242 :
243 1 : iceGen.MaxExhaustperPowerOutput = NumArray(7);
244 1 : iceGen.DesignMinExitGasTemp = NumArray(8);
245 1 : iceGen.FuelHeatingValue = NumArray(9);
246 1 : iceGen.DesignHeatRecVolFlowRate = NumArray(10);
247 1 : if (iceGen.DesignHeatRecVolFlowRate > 0.0) {
248 0 : iceGen.HeatRecActive = true;
249 0 : iceGen.HeatRecInletNodeNum =
250 0 : NodeInputManager::GetOnlySingleNode(state,
251 0 : AlphArray(8),
252 : ErrorsFound,
253 : DataLoopNode::ConnectionObjectType::GeneratorInternalCombustionEngine,
254 0 : AlphArray(1),
255 : DataLoopNode::NodeFluidType::Water,
256 : DataLoopNode::ConnectionType::Inlet,
257 : NodeInputManager::CompFluidStream::Primary,
258 : DataLoopNode::ObjectIsNotParent);
259 0 : if (iceGen.HeatRecInletNodeNum == 0) {
260 0 : ShowSevereError(state, format("Invalid {}={}", s_ipsc->cAlphaFieldNames(8), AlphArray(8)));
261 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, AlphArray(1)));
262 0 : ErrorsFound = true;
263 : }
264 0 : iceGen.HeatRecOutletNodeNum =
265 0 : NodeInputManager::GetOnlySingleNode(state,
266 0 : AlphArray(9),
267 : ErrorsFound,
268 : DataLoopNode::ConnectionObjectType::GeneratorInternalCombustionEngine,
269 0 : AlphArray(1),
270 : DataLoopNode::NodeFluidType::Water,
271 : DataLoopNode::ConnectionType::Outlet,
272 : NodeInputManager::CompFluidStream::Primary,
273 : DataLoopNode::ObjectIsNotParent);
274 0 : if (iceGen.HeatRecOutletNodeNum == 0) {
275 0 : ShowSevereError(state, format("Invalid {}={}", s_ipsc->cAlphaFieldNames(9), AlphArray(9)));
276 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, AlphArray(1)));
277 0 : ErrorsFound = true;
278 : }
279 0 : BranchNodeConnections::TestCompSet(
280 0 : state, s_ipsc->cCurrentModuleObject, AlphArray(1), AlphArray(8), AlphArray(9), "Heat Recovery Nodes");
281 0 : PlantUtilities::RegisterPlantCompDesignFlow(state, iceGen.HeatRecInletNodeNum, iceGen.DesignHeatRecVolFlowRate);
282 : } else {
283 1 : iceGen.HeatRecActive = false;
284 1 : iceGen.HeatRecInletNodeNum = 0;
285 1 : iceGen.HeatRecOutletNodeNum = 0;
286 1 : if (!s_ipsc->lAlphaFieldBlanks(8) || !s_ipsc->lAlphaFieldBlanks(9)) {
287 0 : ShowWarningError(
288 : state,
289 0 : format("Since Design Heat Flow Rate = 0.0, Heat Recovery inactive for {}={}", s_ipsc->cCurrentModuleObject, AlphArray(1)));
290 0 : ShowContinueError(state, "However, Node names were specified for Heat Recovery inlet or outlet nodes");
291 : }
292 : }
293 :
294 : // Validate fuel type input
295 1 : iceGen.FuelType = static_cast<Constant::eFuel>(getEnumValue(Constant::eFuelNamesUC, AlphArray(10)));
296 1 : if (iceGen.FuelType == Constant::eFuel::Invalid) {
297 0 : ShowSevereError(state, format("Invalid {}={}", s_ipsc->cAlphaFieldNames(10), AlphArray(10)));
298 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, AlphArray(1)));
299 0 : ErrorsFound = true;
300 : }
301 :
302 1 : iceGen.HeatRecMaxTemp = NumArray(11);
303 : }
304 :
305 1 : if (ErrorsFound) {
306 0 : ShowFatalError(state, format("Errors found in processing input for {}", s_ipsc->cCurrentModuleObject));
307 : }
308 1 : }
309 :
310 0 : void ICEngineGeneratorSpecs::setupOutputVars(EnergyPlusData &state)
311 : {
312 0 : std::string_view const sFuelType = Constant::eFuelNames[static_cast<int>(this->FuelType)];
313 0 : SetupOutputVariable(state,
314 : "Generator Produced AC Electricity Rate",
315 : Constant::Units::W,
316 0 : this->ElecPowerGenerated,
317 : OutputProcessor::TimeStepType::System,
318 : OutputProcessor::StoreType::Average,
319 0 : this->Name);
320 :
321 0 : SetupOutputVariable(state,
322 : "Generator Produced AC Electricity Energy",
323 : Constant::Units::J,
324 0 : this->ElecEnergyGenerated,
325 : OutputProcessor::TimeStepType::System,
326 : OutputProcessor::StoreType::Sum,
327 0 : this->Name,
328 : Constant::eResource::ElectricityProduced,
329 : OutputProcessor::Group::Plant,
330 : OutputProcessor::EndUseCat::Cogeneration);
331 :
332 0 : SetupOutputVariable(state,
333 0 : format("Generator {} Rate", sFuelType),
334 : Constant::Units::W,
335 0 : this->FuelEnergyUseRate,
336 : OutputProcessor::TimeStepType::System,
337 : OutputProcessor::StoreType::Average,
338 0 : this->Name);
339 :
340 0 : SetupOutputVariable(state,
341 0 : format("Generator {} Energy", sFuelType),
342 : Constant::Units::J,
343 0 : this->FuelEnergy,
344 : OutputProcessor::TimeStepType::System,
345 : OutputProcessor::StoreType::Sum,
346 0 : this->Name,
347 0 : Constant::eFuel2eResource[(int)this->FuelType],
348 : OutputProcessor::Group::Plant,
349 : OutputProcessor::EndUseCat::Cogeneration);
350 :
351 : // general fuel use report to match other generators.
352 0 : SetupOutputVariable(state,
353 : "Generator Fuel HHV Basis Rate",
354 : Constant::Units::W,
355 0 : this->FuelEnergyUseRate,
356 : OutputProcessor::TimeStepType::System,
357 : OutputProcessor::StoreType::Average,
358 0 : this->Name);
359 :
360 0 : SetupOutputVariable(state,
361 : "Generator Fuel HHV Basis Energy",
362 : Constant::Units::J,
363 0 : this->FuelEnergy,
364 : OutputProcessor::TimeStepType::System,
365 : OutputProcessor::StoreType::Sum,
366 0 : this->Name);
367 :
368 0 : SetupOutputVariable(state,
369 0 : format("Generator {} Mass Flow Rate", sFuelType),
370 : Constant::Units::kg_s,
371 0 : this->FuelMdot,
372 : OutputProcessor::TimeStepType::System,
373 : OutputProcessor::StoreType::Average,
374 0 : this->Name);
375 :
376 0 : SetupOutputVariable(state,
377 : "Generator Exhaust Air Temperature",
378 : Constant::Units::C,
379 0 : this->ExhaustStackTemp,
380 : OutputProcessor::TimeStepType::System,
381 : OutputProcessor::StoreType::Average,
382 0 : this->Name);
383 :
384 0 : if (this->HeatRecActive) {
385 0 : SetupOutputVariable(state,
386 : "Generator Heat Recovery Mass Flow Rate",
387 : Constant::Units::kg_s,
388 0 : this->HeatRecMdotActual,
389 : OutputProcessor::TimeStepType::System,
390 : OutputProcessor::StoreType::Average,
391 0 : this->Name);
392 :
393 0 : SetupOutputVariable(state,
394 : "Generator Jacket Heat Recovery Rate",
395 : Constant::Units::W,
396 0 : this->QJacketRecovered,
397 : OutputProcessor::TimeStepType::System,
398 : OutputProcessor::StoreType::Average,
399 0 : this->Name);
400 :
401 0 : SetupOutputVariable(state,
402 : "Generator Jacket Heat Recovery Energy",
403 : Constant::Units::J,
404 0 : this->JacketEnergyRec,
405 : OutputProcessor::TimeStepType::System,
406 : OutputProcessor::StoreType::Sum,
407 0 : this->Name,
408 : Constant::eResource::EnergyTransfer,
409 : OutputProcessor::Group::Plant,
410 : OutputProcessor::EndUseCat::HeatRecovery);
411 :
412 0 : SetupOutputVariable(state,
413 : "Generator Lube Heat Recovery Rate",
414 : Constant::Units::W,
415 0 : this->QLubeOilRecovered,
416 : OutputProcessor::TimeStepType::System,
417 : OutputProcessor::StoreType::Average,
418 0 : this->Name);
419 :
420 0 : SetupOutputVariable(state,
421 : "Generator Lube Heat Recovery Energy",
422 : Constant::Units::J,
423 0 : this->LubeOilEnergyRec,
424 : OutputProcessor::TimeStepType::System,
425 : OutputProcessor::StoreType::Sum,
426 0 : this->Name,
427 : Constant::eResource::EnergyTransfer,
428 : OutputProcessor::Group::Plant,
429 : OutputProcessor::EndUseCat::HeatRecovery);
430 :
431 0 : SetupOutputVariable(state,
432 : "Generator Exhaust Heat Recovery Rate",
433 : Constant::Units::W,
434 0 : this->QExhaustRecovered,
435 : OutputProcessor::TimeStepType::System,
436 : OutputProcessor::StoreType::Average,
437 0 : this->Name);
438 :
439 0 : SetupOutputVariable(state,
440 : "Generator Exhaust Heat Recovery Energy",
441 : Constant::Units::J,
442 0 : this->ExhaustEnergyRec,
443 : OutputProcessor::TimeStepType::System,
444 : OutputProcessor::StoreType::Sum,
445 0 : this->Name,
446 : Constant::eResource::EnergyTransfer,
447 : OutputProcessor::Group::Plant,
448 : OutputProcessor::EndUseCat::HeatRecovery);
449 :
450 0 : SetupOutputVariable(state,
451 : "Generator Produced Thermal Rate",
452 : Constant::Units::W,
453 0 : this->QTotalHeatRecovered,
454 : OutputProcessor::TimeStepType::System,
455 : OutputProcessor::StoreType::Average,
456 0 : this->Name);
457 :
458 0 : SetupOutputVariable(state,
459 : "Generator Produced Thermal Energy",
460 : Constant::Units::J,
461 0 : this->TotalHeatEnergyRec,
462 : OutputProcessor::TimeStepType::System,
463 : OutputProcessor::StoreType::Sum,
464 0 : this->Name);
465 :
466 0 : SetupOutputVariable(state,
467 : "Generator Heat Recovery Inlet Temperature",
468 : Constant::Units::C,
469 0 : this->HeatRecInletTemp,
470 : OutputProcessor::TimeStepType::System,
471 : OutputProcessor::StoreType::Average,
472 0 : this->Name);
473 :
474 0 : SetupOutputVariable(state,
475 : "Generator Heat Recovery Outlet Temperature",
476 : Constant::Units::C,
477 0 : this->HeatRecOutletTemp,
478 : OutputProcessor::TimeStepType::System,
479 : OutputProcessor::StoreType::Average,
480 0 : this->Name);
481 : }
482 0 : }
483 :
484 0 : void ICEngineGeneratorSpecs::getDesignCapacities(
485 : [[maybe_unused]] EnergyPlusData &state, const EnergyPlus::PlantLocation &, Real64 &MaxLoad, Real64 &MinLoad, Real64 &OptLoad)
486 : {
487 0 : MaxLoad = 0.0;
488 0 : MinLoad = 0.0;
489 0 : OptLoad = 0.0;
490 0 : }
491 :
492 0 : void ICEngineGeneratorSpecs::simulate(EnergyPlusData &state,
493 : [[maybe_unused]] const EnergyPlus::PlantLocation &calledFromLocation,
494 : bool FirstHVACIteration,
495 : [[maybe_unused]] Real64 &CurLoad,
496 : [[maybe_unused]] bool RunFlag)
497 : {
498 : // empty function to emulate current behavior as of conversion to using the PlantComponent calling structure.
499 : // calls from the plant side only update the plant nodes.
500 : // calls from the ElectricPowerServiceManger call the init, calc, and update worker functions directly.
501 :
502 0 : PlantUtilities::UpdateComponentHeatRecoverySide(state,
503 : this->HRPlantLoc.loopNum,
504 : this->HRPlantLoc.loopSideNum,
505 : DataPlant::PlantEquipmentType::Generator_ICEngine,
506 : this->HeatRecInletNodeNum,
507 : this->HeatRecOutletNodeNum,
508 : this->QTotalHeatRecovered,
509 : this->HeatRecInletTemp,
510 : this->HeatRecOutletTemp,
511 : this->HeatRecMdotActual,
512 : FirstHVACIteration);
513 0 : }
514 :
515 0 : void ICEngineGeneratorSpecs::CalcICEngineGeneratorModel(EnergyPlusData &state, bool const RunFlag, Real64 const MyLoad)
516 : {
517 : // SUBROUTINE INFORMATION:
518 : // AUTHOR Dan Fisher
519 : // DATE WRITTEN Sept. 2000
520 : // MODIFIED na
521 : // RE-ENGINEERED
522 :
523 : // PURPOSE OF THIS SUBROUTINE:
524 : // simulate a IC ENGINE generator using the BLAST model
525 :
526 : // METHODOLOGY EMPLOYED:
527 : // curve fit of performance data:
528 :
529 0 : constexpr Real64 ExhaustCP(1.047); // Exhaust Gas Specific Heat (J/kg-K)
530 0 : constexpr Real64 KJtoJ(1000.0); // convert Kjoules to joules
531 :
532 : // Heat Recovery Fluid Mass FlowRate (kg/s)
533 : Real64 HeatRecMdot;
534 :
535 : // Heat Recovery Fluid Inlet Temperature (C)
536 : Real64 HeatRecInTemp;
537 :
538 0 : if (this->HeatRecActive) {
539 0 : int HeatRecInNode = this->HeatRecInletNodeNum;
540 0 : HeatRecInTemp = state.dataLoopNodes->Node(HeatRecInNode).Temp;
541 0 : HeatRecMdot = state.dataLoopNodes->Node(HeatRecInNode).MassFlowRate;
542 :
543 : } else {
544 0 : HeatRecInTemp = 0.0;
545 0 : HeatRecMdot = 0.0;
546 : }
547 :
548 : // If no loop demand or Generator OFF, return
549 0 : if (!RunFlag) {
550 0 : this->ElecPowerGenerated = 0.0;
551 0 : this->ElecEnergyGenerated = 0.0;
552 0 : this->HeatRecInletTemp = HeatRecInTemp;
553 0 : this->HeatRecOutletTemp = HeatRecInTemp;
554 0 : this->HeatRecMdotActual = 0.0;
555 0 : this->QJacketRecovered = 0.0;
556 0 : this->QExhaustRecovered = 0.0;
557 0 : this->QLubeOilRecovered = 0.0;
558 0 : this->QTotalHeatRecovered = 0.0;
559 0 : this->JacketEnergyRec = 0.0;
560 0 : this->ExhaustEnergyRec = 0.0;
561 0 : this->LubeOilEnergyRec = 0.0;
562 0 : this->TotalHeatEnergyRec = 0.0;
563 0 : this->FuelEnergyUseRate = 0.0;
564 0 : this->FuelEnergy = 0.0;
565 0 : this->FuelMdot = 0.0;
566 0 : this->ExhaustStackTemp = 0.0;
567 :
568 0 : return;
569 : }
570 :
571 : // Generator output (W)
572 0 : Real64 elecPowerGenerated = min(MyLoad, this->RatedPowerOutput);
573 0 : elecPowerGenerated = max(elecPowerGenerated, 0.0);
574 :
575 : // Generator operating part load ratio
576 0 : Real64 PLR = min(elecPowerGenerated / this->RatedPowerOutput, this->MaxPartLoadRat);
577 0 : PLR = max(PLR, this->MinPartLoadRat);
578 0 : elecPowerGenerated = PLR * this->RatedPowerOutput;
579 :
580 : // DETERMINE FUEL CONSUMED AND AVAILABLE WASTE HEAT
581 :
582 : // Use Curve fit to determine Fuel Energy Input. For electric power generated in Watts, the fuel
583 : // energy input is calculated in J/s. The PLBasedFuelInputCurve selects ratio of fuel flow (J/s)/power generated (J/s).
584 : Real64 fuelEnergyUseRate; // IC ENGINE fuel use rate (W)
585 0 : if (PLR > 0.0) {
586 : // (RELDC) Ratio of generator output to Fuel Energy Input
587 0 : Real64 elecOutputFuelRat = this->ElecOutputFuelCurve->value(state, PLR);
588 0 : fuelEnergyUseRate = elecPowerGenerated / elecOutputFuelRat;
589 : } else {
590 0 : fuelEnergyUseRate = 0.0;
591 : }
592 :
593 : // Use Curve fit to determine heat recovered in the water jacket. This curve calculates the water jacket heat recovered (J/s) by
594 : // multiplying the total fuel input (J/s) by the fraction of that power that could be recovered in the water jacket at that
595 : // particular part load.
596 :
597 : // (RJACDC) Ratio of Recoverable Jacket Heat to Fuel Energy Input
598 0 : Real64 recJacHeattoFuelRat = this->RecJacHeattoFuelCurve->value(state, PLR);
599 :
600 : // water jacket heat recovered (W)
601 0 : Real64 QJacketRec = fuelEnergyUseRate * recJacHeattoFuelRat;
602 :
603 : // Use Curve fit to determine Heat Recovered Lubricant heat. This curve calculates the lube heat recovered (J/s) by
604 : // multiplying the total fuel input (J/s) by the fraction of that power that could be recovered in the lube oil at that
605 : // particular part load.
606 : // (RLUBDC) Ratio of Recoverable Lube Oil Heat to Fuel Energy Input
607 0 : Real64 recLubeHeattoFuelRat = this->RecLubeHeattoFuelCurve->value(state, PLR);
608 :
609 : // lube oil cooler heat recovered (W)
610 0 : Real64 QLubeOilRec = fuelEnergyUseRate * recLubeHeattoFuelRat;
611 :
612 : // Use Curve fit to determine Heat Recovered from the exhaust. This curve calculates the heat recovered (J/s) by
613 : // multiplying the total fuel input (J/s) by the fraction of that power that could be recovered in the exhaust at that
614 : // particular part load.
615 :
616 : // (REXDC) Total Exhaust Energy Input to Fuel Energy Input
617 0 : Real64 totExhausttoFuelRat = this->TotExhausttoFuelCurve->value(state, PLR);
618 :
619 : // total engine exhaust heat (W)
620 0 : Real64 QExhaustTotal = fuelEnergyUseRate * totExhausttoFuelRat;
621 :
622 : // exhaust gas heat recovered (W)
623 : Real64 QExhaustRec;
624 :
625 : // engine stack temp. (C)
626 0 : Real64 exhaustStackTemp = 0.0;
627 :
628 : // Use Curve fit to determine Exhaust Temperature in C. The temperature is simply a curve fit
629 : // of the exhaust temperature in C to the part load ratio.
630 0 : if (PLR > 0.0) {
631 : // (TEX) Exhaust Gas Temp
632 0 : Real64 exhaustTemp = this->ExhaustTempCurve->value(state, PLR);
633 :
634 0 : if (exhaustTemp > ReferenceTemp) {
635 :
636 : // exhaust gas mass flow rate (kg/s)
637 0 : Real64 ExhaustGasFlow = QExhaustTotal / (ExhaustCP * (exhaustTemp - ReferenceTemp));
638 :
639 : // Use Curve fit to determine stack exhaustTemp after heat recovery
640 : // (UACDC) exhaust gas Heat Exchanger UA
641 0 : Real64 UA_loc = this->UACoef(1) * std::pow(this->RatedPowerOutput, this->UACoef(2));
642 :
643 : // design engine stact saturated steam exhaustTemp. (C)
644 0 : Real64 designMinExitGasTemp = this->DesignMinExitGasTemp;
645 :
646 0 : exhaustStackTemp = designMinExitGasTemp +
647 0 : (exhaustTemp - designMinExitGasTemp) /
648 0 : std::exp(UA_loc / (max(ExhaustGasFlow, this->MaxExhaustperPowerOutput * this->RatedPowerOutput) * ExhaustCP));
649 :
650 0 : QExhaustRec = max(ExhaustGasFlow * ExhaustCP * (exhaustTemp - exhaustStackTemp), 0.0);
651 : } else {
652 0 : if (this->ErrExhaustTempIndex == 0) {
653 0 : ShowWarningMessage(
654 0 : state, format("CalcICEngineGeneratorModel: {}=\"{}\" low Exhaust Temperature from Curve Value", this->TypeOf, this->Name));
655 0 : ShowContinueError(state, format("...curve generated temperature=[{:.3R} C], PLR=[{:.3R}].", exhaustTemp, PLR));
656 0 : ShowContinueError(state, "...simulation will continue with exhaust heat reclaim set to 0.");
657 : }
658 0 : ShowRecurringWarningErrorAtEnd(state,
659 0 : "CalcICEngineGeneratorModel: " + this->TypeOf + "=\"" + this->Name +
660 : "\" low Exhaust Temperature continues...",
661 0 : this->ErrExhaustTempIndex,
662 : exhaustTemp,
663 : exhaustTemp,
664 : _,
665 : "[C]",
666 : "[C]");
667 0 : QExhaustRec = 0.0;
668 0 : exhaustStackTemp = this->DesignMinExitGasTemp;
669 : }
670 : } else {
671 0 : QExhaustRec = 0.0;
672 : // Bug exhaustStackTemp not set but used below
673 : }
674 :
675 0 : Real64 qTotalHeatRecovered = QExhaustRec + QLubeOilRec + QJacketRec;
676 :
677 : // When Max Temp is reached the amount of recovered heat has to be reduced.
678 : Real64 HRecRatio;
679 :
680 0 : if (this->HeatRecActive) {
681 0 : this->CalcICEngineGenHeatRecovery(state, qTotalHeatRecovered, HeatRecMdot, HRecRatio);
682 0 : QExhaustRec *= HRecRatio;
683 0 : QLubeOilRec *= HRecRatio;
684 0 : QJacketRec *= HRecRatio;
685 0 : qTotalHeatRecovered *= HRecRatio;
686 : } else {
687 0 : this->HeatRecInletTemp = HeatRecInTemp;
688 0 : this->HeatRecOutletTemp = HeatRecInTemp;
689 0 : this->HeatRecMdotActual = HeatRecMdot;
690 : }
691 :
692 : // Calculate Energy
693 : // Generator output (J)
694 0 : Real64 ElectricEnergyGen = elecPowerGenerated * state.dataHVACGlobal->TimeStepSysSec;
695 :
696 : // IC ENGINE fuel use (J)
697 0 : Real64 FuelEnergyUsed = fuelEnergyUseRate * state.dataHVACGlobal->TimeStepSysSec;
698 :
699 : // water jacket heat recovered (J)
700 0 : Real64 jacketEnergyRec = QJacketRec * state.dataHVACGlobal->TimeStepSysSec;
701 :
702 : // lube oil cooler heat recovered (J)
703 0 : Real64 lubeOilEnergyRec = QLubeOilRec * state.dataHVACGlobal->TimeStepSysSec;
704 :
705 : // exhaust gas heat recovered (J)
706 0 : Real64 exhaustEnergyRec = QExhaustRec * state.dataHVACGlobal->TimeStepSysSec;
707 0 : this->ElecPowerGenerated = elecPowerGenerated;
708 0 : this->ElecEnergyGenerated = ElectricEnergyGen;
709 0 : this->QJacketRecovered = QJacketRec;
710 0 : this->QLubeOilRecovered = QLubeOilRec;
711 0 : this->QExhaustRecovered = QExhaustRec;
712 0 : this->QTotalHeatRecovered = qTotalHeatRecovered;
713 0 : this->JacketEnergyRec = jacketEnergyRec;
714 0 : this->LubeOilEnergyRec = lubeOilEnergyRec;
715 0 : this->ExhaustEnergyRec = exhaustEnergyRec;
716 0 : this->QTotalHeatRecovered = (QExhaustRec + QLubeOilRec + QJacketRec);
717 0 : this->TotalHeatEnergyRec = (exhaustEnergyRec + lubeOilEnergyRec + jacketEnergyRec);
718 0 : this->FuelEnergyUseRate = std::abs(fuelEnergyUseRate);
719 0 : this->FuelEnergy = std::abs(FuelEnergyUsed);
720 :
721 : // Heating Value of Fuel in kJ/kg
722 0 : Real64 fuelHeatingValue = this->FuelHeatingValue;
723 :
724 0 : this->FuelMdot = std::abs(fuelEnergyUseRate) / (fuelHeatingValue * KJtoJ);
725 0 : this->ExhaustStackTemp = exhaustStackTemp;
726 : }
727 :
728 0 : void ICEngineGeneratorSpecs::CalcICEngineGenHeatRecovery(EnergyPlusData &state,
729 : Real64 const EnergyRecovered,
730 : Real64 const HeatRecMdot,
731 : Real64 &HRecRatio)
732 : {
733 : // SUBROUTINE INFORMATION:
734 : // AUTHOR: Brandon Anderson
735 : // DATE WRITTEN: November 2000
736 :
737 : // PURPOSE OF THIS SUBROUTINE:
738 : // To perform heat recovery calculations and node updates
739 :
740 : // METHODOLOGY EMPLOYED: This routine is required for the heat recovery loop.
741 : // It works in conjunction with the Heat Recovery Manager, and the PlantWaterHeater.
742 : // The chiller sets the flow on the loop first by the input design flow rate and then
743 : // performs a check to verify that
744 :
745 : static constexpr std::string_view RoutineName("CalcICEngineGeneratorModel");
746 :
747 : // Need to set the HeatRecRatio to 1.0 if it is not modified
748 0 : HRecRatio = 1.0;
749 :
750 0 : Real64 HeatRecInTemp = state.dataLoopNodes->Node(this->HeatRecInletNodeNum).Temp;
751 0 : Real64 HeatRecCp = state.dataPlnt->PlantLoop(this->HRPlantLoc.loopNum).glycol->getSpecificHeat(state, HeatRecInTemp, RoutineName);
752 :
753 : // Don't divide by zero - Note This also results in no heat recovery when
754 : // design Mdot for Heat Recovery - Specified on Chiller Input - is zero
755 : // In order to see what minimum heat recovery flow rate is for the design temperature
756 : // The design heat recovery flow rate can be set very small, but greater than zero.
757 :
758 : Real64 HeatRecOutTemp;
759 :
760 0 : if ((HeatRecMdot > 0) && (HeatRecCp > 0)) {
761 0 : HeatRecOutTemp = (EnergyRecovered) / (HeatRecMdot * HeatRecCp) + HeatRecInTemp;
762 : } else {
763 0 : HeatRecOutTemp = HeatRecInTemp;
764 : }
765 :
766 : // Note: check to make sure the Max Temperature was not exceeded
767 0 : if (HeatRecOutTemp > this->HeatRecMaxTemp) {
768 : Real64 MinHeatRecMdot;
769 0 : if (this->HeatRecMaxTemp != HeatRecInTemp) {
770 0 : MinHeatRecMdot = (EnergyRecovered) / (HeatRecCp * (this->HeatRecMaxTemp - HeatRecInTemp));
771 0 : if (MinHeatRecMdot < 0.0) MinHeatRecMdot = 0.0;
772 : } else {
773 0 : MinHeatRecMdot = 0.0;
774 : }
775 :
776 : // Recalculate Outlet Temperature, with adjusted flowrate
777 0 : if ((MinHeatRecMdot > 0.0) && (HeatRecCp > 0.0)) {
778 0 : HeatRecOutTemp = (EnergyRecovered) / (MinHeatRecMdot * HeatRecCp) + HeatRecInTemp;
779 0 : HRecRatio = HeatRecMdot / MinHeatRecMdot;
780 : } else {
781 0 : HeatRecOutTemp = HeatRecInTemp;
782 0 : HRecRatio = 0.0;
783 : }
784 : }
785 :
786 : // Update global variables for reporting later
787 0 : this->HeatRecInletTemp = HeatRecInTemp;
788 0 : this->HeatRecOutletTemp = HeatRecOutTemp;
789 0 : this->HeatRecMdotActual = HeatRecMdot;
790 0 : }
791 :
792 0 : void ICEngineGeneratorSpecs::InitICEngineGenerators(EnergyPlusData &state, bool const RunFlag, bool const FirstHVACIteration)
793 : {
794 :
795 : // SUBROUTINE INFORMATION:
796 : // AUTHOR Dan Fisher
797 : // DATE WRITTEN Oct 2000
798 : // MODIFIED na
799 : // RE-ENGINEERED Brent Griffith, Sept 2010, plant upgrades, generalize fluid props
800 :
801 : // PURPOSE OF THIS SUBROUTINE:
802 : // This subroutine is for initializations of the IC ENGINE generators.
803 :
804 : // METHODOLOGY EMPLOYED:
805 : // Uses the status flags to trigger initializations.
806 :
807 0 : this->oneTimeInit(state); // end one time inits
808 :
809 : // Do the Begin Environment initializations
810 0 : if (state.dataGlobal->BeginEnvrnFlag && this->MyEnvrnFlag && this->HeatRecActive) {
811 0 : int HeatRecInletNode = this->HeatRecInletNodeNum;
812 0 : int HeatRecOutletNode = this->HeatRecOutletNodeNum;
813 : // set the node Temperature, assuming freeze control
814 0 : state.dataLoopNodes->Node(HeatRecInletNode).Temp = 20.0;
815 0 : state.dataLoopNodes->Node(HeatRecOutletNode).Temp = 20.0;
816 : // set the node max and min mass flow rates
817 0 : PlantUtilities::InitComponentNodes(state, 0.0, this->DesignHeatRecMassFlowRate, HeatRecInletNode, HeatRecOutletNode);
818 :
819 0 : this->MyEnvrnFlag = false;
820 : } // end environmental inits
821 :
822 0 : if (!state.dataGlobal->BeginEnvrnFlag) {
823 0 : this->MyEnvrnFlag = true;
824 : }
825 :
826 0 : if (this->HeatRecActive) {
827 0 : if (FirstHVACIteration) {
828 : Real64 mdot;
829 0 : if (RunFlag) {
830 0 : mdot = this->DesignHeatRecMassFlowRate;
831 : } else {
832 0 : mdot = 0.0;
833 : }
834 0 : PlantUtilities::SetComponentFlowRate(state, mdot, this->HeatRecInletNodeNum, this->HeatRecOutletNodeNum, this->HRPlantLoc);
835 :
836 : } else {
837 0 : PlantUtilities::SetComponentFlowRate(
838 0 : state, this->HeatRecMdotActual, this->HeatRecInletNodeNum, this->HeatRecOutletNodeNum, this->HRPlantLoc);
839 : }
840 : }
841 0 : }
842 :
843 0 : void ICEngineGeneratorSpecs::update(EnergyPlusData &state)
844 : {
845 0 : if (this->HeatRecActive) {
846 0 : int HeatRecOutletNode = this->HeatRecOutletNodeNum;
847 0 : state.dataLoopNodes->Node(HeatRecOutletNode).Temp = this->HeatRecOutletTemp;
848 : }
849 0 : }
850 0 : void ICEngineGeneratorSpecs::oneTimeInit(EnergyPlusData &state)
851 : {
852 : static constexpr std::string_view RoutineName("InitICEngineGenerators");
853 :
854 : bool errFlag;
855 :
856 0 : if (this->myFlag) {
857 0 : this->setupOutputVars(state);
858 0 : this->myFlag = false;
859 : }
860 :
861 0 : if (this->MyPlantScanFlag && allocated(state.dataPlnt->PlantLoop) && this->HeatRecActive) {
862 0 : errFlag = false;
863 0 : PlantUtilities::ScanPlantLoopsForObject(
864 0 : state, this->Name, DataPlant::PlantEquipmentType::Generator_ICEngine, this->HRPlantLoc, errFlag, _, _, _, _, _);
865 0 : if (errFlag) {
866 0 : ShowFatalError(state, "InitICEngineGenerators: Program terminated due to previous condition(s).");
867 : }
868 :
869 0 : this->MyPlantScanFlag = false;
870 : }
871 :
872 0 : if (this->MySizeAndNodeInitFlag && (!this->MyPlantScanFlag) && this->HeatRecActive) {
873 :
874 : // size mass flow rate
875 0 : Real64 rho = state.dataPlnt->PlantLoop(this->HRPlantLoc.loopNum).glycol->getDensity(state, Constant::InitConvTemp, RoutineName);
876 :
877 0 : this->DesignHeatRecMassFlowRate = rho * this->DesignHeatRecVolFlowRate;
878 0 : this->HeatRecMdotDesign = this->DesignHeatRecMassFlowRate;
879 :
880 0 : PlantUtilities::InitComponentNodes(state, 0.0, this->DesignHeatRecMassFlowRate, this->HeatRecInletNodeNum, this->HeatRecOutletNodeNum);
881 :
882 0 : this->MySizeAndNodeInitFlag = false;
883 : }
884 0 : }
885 :
886 : } // namespace ICEngineElectricGenerator
887 :
888 : } // namespace EnergyPlus
|