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/CTElectricGenerator.hh>
58 : #include <EnergyPlus/CurveManager.hh>
59 : #include <EnergyPlus/Data/EnergyPlusData.hh>
60 : #include <EnergyPlus/DataEnvironment.hh>
61 : #include <EnergyPlus/DataGlobalConstants.hh>
62 : #include <EnergyPlus/DataHVACGlobals.hh>
63 : #include <EnergyPlus/DataIPShortCuts.hh>
64 : #include <EnergyPlus/DataLoopNode.hh>
65 : #include <EnergyPlus/FluidProperties.hh>
66 : #include <EnergyPlus/General.hh>
67 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
68 : #include <EnergyPlus/NodeInputManager.hh>
69 : #include <EnergyPlus/OutAirNodeManager.hh>
70 : #include <EnergyPlus/OutputProcessor.hh>
71 : #include <EnergyPlus/Plant/DataPlant.hh>
72 : #include <EnergyPlus/Plant/PlantLocation.hh>
73 : #include <EnergyPlus/PlantUtilities.hh>
74 : #include <EnergyPlus/UtilityRoutines.hh>
75 :
76 : namespace EnergyPlus {
77 :
78 : namespace CTElectricGenerator {
79 :
80 : //__________________________________________________________________________;
81 : // BLAST inherited generators:
82 : // CTElectricGenerator (COMBUSTION Turbine)
83 :
84 : // MODULE INFORMATION:
85 : // AUTHOR Dan Fisher
86 : // DATE WRITTEN Sept 2000
87 :
88 : // PURPOSE OF THIS MODULE:
89 : // This module simulates the performance of the COMBUSTION turbine
90 : // Generators.
91 :
92 : // METHODOLOGY EMPLOYED:
93 : // Once the Electric power manager determines that the CT Generator
94 : // is available, it calls SimCTGenerator which in turn calls the
95 : // appropriate COMBUSTION turbine Generator model.
96 : // All CT Generator models are based on a polynomial fit of Generator
97 : // performance data.
98 :
99 0 : CTGeneratorData *CTGeneratorData::factory(EnergyPlusData &state, std::string const &objectName)
100 : {
101 : // Process the input data for generators if it hasn't been done already
102 0 : if (state.dataCTElectricGenerator->getCTInputFlag) {
103 0 : GetCTGeneratorInput(state);
104 0 : state.dataCTElectricGenerator->getCTInputFlag = false;
105 : }
106 :
107 : // Now look for this particular generator in the list
108 0 : auto myCTGen = std::find_if(state.dataCTElectricGenerator->CTGenerator.begin(),
109 0 : state.dataCTElectricGenerator->CTGenerator.end(),
110 0 : [&objectName](const CTGeneratorData &CTElecGen) { return CTElecGen.Name == objectName; });
111 0 : if (myCTGen != state.dataCTElectricGenerator->CTGenerator.end()) return myCTGen;
112 : // If we didn't find it, fatal
113 0 : ShowFatalError(state,
114 0 : format("LocalCombustionTurbineGeneratorFactory: Error getting inputs for combustion turbine generator named: {}",
115 : objectName)); // LCOV_EXCL_LINE
116 : // Shut up the compiler
117 : return nullptr; // LCOV_EXCL_LINE
118 : }
119 :
120 0 : void CTGeneratorData::simulate([[maybe_unused]] EnergyPlusData &state,
121 : [[maybe_unused]] const EnergyPlus::PlantLocation &calledFromLocation,
122 : [[maybe_unused]] bool FirstHVACIteration,
123 : [[maybe_unused]] Real64 &CurLoad,
124 : [[maybe_unused]] bool RunFlag)
125 : {
126 : // SUBROUTINE INFORMATION:
127 : // AUTHOR Dan Fisher
128 : // DATE WRITTEN Sept. 2000
129 :
130 : // PURPOSE OF THIS SUBROUTINE: This is the CT Generator driver. It
131 : // gets the input for the models, initializes simulation variables, call
132 : // the appropriate model and sets up reporting variables.
133 :
134 : // empty function to emulate current behavior as of conversion to using the PlantComponent calling structure.
135 : // calls from the plant side... do nothing.
136 : // calls from the ElectricPowerServiceManger call the init and calculation worker functions directly.
137 0 : }
138 :
139 1 : void GetCTGeneratorInput(EnergyPlusData &state)
140 : {
141 : // SUBROUTINE INFORMATION:
142 : // AUTHOR: Dan Fisher
143 : // DATE WRITTEN: April 2000
144 :
145 : // PURPOSE OF THIS SUBROUTINE:
146 : // This routine will get the input
147 : // required by the CT Generator models.
148 : static constexpr std::string_view routineName = "GetCTGeneratorInput";
149 :
150 : int NumAlphas; // Number of elements in the alpha array
151 : int NumNums; // Number of elements in the numeric array
152 : int IOStat; // IO Status when calling get input subroutine
153 1 : Array1D_string AlphArray(12); // character string data
154 1 : Array1D<Real64> NumArray(12); // numeric data
155 1 : bool ErrorsFound(false); // error flag
156 :
157 1 : state.dataIPShortCut->cCurrentModuleObject = "Generator:CombustionTurbine";
158 1 : int NumCTGenerators = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, state.dataIPShortCut->cCurrentModuleObject);
159 :
160 1 : if (NumCTGenerators <= 0) {
161 0 : ShowSevereError(state, format("No {} equipment specified in input file", state.dataIPShortCut->cCurrentModuleObject));
162 0 : ErrorsFound = true;
163 : }
164 :
165 : // ALLOCATE ARRAYS
166 1 : state.dataCTElectricGenerator->CTGenerator.allocate(NumCTGenerators);
167 :
168 : // LOAD ARRAYS WITH CT CURVE FIT Generator DATA
169 2 : for (int genNum = 1; genNum <= NumCTGenerators; ++genNum) {
170 3 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
171 1 : state.dataIPShortCut->cCurrentModuleObject,
172 : genNum,
173 : AlphArray,
174 : NumAlphas,
175 : NumArray,
176 : NumNums,
177 : IOStat,
178 : _,
179 1 : state.dataIPShortCut->lAlphaFieldBlanks,
180 1 : state.dataIPShortCut->cAlphaFieldNames,
181 1 : state.dataIPShortCut->cNumericFieldNames);
182 :
183 1 : ErrorObjectHeader eoh{routineName, state.dataIPShortCut->cCurrentModuleObject, AlphArray(1)};
184 :
185 1 : state.dataCTElectricGenerator->CTGenerator(genNum).Name = AlphArray(1);
186 :
187 1 : state.dataCTElectricGenerator->CTGenerator(genNum).RatedPowerOutput = NumArray(1);
188 1 : if (NumArray(1) == 0.0) {
189 0 : ShowSevereError(state, format("Invalid {}={:.2R}", state.dataIPShortCut->cNumericFieldNames(1), NumArray(1)));
190 0 : ShowContinueError(state, format("Entered in {}={}", state.dataIPShortCut->cCurrentModuleObject, AlphArray(1)));
191 0 : ErrorsFound = true;
192 : }
193 :
194 : // Not sure what to do with electric nodes, so do not use optional arguments
195 1 : state.dataCTElectricGenerator->CTGenerator(genNum).ElectricCircuitNode =
196 2 : NodeInputManager::GetOnlySingleNode(state,
197 1 : AlphArray(2),
198 : ErrorsFound,
199 : DataLoopNode::ConnectionObjectType::GeneratorCombustionTurbine,
200 1 : AlphArray(1),
201 : DataLoopNode::NodeFluidType::Electric,
202 : DataLoopNode::ConnectionType::Electric,
203 : NodeInputManager::CompFluidStream::Primary,
204 : DataLoopNode::ObjectIsNotParent);
205 :
206 1 : state.dataCTElectricGenerator->CTGenerator(genNum).MinPartLoadRat = NumArray(2);
207 1 : state.dataCTElectricGenerator->CTGenerator(genNum).MaxPartLoadRat = NumArray(3);
208 1 : state.dataCTElectricGenerator->CTGenerator(genNum).OptPartLoadRat = NumArray(4);
209 :
210 : // Load Special CT Generator Input
211 :
212 1 : state.dataCTElectricGenerator->CTGenerator(genNum).PLBasedFuelInputCurve = Curve::GetCurve(state, AlphArray(3));
213 1 : if (state.dataCTElectricGenerator->CTGenerator(genNum).PLBasedFuelInputCurve == 0) {
214 0 : ShowSevereItemNotFound(state, eoh, state.dataIPShortCut->cAlphaFieldNames(3), AlphArray(3));
215 0 : ErrorsFound = true;
216 : }
217 :
218 1 : state.dataCTElectricGenerator->CTGenerator(genNum).TempBasedFuelInputCurve = Curve::GetCurve(state, AlphArray(4));
219 1 : if (state.dataCTElectricGenerator->CTGenerator(genNum).TempBasedFuelInputCurve == nullptr) {
220 0 : ShowSevereItemNotFound(state, eoh, state.dataIPShortCut->cAlphaFieldNames(4), AlphArray(4));
221 0 : ErrorsFound = true;
222 : }
223 :
224 1 : state.dataCTElectricGenerator->CTGenerator(genNum).ExhaustFlowCurve = Curve::GetCurve(state, AlphArray(5));
225 1 : if (state.dataCTElectricGenerator->CTGenerator(genNum).ExhaustFlowCurve == nullptr) {
226 0 : ShowSevereItemNotFound(state, eoh, state.dataIPShortCut->cAlphaFieldNames(5), AlphArray(5));
227 0 : ErrorsFound = true;
228 : }
229 :
230 1 : state.dataCTElectricGenerator->CTGenerator(genNum).PLBasedExhaustTempCurve = Curve::GetCurve(state, AlphArray(6));
231 1 : if (state.dataCTElectricGenerator->CTGenerator(genNum).PLBasedExhaustTempCurve == nullptr) {
232 0 : ShowSevereItemNotFound(state, eoh, state.dataIPShortCut->cAlphaFieldNames(6), AlphArray(6));
233 0 : ErrorsFound = true;
234 : }
235 :
236 1 : state.dataCTElectricGenerator->CTGenerator(genNum).TempBasedExhaustTempCurve = Curve::GetCurve(state, AlphArray(7));
237 1 : if (state.dataCTElectricGenerator->CTGenerator(genNum).TempBasedExhaustTempCurve == nullptr) {
238 0 : ShowSevereItemNotFound(state, eoh, state.dataIPShortCut->cAlphaFieldNames(7), AlphArray(7));
239 0 : ErrorsFound = true;
240 : }
241 :
242 1 : state.dataCTElectricGenerator->CTGenerator(genNum).QLubeOilRecoveredCurve = Curve::GetCurve(state, AlphArray(8));
243 1 : if (state.dataCTElectricGenerator->CTGenerator(genNum).QLubeOilRecoveredCurve == nullptr) {
244 0 : ShowSevereItemNotFound(state, eoh, state.dataIPShortCut->cAlphaFieldNames(8), AlphArray(8));
245 0 : ErrorsFound = true;
246 : }
247 :
248 1 : state.dataCTElectricGenerator->CTGenerator(genNum).UACoef[0] = NumArray(5);
249 1 : state.dataCTElectricGenerator->CTGenerator(genNum).UACoef[1] = NumArray(6);
250 :
251 1 : state.dataCTElectricGenerator->CTGenerator(genNum).MaxExhaustperCTPower = NumArray(7);
252 1 : state.dataCTElectricGenerator->CTGenerator(genNum).DesignMinExitGasTemp = NumArray(8);
253 1 : state.dataCTElectricGenerator->CTGenerator(genNum).DesignAirInletTemp = NumArray(9);
254 1 : state.dataCTElectricGenerator->CTGenerator(genNum).FuelHeatingValue = NumArray(10);
255 1 : state.dataCTElectricGenerator->CTGenerator(genNum).DesignHeatRecVolFlowRate = NumArray(11);
256 :
257 1 : if (state.dataCTElectricGenerator->CTGenerator(genNum).DesignHeatRecVolFlowRate > 0.0) {
258 0 : state.dataCTElectricGenerator->CTGenerator(genNum).HeatRecActive = true;
259 0 : state.dataCTElectricGenerator->CTGenerator(genNum).HeatRecInletNodeNum =
260 0 : NodeInputManager::GetOnlySingleNode(state,
261 0 : AlphArray(9),
262 : ErrorsFound,
263 : DataLoopNode::ConnectionObjectType::GeneratorCombustionTurbine,
264 0 : AlphArray(1),
265 : DataLoopNode::NodeFluidType::Water,
266 : DataLoopNode::ConnectionType::Inlet,
267 : NodeInputManager::CompFluidStream::Primary,
268 : DataLoopNode::ObjectIsNotParent);
269 0 : if (state.dataCTElectricGenerator->CTGenerator(genNum).HeatRecInletNodeNum == 0) {
270 0 : ShowSevereError(
271 0 : state, format("Missing Node Name, Heat Recovery Inlet, for {}={}", state.dataIPShortCut->cCurrentModuleObject, AlphArray(1)));
272 0 : ErrorsFound = true;
273 : }
274 0 : state.dataCTElectricGenerator->CTGenerator(genNum).HeatRecOutletNodeNum =
275 0 : NodeInputManager::GetOnlySingleNode(state,
276 0 : AlphArray(10),
277 : ErrorsFound,
278 : DataLoopNode::ConnectionObjectType::GeneratorCombustionTurbine,
279 0 : AlphArray(1),
280 : DataLoopNode::NodeFluidType::Water,
281 : DataLoopNode::ConnectionType::Outlet,
282 : NodeInputManager::CompFluidStream::Primary,
283 : DataLoopNode::ObjectIsNotParent);
284 0 : if (state.dataCTElectricGenerator->CTGenerator(genNum).HeatRecOutletNodeNum == 0) {
285 0 : ShowSevereError(
286 : state,
287 0 : format("Missing Node Name, Heat Recovery Outlet, for {}={}", state.dataIPShortCut->cCurrentModuleObject, AlphArray(1)));
288 0 : ErrorsFound = true;
289 : }
290 0 : BranchNodeConnections::TestCompSet(
291 0 : state, state.dataIPShortCut->cCurrentModuleObject, AlphArray(1), AlphArray(9), AlphArray(10), "Heat Recovery Nodes");
292 0 : PlantUtilities::RegisterPlantCompDesignFlow(state,
293 0 : state.dataCTElectricGenerator->CTGenerator(genNum).HeatRecInletNodeNum,
294 0 : state.dataCTElectricGenerator->CTGenerator(genNum).DesignHeatRecVolFlowRate);
295 : } else {
296 1 : state.dataCTElectricGenerator->CTGenerator(genNum).HeatRecActive = false;
297 1 : state.dataCTElectricGenerator->CTGenerator(genNum).HeatRecInletNodeNum = 0;
298 1 : state.dataCTElectricGenerator->CTGenerator(genNum).HeatRecOutletNodeNum = 0;
299 1 : if (!state.dataIPShortCut->lAlphaFieldBlanks(9) || !state.dataIPShortCut->lAlphaFieldBlanks(10)) {
300 0 : ShowWarningError(state,
301 0 : format("Since Design Heat Flow Rate = 0.0, Heat Recovery inactive for {}={}",
302 0 : state.dataIPShortCut->cCurrentModuleObject,
303 : AlphArray(1)));
304 0 : ShowContinueError(state, "However, Node names were specified for Heat Recovery inlet or outlet nodes");
305 : }
306 : }
307 :
308 : // Validate fuel type input
309 1 : state.dataCTElectricGenerator->CTGenerator(genNum).FuelType =
310 1 : static_cast<Constant::eFuel>(getEnumValue(Constant::eFuelNamesUC, AlphArray(11)));
311 1 : if (state.dataCTElectricGenerator->CTGenerator(genNum).FuelType == Constant::eFuel::Invalid) {
312 0 : ShowSevereError(state, format("Invalid {}={}", state.dataIPShortCut->cAlphaFieldNames(11), AlphArray(11)));
313 0 : ShowContinueError(state, format("Entered in {}={}", state.dataIPShortCut->cCurrentModuleObject, AlphArray(1)));
314 0 : ErrorsFound = true;
315 : }
316 :
317 1 : state.dataCTElectricGenerator->CTGenerator(genNum).HeatRecMaxTemp = NumArray(12);
318 :
319 : // begin CR7021
320 1 : if (state.dataIPShortCut->lAlphaFieldBlanks(12)) {
321 1 : state.dataCTElectricGenerator->CTGenerator(genNum).OAInletNode = 0;
322 : } else {
323 0 : state.dataCTElectricGenerator->CTGenerator(genNum).OAInletNode =
324 0 : NodeInputManager::GetOnlySingleNode(state,
325 0 : AlphArray(12),
326 : ErrorsFound,
327 : DataLoopNode::ConnectionObjectType::GeneratorCombustionTurbine,
328 0 : AlphArray(1),
329 : DataLoopNode::NodeFluidType::Air,
330 : DataLoopNode::ConnectionType::OutsideAirReference,
331 : NodeInputManager::CompFluidStream::Primary,
332 : DataLoopNode::ObjectIsNotParent);
333 0 : if (!OutAirNodeManager::CheckOutAirNodeNumber(state, state.dataCTElectricGenerator->CTGenerator(genNum).OAInletNode)) {
334 0 : ShowSevereError(state,
335 0 : format("{}, \"{}\" Outdoor Air Inlet Node Name not valid Outdoor Air Node= {}",
336 0 : state.dataIPShortCut->cCurrentModuleObject,
337 0 : state.dataCTElectricGenerator->CTGenerator(genNum).Name,
338 : AlphArray(12)));
339 0 : ShowContinueError(state, "...does not appear in an OutdoorAir:NodeList or as an OutdoorAir:Node.");
340 0 : ErrorsFound = true;
341 : }
342 : }
343 : }
344 :
345 1 : if (ErrorsFound) {
346 0 : ShowFatalError(state, format("Errors found in processing input for {}", state.dataIPShortCut->cCurrentModuleObject));
347 : }
348 1 : }
349 :
350 0 : void CTGeneratorData::setupOutputVars(EnergyPlusData &state)
351 : {
352 0 : std::string_view const sFuelType = Constant::eFuelNames[static_cast<int>(this->FuelType)];
353 0 : SetupOutputVariable(state,
354 : "Generator Produced AC Electricity Rate",
355 : Constant::Units::W,
356 0 : this->ElecPowerGenerated,
357 : OutputProcessor::TimeStepType::System,
358 : OutputProcessor::StoreType::Average,
359 0 : this->Name);
360 :
361 0 : SetupOutputVariable(state,
362 : "Generator Produced AC Electricity Energy",
363 : Constant::Units::J,
364 0 : this->ElecEnergyGenerated,
365 : OutputProcessor::TimeStepType::System,
366 : OutputProcessor::StoreType::Sum,
367 0 : this->Name,
368 : Constant::eResource::ElectricityProduced,
369 : OutputProcessor::Group::Plant,
370 : OutputProcessor::EndUseCat::Cogeneration);
371 :
372 0 : SetupOutputVariable(state,
373 0 : format("Generator {} Rate", sFuelType),
374 : Constant::Units::W,
375 0 : this->FuelEnergyUseRate,
376 : OutputProcessor::TimeStepType::System,
377 : OutputProcessor::StoreType::Average,
378 0 : this->Name);
379 :
380 0 : SetupOutputVariable(state,
381 0 : format("Generator {} Energy", sFuelType),
382 : Constant::Units::J,
383 0 : this->FuelEnergy,
384 : OutputProcessor::TimeStepType::System,
385 : OutputProcessor::StoreType::Sum,
386 0 : this->Name,
387 0 : Constant::eFuel2eResource[(int)this->FuelType],
388 : OutputProcessor::Group::Plant,
389 : OutputProcessor::EndUseCat::Cogeneration);
390 :
391 : // general fuel use report (to match other generators)
392 0 : SetupOutputVariable(state,
393 : "Generator Fuel HHV Basis Rate",
394 : Constant::Units::W,
395 0 : this->FuelEnergyUseRate,
396 : OutputProcessor::TimeStepType::System,
397 : OutputProcessor::StoreType::Average,
398 0 : this->Name);
399 :
400 0 : SetupOutputVariable(state,
401 : "Generator Fuel HHV Basis Energy",
402 : Constant::Units::J,
403 0 : this->FuelEnergy,
404 : OutputProcessor::TimeStepType::System,
405 : OutputProcessor::StoreType::Sum,
406 0 : this->Name);
407 :
408 0 : SetupOutputVariable(state,
409 0 : format("Generator {} Mass Flow Rate", sFuelType),
410 : Constant::Units::kg_s,
411 0 : this->FuelMdot,
412 : OutputProcessor::TimeStepType::System,
413 : OutputProcessor::StoreType::Average,
414 0 : this->Name);
415 :
416 0 : SetupOutputVariable(state,
417 : "Generator Exhaust Air Temperature",
418 : Constant::Units::C,
419 0 : this->ExhaustStackTemp,
420 : OutputProcessor::TimeStepType::System,
421 : OutputProcessor::StoreType::Average,
422 0 : this->Name);
423 :
424 0 : if (this->HeatRecActive) {
425 0 : SetupOutputVariable(state,
426 : "Generator Exhaust Heat Recovery Rate",
427 : Constant::Units::W,
428 0 : this->QExhaustRecovered,
429 : OutputProcessor::TimeStepType::System,
430 : OutputProcessor::StoreType::Average,
431 0 : this->Name);
432 :
433 0 : SetupOutputVariable(state,
434 : "Generator Exhaust Heat Recovery Energy",
435 : Constant::Units::J,
436 0 : this->ExhaustEnergyRec,
437 : OutputProcessor::TimeStepType::System,
438 : OutputProcessor::StoreType::Sum,
439 0 : this->Name,
440 : Constant::eResource::EnergyTransfer,
441 : OutputProcessor::Group::Plant,
442 : OutputProcessor::EndUseCat::HeatRecovery);
443 :
444 0 : SetupOutputVariable(state,
445 : "Generator Lube Heat Recovery Rate",
446 : Constant::Units::W,
447 0 : this->QLubeOilRecovered,
448 : OutputProcessor::TimeStepType::System,
449 : OutputProcessor::StoreType::Average,
450 0 : this->Name);
451 :
452 0 : SetupOutputVariable(state,
453 : "Generator Lube Heat Recovery Energy",
454 : Constant::Units::J,
455 0 : this->LubeOilEnergyRec,
456 : OutputProcessor::TimeStepType::System,
457 : OutputProcessor::StoreType::Sum,
458 0 : this->Name,
459 : Constant::eResource::EnergyTransfer,
460 : OutputProcessor::Group::Plant,
461 : OutputProcessor::EndUseCat::HeatRecovery);
462 :
463 0 : SetupOutputVariable(state,
464 : "Generator Produced Thermal Rate",
465 : Constant::Units::W,
466 0 : this->QTotalHeatRecovered,
467 : OutputProcessor::TimeStepType::System,
468 : OutputProcessor::StoreType::Average,
469 0 : this->Name);
470 :
471 0 : SetupOutputVariable(state,
472 : "Generator Produced Thermal Energy",
473 : Constant::Units::J,
474 0 : this->TotalHeatEnergyRec,
475 : OutputProcessor::TimeStepType::System,
476 : OutputProcessor::StoreType::Sum,
477 0 : this->Name);
478 :
479 0 : SetupOutputVariable(state,
480 : "Generator Heat Recovery Inlet Temperature",
481 : Constant::Units::C,
482 0 : this->HeatRecInletTemp,
483 : OutputProcessor::TimeStepType::System,
484 : OutputProcessor::StoreType::Average,
485 0 : this->Name);
486 :
487 0 : SetupOutputVariable(state,
488 : "Generator Heat Recovery Outlet Temperature",
489 : Constant::Units::C,
490 0 : this->HeatRecOutletTemp,
491 : OutputProcessor::TimeStepType::System,
492 : OutputProcessor::StoreType::Average,
493 0 : this->Name);
494 :
495 0 : SetupOutputVariable(state,
496 : "Generator Heat Recovery Mass Flow Rate",
497 : Constant::Units::kg_s,
498 0 : this->HeatRecMdot,
499 : OutputProcessor::TimeStepType::System,
500 : OutputProcessor::StoreType::Average,
501 0 : this->Name);
502 : }
503 0 : }
504 :
505 0 : void CTGeneratorData::CalcCTGeneratorModel(EnergyPlusData &state,
506 : bool const RunFlag, // TRUE when Generator operating
507 : Real64 const MyLoad, // Generator demand
508 : bool const FirstHVACIteration)
509 : {
510 : // SUBROUTINE INFORMATION:
511 : // AUTHOR Dan Fisher
512 : // DATE WRITTEN Sept. 2000
513 :
514 : // PURPOSE OF THIS SUBROUTINE:
515 : // simulate a vapor compression Generator using the CT model
516 :
517 : // METHODOLOGY EMPLOYED:
518 : // curve fit of performance data. This model was originally
519 : // developed by Dale Herron for the BLAST program
520 :
521 0 : Real64 constexpr exhaustCp(1.047); // Exhaust Gas Specific Heat (J/kg-K)
522 0 : Real64 constexpr KJtoJ(1000.0); // convert Kjoules to joules
523 : static constexpr std::string_view RoutineName("CalcCTGeneratorModel");
524 :
525 : // min allowed operating frac full load
526 0 : Real64 minPartLoadRat = this->MinPartLoadRat;
527 :
528 : // max allowed operating frac full load
529 0 : Real64 maxPartLoadRat = this->MaxPartLoadRat;
530 :
531 : // Generator nominal capacity (W)
532 0 : Real64 ratedPowerOutput = this->RatedPowerOutput;
533 :
534 : // MAX EXHAUST FLOW PER W POWER OUTPUT COEFF
535 0 : Real64 maxExhaustperCTPower = this->MaxExhaustperCTPower;
536 :
537 : // design turbine inlet temperature (C)
538 0 : Real64 designAirInletTemp = this->DesignAirInletTemp;
539 :
540 : Real64 heatRecInTemp; // Heat Recovery Fluid Inlet Temperature (C)
541 :
542 : Real64 heatRecMdot; // Heat Recovery Fluid Mass FlowRate (kg/s)
543 : Real64 heatRecCp; // Specific Heat of the Heat Recovery Fluid (J/kg-K)
544 :
545 0 : if (this->HeatRecActive) {
546 0 : int heatRecInNode = this->HeatRecInletNodeNum;
547 0 : heatRecInTemp = state.dataLoopNodes->Node(heatRecInNode).Temp;
548 :
549 0 : heatRecCp = state.dataPlnt->PlantLoop(this->HRPlantLoc.loopNum).glycol->getSpecificHeat(state, heatRecInTemp, RoutineName);
550 0 : if (FirstHVACIteration && RunFlag) {
551 0 : heatRecMdot = this->DesignHeatRecMassFlowRate;
552 : } else {
553 0 : heatRecMdot = state.dataLoopNodes->Node(heatRecInNode).MassFlowRate;
554 : }
555 : } else {
556 0 : heatRecInTemp = 0.0;
557 0 : heatRecCp = 0.0;
558 0 : heatRecMdot = 0.0;
559 : }
560 :
561 : // If no loop demand or Generator OFF, return
562 0 : if (!RunFlag) {
563 0 : this->ElecPowerGenerated = 0.0;
564 0 : this->ElecEnergyGenerated = 0.0;
565 0 : this->HeatRecInletTemp = heatRecInTemp;
566 0 : this->HeatRecOutletTemp = heatRecInTemp;
567 0 : this->HeatRecMdot = 0.0;
568 0 : this->QLubeOilRecovered = 0.0;
569 0 : this->QExhaustRecovered = 0.0;
570 0 : this->QTotalHeatRecovered = 0.0;
571 0 : this->LubeOilEnergyRec = 0.0;
572 0 : this->ExhaustEnergyRec = 0.0;
573 0 : this->TotalHeatEnergyRec = 0.0;
574 0 : this->FuelEnergyUseRate = 0.0;
575 0 : this->FuelEnergy = 0.0;
576 0 : this->FuelMdot = 0.0;
577 0 : this->ExhaustStackTemp = 0.0;
578 0 : return;
579 : }
580 :
581 : // CALCULATE POWER GENERATED AND PLR
582 : // Generator output (W)
583 0 : Real64 elecPowerGenerated = min(MyLoad, ratedPowerOutput);
584 0 : elecPowerGenerated = max(elecPowerGenerated, 0.0);
585 :
586 : // Generator operating part load ratio
587 0 : Real64 PLR = min(elecPowerGenerated / ratedPowerOutput, maxPartLoadRat);
588 0 : PLR = max(PLR, minPartLoadRat);
589 0 : elecPowerGenerated = PLR * ratedPowerOutput;
590 :
591 : // SET OFF-DESIGN AIR TEMPERATURE DIFFERENCE
592 : // (ATAIR) Difference between ambient actual and ambient design temperatures
593 : Real64 ambientDeltaT;
594 0 : if (this->OAInletNode == 0) {
595 0 : ambientDeltaT = state.dataEnvrn->OutDryBulbTemp - designAirInletTemp;
596 : } else {
597 0 : ambientDeltaT = state.dataLoopNodes->Node(this->OAInletNode).Temp - designAirInletTemp;
598 : }
599 :
600 : // Use Curve fit to determine Fuel Energy Input. For electric power generated in Watts, the fuel
601 : // energy input is calculated in J/s. The PLBasedFuelInputCurve selects ratio of fuel flow (J/s)/power generated (J/s).
602 : // The TempBasedFuelInputCurve is a correction based on deviation from design inlet air temperature conditions.
603 : // The first coefficient of this fit should be 1.0 to ensure that no correction is made at design conditions.
604 : // (EFUEL) rate of Fuel Energy Required to run COMBUSTION turbine (W)
605 : Real64 FuelUseRate =
606 0 : elecPowerGenerated * this->PLBasedFuelInputCurve->value(state, PLR) * this->TempBasedFuelInputCurve->value(state, ambientDeltaT);
607 :
608 : // Use Curve fit to determine Exhaust Flow. This curve shows the ratio of exhaust gas flow (kg/s) to electric power
609 : // output (J/s). The units on ExhaustFlowCurve are (kg/J). When multiplied by the rated power of the unit,
610 : // it gives the exhaust flow rate in kg/s
611 : // (FEX) Exhaust Gas Flow Rate cubic meters per second???
612 0 : Real64 exhaustFlow = ratedPowerOutput * this->ExhaustFlowCurve->value(state, ambientDeltaT);
613 :
614 : // Use Curve fit to determine Exhaust Temperature. This curve calculates the exhaust temperature (C) by
615 : // multiplying the exhaust temperature (C) for a particular part load as given by PLBasedExhaustTempCurve
616 : // a correction factor based on the deviation from design temperature, TempBasedExhaustTempCurve
617 :
618 : Real64 QExhaustRec; // recovered exhaust heat (W)
619 : Real64 exhaustStackTemp; // turbine stack temp. (C)
620 0 : if ((PLR > 0.0) && ((exhaustFlow > 0.0) || (maxExhaustperCTPower > 0.0))) {
621 :
622 : // (TEX) Exhaust Gas Temperature in C
623 0 : Real64 exhaustTemp = this->PLBasedExhaustTempCurve->value(state, PLR) * this->TempBasedExhaustTempCurve->value(state, ambientDeltaT);
624 :
625 : // (UACGC) Heat Exchanger UA to Capacity
626 0 : Real64 UA_loc = this->UACoef[0] * std::pow(ratedPowerOutput, this->UACoef[1]);
627 :
628 : // design engine stack saturated steam temp. (C)
629 0 : Real64 designMinExitGasTemp = this->DesignMinExitGasTemp;
630 :
631 0 : exhaustStackTemp = designMinExitGasTemp + (exhaustTemp - designMinExitGasTemp) /
632 0 : std::exp(UA_loc / (max(exhaustFlow, maxExhaustperCTPower * ratedPowerOutput) * exhaustCp));
633 :
634 0 : QExhaustRec = max(exhaustFlow * exhaustCp * (exhaustTemp - exhaustStackTemp), 0.0);
635 0 : } else {
636 0 : exhaustStackTemp = this->DesignMinExitGasTemp;
637 0 : QExhaustRec = 0.0;
638 : }
639 :
640 : // Use Curve fit to determine Heat Recovered Lubricant heat. This curve calculates the lube heat recovered (J/s) by
641 : // multiplying the total power generated by the fraction of that power that could be recovered in the lube oil at that
642 : // particular part load.
643 : // recovered lube oil heat (W)
644 0 : Real64 QLubeOilRec = elecPowerGenerated * this->QLubeOilRecoveredCurve->value(state, PLR);
645 :
646 : // Check for divide by zero
647 : Real64 HeatRecOutTemp; // Heat Recovery Fluid Outlet Temperature (C)
648 0 : if ((heatRecMdot > 0.0) && (heatRecCp > 0.0)) {
649 0 : HeatRecOutTemp = (QExhaustRec + QLubeOilRec) / (heatRecMdot * heatRecCp) + heatRecInTemp;
650 : } else {
651 0 : heatRecMdot = 0.0;
652 0 : HeatRecOutTemp = heatRecInTemp;
653 0 : QExhaustRec = 0.0;
654 0 : QLubeOilRec = 0.0;
655 : }
656 :
657 : // Now verify the maximum temperature was not exceeded
658 : // Heat Recovery Flow Rate if minimal heat recovery is accomplished
659 0 : Real64 MinHeatRecMdot = 0.0;
660 :
661 : Real64 HRecRatio; // When Max Temp is reached the amount of recovered heat has to be reduced.
662 :
663 0 : if (HeatRecOutTemp > this->HeatRecMaxTemp) {
664 0 : if (this->HeatRecMaxTemp != heatRecInTemp) {
665 0 : MinHeatRecMdot = (QExhaustRec + QLubeOilRec) / (heatRecCp * (this->HeatRecMaxTemp - heatRecInTemp));
666 0 : if (MinHeatRecMdot < 0.0) MinHeatRecMdot = 0.0;
667 : }
668 :
669 : // Recalculate Outlet Temperature, with adjusted flowrate
670 0 : if ((MinHeatRecMdot > 0.0) && (heatRecCp > 0.0)) {
671 0 : HeatRecOutTemp = (QExhaustRec + QLubeOilRec) / (MinHeatRecMdot * heatRecCp) + heatRecInTemp;
672 0 : HRecRatio = heatRecMdot / MinHeatRecMdot;
673 : } else {
674 0 : HeatRecOutTemp = heatRecInTemp;
675 0 : HRecRatio = 0.0;
676 : }
677 0 : QLubeOilRec *= HRecRatio;
678 0 : QExhaustRec *= HRecRatio;
679 : }
680 :
681 : // Calculate Energy
682 : // Generator output (J)
683 0 : Real64 ElectricEnergyGen = elecPowerGenerated * state.dataHVACGlobal->TimeStepSysSec;
684 :
685 : // Amount of Fuel Energy Required to run COMBUSTION turbine (J)
686 0 : Real64 FuelEnergyUsed = FuelUseRate * state.dataHVACGlobal->TimeStepSysSec;
687 :
688 : // recovered lube oil heat (J)
689 0 : Real64 lubeOilEnergyRec = QLubeOilRec * state.dataHVACGlobal->TimeStepSysSec;
690 :
691 : // recovered exhaust heat (J)
692 0 : Real64 exhaustEnergyRec = QExhaustRec * state.dataHVACGlobal->TimeStepSysSec;
693 :
694 0 : this->ElecPowerGenerated = elecPowerGenerated;
695 0 : this->ElecEnergyGenerated = ElectricEnergyGen;
696 :
697 0 : this->HeatRecInletTemp = heatRecInTemp;
698 0 : this->HeatRecOutletTemp = HeatRecOutTemp;
699 :
700 0 : this->HeatRecMdot = heatRecMdot;
701 0 : this->QExhaustRecovered = QExhaustRec;
702 0 : this->QLubeOilRecovered = QLubeOilRec;
703 0 : this->QTotalHeatRecovered = QExhaustRec + QLubeOilRec;
704 0 : this->FuelEnergyUseRate = std::abs(FuelUseRate);
705 0 : this->ExhaustEnergyRec = exhaustEnergyRec;
706 0 : this->LubeOilEnergyRec = lubeOilEnergyRec;
707 0 : this->TotalHeatEnergyRec = exhaustEnergyRec + lubeOilEnergyRec;
708 0 : this->FuelEnergy = std::abs(FuelEnergyUsed);
709 :
710 : // Heating Value of Fuel in (kJ/kg)
711 0 : Real64 fuelHeatingValue = this->FuelHeatingValue;
712 :
713 0 : this->FuelMdot = std::abs(FuelUseRate) / (fuelHeatingValue * KJtoJ);
714 :
715 0 : this->ExhaustStackTemp = exhaustStackTemp;
716 :
717 0 : if (this->HeatRecActive) {
718 0 : int HeatRecOutletNode = this->HeatRecOutletNodeNum;
719 0 : state.dataLoopNodes->Node(HeatRecOutletNode).Temp = this->HeatRecOutletTemp;
720 : }
721 : }
722 :
723 0 : void CTGeneratorData::InitCTGenerators(EnergyPlusData &state,
724 : bool const RunFlag, // TRUE when Generator operating
725 : bool const FirstHVACIteration)
726 : {
727 :
728 : // SUBROUTINE INFORMATION:
729 : // AUTHOR Dan Fisher
730 : // DATE WRITTEN Oct 2000
731 : // RE-ENGINEERED Brent Griffith, Sept 2010 plant upgrades, generalize fluid props
732 :
733 : // PURPOSE OF THIS SUBROUTINE:
734 : // This subroutine is for initializations of the CT generators.
735 :
736 0 : this->oneTimeInit(state); // Do one-time inits
737 :
738 : // Do the Begin Environment initializations
739 0 : if (state.dataGlobal->BeginEnvrnFlag && this->MyEnvrnFlag && this->HeatRecActive) {
740 0 : int HeatRecInletNode = this->HeatRecInletNodeNum;
741 0 : int HeatRecOutletNode = this->HeatRecOutletNodeNum;
742 : // set the node Temperature, assuming freeze control
743 0 : state.dataLoopNodes->Node(HeatRecInletNode).Temp = 20.0;
744 0 : state.dataLoopNodes->Node(HeatRecOutletNode).Temp = 20.0;
745 : // set the node max and min mass flow rates
746 0 : PlantUtilities::InitComponentNodes(state, 0.0, this->DesignHeatRecMassFlowRate, HeatRecInletNode, HeatRecOutletNode);
747 :
748 0 : this->MyEnvrnFlag = false;
749 : } // end environmental inits
750 :
751 0 : if (!state.dataGlobal->BeginEnvrnFlag) {
752 0 : this->MyEnvrnFlag = true;
753 : }
754 :
755 0 : if (this->HeatRecActive) {
756 0 : if (FirstHVACIteration) {
757 : Real64 mdot;
758 0 : if (RunFlag) {
759 0 : mdot = this->DesignHeatRecMassFlowRate;
760 : } else {
761 0 : mdot = 0.0;
762 : }
763 0 : PlantUtilities::SetComponentFlowRate(state, mdot, this->HeatRecInletNodeNum, this->HeatRecOutletNodeNum, this->HRPlantLoc);
764 :
765 : } else {
766 0 : PlantUtilities::SetComponentFlowRate(
767 0 : state, this->HeatRecMdot, this->HeatRecInletNodeNum, this->HeatRecOutletNodeNum, this->HRPlantLoc);
768 : }
769 : }
770 0 : }
771 :
772 0 : void CTGeneratorData::oneTimeInit(EnergyPlusData &state)
773 : {
774 0 : std::string_view constexpr RoutineName("InitICEngineGenerators");
775 :
776 0 : if (this->MyPlantScanFlag) { // this flag to be removed
777 0 : if (allocated(state.dataPlnt->PlantLoop) && this->HeatRecActive) {
778 0 : bool errFlag = false;
779 0 : PlantUtilities::ScanPlantLoopsForObject(
780 0 : state, this->Name, DataPlant::PlantEquipmentType::Generator_CTurbine, this->HRPlantLoc, errFlag, _, _, _, _, _);
781 0 : if (errFlag) {
782 0 : ShowFatalError(state, "InitCTGenerators: Program terminated due to previous condition(s).");
783 : }
784 : }
785 :
786 0 : this->MyPlantScanFlag = false;
787 : }
788 :
789 0 : if (this->MyFlag) {
790 0 : this->setupOutputVars(state);
791 0 : this->MyFlag = false;
792 : }
793 :
794 0 : if (this->MySizeAndNodeInitFlag && (!this->MyPlantScanFlag) && this->HeatRecActive) {
795 0 : int HeatRecInletNode = this->HeatRecInletNodeNum;
796 0 : int HeatRecOutletNode = this->HeatRecOutletNodeNum;
797 :
798 : // size mass flow rate
799 0 : Real64 rho = state.dataPlnt->PlantLoop(this->HRPlantLoc.loopNum).glycol->getDensity(state, Constant::InitConvTemp, RoutineName);
800 :
801 0 : this->DesignHeatRecMassFlowRate = rho * this->DesignHeatRecVolFlowRate;
802 :
803 0 : PlantUtilities::InitComponentNodes(state, 0.0, this->DesignHeatRecMassFlowRate, HeatRecInletNode, HeatRecOutletNode);
804 :
805 0 : this->MySizeAndNodeInitFlag = false;
806 : }
807 0 : }
808 :
809 : } // namespace CTElectricGenerator
810 :
811 : } // namespace EnergyPlus
|