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