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