Line data Source code
1 : // EnergyPlus, Copyright (c) 1996-2025, The Board of Trustees of the University of Illinois,
2 : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
3 : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
4 : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
5 : // contributors. All rights reserved.
6 : //
7 : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
8 : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
9 : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
10 : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
11 : // derivative works, and perform publicly and display publicly, and to permit others to do so.
12 : //
13 : // Redistribution and use in source and binary forms, with or without modification, are permitted
14 : // provided that the following conditions are met:
15 : //
16 : // (1) Redistributions of source code must retain the above copyright notice, this list of
17 : // conditions and the following disclaimer.
18 : //
19 : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
20 : // conditions and the following disclaimer in the documentation and/or other materials
21 : // provided with the distribution.
22 : //
23 : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
24 : // the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
25 : // used to endorse or promote products derived from this software without specific prior
26 : // written permission.
27 : //
28 : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
29 : // without changes from the version obtained under this License, or (ii) Licensee makes a
30 : // reference solely to the software portion of its product, Licensee must refer to the
31 : // software as "EnergyPlus version X" software, where "X" is the version number Licensee
32 : // obtained under this License and may not use a different name for the software. Except as
33 : // specifically required in this Section (4), Licensee shall not use in a company name, a
34 : // product name, in advertising, publicity, or other promotional activities any name, trade
35 : // name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
36 : // similar designation, without the U.S. Department of Energy's prior written consent.
37 : //
38 : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
39 : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
40 : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
41 : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
43 : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44 : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
45 : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46 : // POSSIBILITY OF SUCH DAMAGE.
47 :
48 : // C++ Headers
49 : #include <cmath>
50 :
51 : // ObjexxFCL Headers
52 : #include <ObjexxFCL/Array.functions.hh>
53 : #include <ObjexxFCL/Fmath.hh>
54 :
55 : // EnergyPlus Headers
56 : #include <EnergyPlus/BranchNodeConnections.hh>
57 : #include <EnergyPlus/CurveManager.hh>
58 : #include <EnergyPlus/Data/EnergyPlusData.hh>
59 : #include <EnergyPlus/DataEnvironment.hh>
60 : #include <EnergyPlus/DataGenerators.hh>
61 : #include <EnergyPlus/DataGlobalConstants.hh>
62 : #include <EnergyPlus/DataHVACGlobals.hh>
63 : #include <EnergyPlus/DataHeatBalance.hh>
64 : #include <EnergyPlus/DataIPShortCuts.hh>
65 : #include <EnergyPlus/DataLoopNode.hh>
66 : #include <EnergyPlus/DataSizing.hh>
67 : #include <EnergyPlus/FluidProperties.hh>
68 : #include <EnergyPlus/General.hh>
69 : #include <EnergyPlus/GeneratorDynamicsManager.hh>
70 : #include <EnergyPlus/GeneratorFuelSupply.hh>
71 : #include <EnergyPlus/HeatBalanceInternalHeatGains.hh>
72 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
73 : #include <EnergyPlus/MicroCHPElectricGenerator.hh>
74 : #include <EnergyPlus/NodeInputManager.hh>
75 : #include <EnergyPlus/OutputProcessor.hh>
76 : #include <EnergyPlus/Plant/DataPlant.hh>
77 : #include <EnergyPlus/PlantUtilities.hh>
78 : #include <EnergyPlus/ScheduleManager.hh>
79 : #include <EnergyPlus/UtilityRoutines.hh>
80 : #include <EnergyPlus/ZoneTempPredictorCorrector.hh>
81 :
82 : namespace EnergyPlus::MicroCHPElectricGenerator {
83 :
84 : // MODULE INFORMATION:
85 : // AUTHOR Brent Griffith
86 : // DATE WRITTEN June 2006
87 : // MODIFIED na
88 : // RE-ENGINEERED na
89 :
90 : // PURPOSE OF THIS MODULE:
91 : // This module simulates the operation of Internal Combustion and Stirling Engine
92 : // residential-scale generators for combined heat and power.
93 :
94 : // METHODOLOGY EMPLOYED:
95 : // Once the ElectricPowerManager determines that the Combustion Generator
96 : // is available to meet an electric load demand, it calls SimCombustionGenerator
97 : // which in turn calls the Combustion model.
98 : // See DataFuelCells.cc for structures and variables
99 :
100 : // REFERENCES:
101 : // IEA/ECBCS Annex 42 model specification titled: " A Generic Model Specification for
102 : // Combustion-based Residential CHP Devices" Alex Ferguson, Nick Kelly, IEA Annex 42.
103 : // Module developed from
104 :
105 2 : PlantComponent *MicroCHPDataStruct::factory(EnergyPlusData &state, std::string const &objectName)
106 : {
107 : // Process the input data
108 2 : if (state.dataCHPElectGen->getMicroCHPInputFlag) {
109 1 : GetMicroCHPGeneratorInput(state);
110 1 : state.dataCHPElectGen->getMicroCHPInputFlag = false;
111 : }
112 :
113 : // Now look for this object
114 3 : for (auto &thisMCHP : state.dataCHPElectGen->MicroCHP) {
115 3 : if (thisMCHP.Name == objectName) {
116 2 : return &thisMCHP;
117 : }
118 : }
119 : // If we didn't find it, fatal
120 : ShowFatalError(state, format("LocalMicroCHPGenFactory: Error getting inputs for micro-CHP gen named: {}", objectName)); // LCOV_EXCL_LINE
121 : // Shut up the compiler
122 : return nullptr; // LCOV_EXCL_LINE
123 : }
124 :
125 1 : void GetMicroCHPGeneratorInput(EnergyPlusData &state)
126 : {
127 : // SUBROUTINE INFORMATION:
128 : // AUTHOR: Brent Griffith
129 : // DATE WRITTEN: July 2005
130 :
131 : // PURPOSE OF THIS SUBROUTINE:
132 : // This routine will get the input
133 : // required by the Micro CHP Generator models.
134 :
135 : // METHODOLOGY EMPLOYED:
136 : // EnergyPlus input processor
137 : static constexpr std::string_view routineName = "GetMicroCHPGeneratorInput";
138 :
139 1 : Array1D_string AlphArray(25); // character string data
140 1 : Array1D<Real64> NumArray(200); // numeric data TODO deal with allocatable for extensible
141 :
142 1 : auto &s_ipsc = state.dataIPShortCut;
143 :
144 1 : if (state.dataCHPElectGen->MyOneTimeFlag) {
145 1 : int NumAlphas = 0; // Number of elements in the alpha array
146 1 : int NumNums = 0; // Number of elements in the numeric array
147 1 : int IOStat = 0; // IO Status when calling get input subroutine
148 1 : bool ErrorsFound = false; // error flag
149 :
150 : // call to Fuel supply module to set up data there.
151 1 : GeneratorFuelSupply::GetGeneratorFuelSupplyInput(state);
152 :
153 : // First get the Micro CHP Parameters so they can be nested in structure later
154 1 : s_ipsc->cCurrentModuleObject = "Generator:MicroCHP:NonNormalizedParameters";
155 1 : state.dataCHPElectGen->NumMicroCHPParams = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, s_ipsc->cCurrentModuleObject);
156 :
157 1 : if (state.dataCHPElectGen->NumMicroCHPParams <= 0) {
158 0 : ShowSevereError(state, format("No {} equipment specified in input file", s_ipsc->cCurrentModuleObject));
159 0 : ErrorsFound = true;
160 : }
161 :
162 1 : state.dataCHPElectGen->MicroCHPParamInput.allocate(state.dataCHPElectGen->NumMicroCHPParams);
163 :
164 2 : for (int CHPParamNum = 1; CHPParamNum <= state.dataCHPElectGen->NumMicroCHPParams; ++CHPParamNum) {
165 3 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
166 1 : s_ipsc->cCurrentModuleObject,
167 : CHPParamNum,
168 : AlphArray,
169 : NumAlphas,
170 : NumArray,
171 : NumNums,
172 : IOStat,
173 : _,
174 1 : s_ipsc->lAlphaFieldBlanks,
175 1 : s_ipsc->cAlphaFieldNames,
176 1 : s_ipsc->cNumericFieldNames);
177 :
178 1 : ErrorObjectHeader eoh{routineName, s_ipsc->cCurrentModuleObject, AlphArray(1)};
179 :
180 1 : auto µCHPParams = state.dataCHPElectGen->MicroCHPParamInput(CHPParamNum);
181 :
182 1 : std::string ObjMSGName = s_ipsc->cCurrentModuleObject + " Named " + AlphArray(1);
183 :
184 1 : microCHPParams.Name = AlphArray(1); // A1 name
185 1 : microCHPParams.MaxElecPower = NumArray(1); // N1 Maximum Electric Power [W]
186 1 : microCHPParams.MinElecPower = NumArray(2); // N2 Minimum Electric Power [W]
187 1 : microCHPParams.MinWaterMdot = NumArray(3); // N3 Minimum Cooling Water Flow Rate [kg/s]
188 1 : microCHPParams.MaxWaterTemp = NumArray(4); // N3 Maximum Cooling Water Inlet Temp [C]
189 :
190 1 : if (s_ipsc->lAlphaFieldBlanks(2)) {
191 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(2));
192 0 : ErrorsFound = true;
193 1 : } else if ((microCHPParams.ElecEffCurve = Curve::GetCurve(state, AlphArray(2))) == nullptr) {
194 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(2), AlphArray(2));
195 0 : ErrorsFound = true;
196 : }
197 :
198 1 : if (s_ipsc->lAlphaFieldBlanks(3)) {
199 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(3));
200 0 : ErrorsFound = true;
201 1 : } else if ((microCHPParams.ThermalEffCurve = Curve::GetCurve(state, AlphArray(3))) == nullptr) {
202 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(3), AlphArray(3));
203 0 : ErrorsFound = true;
204 : }
205 :
206 1 : if (Util::SameString(AlphArray(4), "InternalControl")) {
207 1 : microCHPParams.InternalFlowControl = true; // A4, \field Cooling Water Flow Rate Mode
208 1 : microCHPParams.PlantFlowControl = false;
209 : }
210 1 : if ((!(Util::SameString(AlphArray(4), "InternalControl"))) && (!(Util::SameString(AlphArray(4), "PlantControl")))) {
211 0 : ShowSevereError(state, format("Invalid, {} = {}", s_ipsc->cAlphaFieldNames(4), AlphArray(4)));
212 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, AlphArray(1)));
213 0 : ErrorsFound = true;
214 : }
215 1 : if (microCHPParams.InternalFlowControl) { // get the curve
216 1 : if (s_ipsc->lAlphaFieldBlanks(5)) {
217 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(5));
218 0 : ErrorsFound = true;
219 1 : } else if ((microCHPParams.WaterFlowCurve = Curve::GetCurve(state, AlphArray(5))) == nullptr) {
220 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(5), AlphArray(5));
221 0 : ErrorsFound = true;
222 : }
223 : }
224 :
225 1 : if (s_ipsc->lAlphaFieldBlanks(6)) {
226 0 : ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(6));
227 0 : ErrorsFound = true;
228 1 : } else if ((microCHPParams.AirFlowCurve = Curve::GetCurve(state, AlphArray(6))) == nullptr) {
229 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(6), AlphArray(6));
230 0 : ErrorsFound = true;
231 : }
232 :
233 : // Name of Curve for Air Flow Rate
234 1 : microCHPParams.DeltaPelMax = NumArray(5); // N5 Maximum rate of change in net electrical power [W/s]
235 1 : microCHPParams.DeltaFuelMdotMax = NumArray(6); // N6 Maximum Rate of change in fuel flow rate [kg/s2]
236 1 : microCHPParams.UAhx = NumArray(7); // N7 Heat Exchanger UA_hx
237 1 : microCHPParams.UAskin = NumArray(8); // N8 Skin Loss UA_loss
238 1 : microCHPParams.RadiativeFraction = NumArray(9); // N9 radiative fraction for skin losses
239 1 : microCHPParams.MCeng = NumArray(10); // N10 Aggregated Thermal Mass of Generator MC_eng
240 1 : if (microCHPParams.MCeng <= 0.0) {
241 0 : ShowSevereError(state, format("Invalid, {} = {:.5R}", s_ipsc->cNumericFieldNames(10), NumArray(10)));
242 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, AlphArray(1)));
243 0 : ShowContinueError(state, "Thermal mass must be greater than zero");
244 0 : ErrorsFound = true;
245 : }
246 1 : microCHPParams.MCcw = NumArray(11); // Aggregated Thermal Mass of Heat Recovery MC_cw
247 1 : if (microCHPParams.MCcw <= 0.0) {
248 0 : ShowSevereError(state, format("Invalid, {} = {:.5R}", s_ipsc->cNumericFieldNames(11), NumArray(11)));
249 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, AlphArray(1)));
250 0 : ShowContinueError(state, "Thermal mass must be greater than zero");
251 0 : ErrorsFound = true;
252 : }
253 1 : microCHPParams.Pstandby = NumArray(12); // N12 Standby Power [W]
254 :
255 1 : if (Util::SameString(AlphArray(7), "TimeDelay")) {
256 1 : microCHPParams.WarmUpByTimeDelay = true;
257 1 : microCHPParams.WarmUpByEngineTemp = false;
258 : }
259 1 : if ((!(Util::SameString(AlphArray(7), "NominalEngineTemperature"))) && (!(Util::SameString(AlphArray(7), "TimeDelay")))) {
260 0 : ShowSevereError(state, format("Invalid, {} = {}", s_ipsc->cAlphaFieldNames(7), AlphArray(7)));
261 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, AlphArray(1)));
262 0 : ErrorsFound = true;
263 : }
264 1 : microCHPParams.kf = NumArray(13); // N13 Warmup Fuel Flow Rate Coefficient k_f
265 1 : microCHPParams.TnomEngOp = NumArray(14); // N14 Nominal Engine Operating Temperature [C]
266 1 : microCHPParams.kp = NumArray(15); // N15 Warmup Power Coefficient k_p
267 1 : microCHPParams.Rfuelwarmup = NumArray(16); // N16 Warm Up Fuel Flow Rate Limit Ratio
268 1 : microCHPParams.WarmUpDelay = NumArray(17); // N17 Warm Up Delay Time
269 :
270 1 : microCHPParams.PcoolDown = NumArray(18); // N18 Cool Down Power
271 :
272 1 : microCHPParams.CoolDownDelay = NumArray(19); // N19 Cool Down Delay Time in seconds
273 :
274 1 : if (Util::SameString(AlphArray(8), "MandatoryCoolDown")) {
275 0 : microCHPParams.MandatoryFullCoolDown = true;
276 0 : microCHPParams.WarmRestartOkay = false;
277 : }
278 1 : if ((!(Util::SameString(AlphArray(8), "MandatoryCoolDown"))) && (!(Util::SameString(AlphArray(8), "OptionalCoolDown")))) {
279 0 : ShowSevereError(state, format("Invalid, {} = {}", s_ipsc->cAlphaFieldNames(8), AlphArray(8)));
280 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, AlphArray(1)));
281 0 : ErrorsFound = true;
282 : }
283 1 : }
284 :
285 1 : s_ipsc->cCurrentModuleObject = "Generator:MicroCHP";
286 1 : state.dataCHPElectGen->NumMicroCHPs = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, s_ipsc->cCurrentModuleObject);
287 :
288 1 : if (state.dataCHPElectGen->NumMicroCHPs <= 0) {
289 : // shouldn't ever come here?
290 0 : ShowSevereError(state, format("No {} equipment specified in input file", s_ipsc->cCurrentModuleObject));
291 0 : ErrorsFound = true;
292 : }
293 :
294 1 : if (!(allocated(state.dataCHPElectGen->MicroCHP))) {
295 1 : state.dataCHPElectGen->MicroCHP.allocate(state.dataCHPElectGen->NumMicroCHPs); // inits handled in derived type definitions
296 : }
297 :
298 : // load in Micro CHPs
299 3 : for (int GeneratorNum = 1; GeneratorNum <= state.dataCHPElectGen->NumMicroCHPs; ++GeneratorNum) {
300 6 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
301 2 : s_ipsc->cCurrentModuleObject,
302 : GeneratorNum,
303 : AlphArray,
304 : NumAlphas,
305 : NumArray,
306 : NumNums,
307 : IOStat,
308 : _,
309 2 : s_ipsc->lAlphaFieldBlanks,
310 2 : s_ipsc->cAlphaFieldNames,
311 2 : s_ipsc->cNumericFieldNames);
312 :
313 2 : ErrorObjectHeader eoh{routineName, s_ipsc->cCurrentModuleObject, AlphArray(1)};
314 :
315 2 : auto µCHP = state.dataCHPElectGen->MicroCHP(GeneratorNum);
316 : // GENERATOR:MICRO CHP,
317 2 : microCHP.DynamicsControlID = GeneratorNum;
318 2 : microCHP.Name = AlphArray(1); // A1 Generator name
319 2 : microCHP.ParamObjName = AlphArray(2); // A2 Micro CHP Parameter Object Name
320 : // find input structure
321 2 : int thisParamID = Util::FindItemInList(AlphArray(2), state.dataCHPElectGen->MicroCHPParamInput);
322 2 : if (thisParamID != 0) {
323 2 : microCHP.A42Model = state.dataCHPElectGen->MicroCHPParamInput(thisParamID); // entire structure of input data assigned here!
324 : } else {
325 0 : ShowSevereError(state, format("Invalid, {} = {}", s_ipsc->cAlphaFieldNames(2), AlphArray(2)));
326 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, AlphArray(1)));
327 0 : ErrorsFound = true;
328 : }
329 :
330 2 : if (!s_ipsc->lAlphaFieldBlanks(3)) {
331 0 : microCHP.ZoneName = AlphArray(3); // A3 Zone Name
332 0 : microCHP.ZoneID = Util::FindItemInList(microCHP.ZoneName, state.dataHeatBal->Zone);
333 0 : if (microCHP.ZoneID == 0) {
334 0 : ShowSevereError(state, format("Invalid, {} = {}", s_ipsc->cAlphaFieldNames(3), AlphArray(3)));
335 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, AlphArray(1)));
336 0 : ErrorsFound = true;
337 : }
338 : } else {
339 2 : microCHP.ZoneID = 0;
340 : }
341 2 : microCHP.PlantInletNodeName = AlphArray(4); // A4 Cooling Water Inlet Node Name
342 2 : microCHP.PlantOutletNodeName = AlphArray(5); // A5 Cooling Water Outlet Node Name
343 : // find node ids for water path
344 2 : microCHP.PlantInletNodeID = NodeInputManager::GetOnlySingleNode(state,
345 2 : AlphArray(4),
346 : ErrorsFound,
347 : DataLoopNode::ConnectionObjectType::GeneratorMicroCHP,
348 2 : AlphArray(1),
349 : DataLoopNode::NodeFluidType::Water,
350 : DataLoopNode::ConnectionType::Inlet,
351 : NodeInputManager::CompFluidStream::Primary,
352 : DataLoopNode::ObjectIsNotParent);
353 4 : microCHP.PlantOutletNodeID = NodeInputManager::GetOnlySingleNode(state,
354 2 : AlphArray(5),
355 : ErrorsFound,
356 : DataLoopNode::ConnectionObjectType::GeneratorMicroCHP,
357 2 : AlphArray(1),
358 : DataLoopNode::NodeFluidType::Water,
359 : DataLoopNode::ConnectionType::Outlet,
360 : NodeInputManager::CompFluidStream::Primary,
361 : DataLoopNode::ObjectIsNotParent);
362 2 : BranchNodeConnections::TestCompSet(state, s_ipsc->cCurrentModuleObject, AlphArray(1), AlphArray(4), AlphArray(5), "Heat Recovery Nodes");
363 :
364 2 : microCHP.AirInletNodeName = AlphArray(6); // A6 Air Inlet Node Name
365 : // check the node connections
366 2 : microCHP.AirInletNodeID = NodeInputManager::GetOnlySingleNode(state,
367 2 : AlphArray(6),
368 : ErrorsFound,
369 : DataLoopNode::ConnectionObjectType::GeneratorMicroCHP,
370 2 : AlphArray(1),
371 : DataLoopNode::NodeFluidType::Air,
372 : DataLoopNode::ConnectionType::Inlet,
373 : NodeInputManager::CompFluidStream::Secondary,
374 : DataLoopNode::ObjectIsNotParent);
375 :
376 2 : microCHP.AirOutletNodeName = AlphArray(7); // A7 Air Outlet Node Name
377 2 : microCHP.AirOutletNodeID = NodeInputManager::GetOnlySingleNode(state,
378 2 : AlphArray(7),
379 : ErrorsFound,
380 : DataLoopNode::ConnectionObjectType::GeneratorMicroCHP,
381 2 : AlphArray(1),
382 : DataLoopNode::NodeFluidType::Air,
383 : DataLoopNode::ConnectionType::Outlet,
384 : NodeInputManager::CompFluidStream::Secondary,
385 : DataLoopNode::ObjectIsNotParent);
386 :
387 2 : microCHP.FuelSupplyID = Util::FindItemInList(AlphArray(8), state.dataGenerator->FuelSupply); // Fuel Supply ID
388 2 : if (microCHP.FuelSupplyID == 0) {
389 0 : ShowSevereError(state, format("Invalid, {} = {}", s_ipsc->cAlphaFieldNames(8), AlphArray(8)));
390 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, AlphArray(1)));
391 0 : ErrorsFound = true;
392 : }
393 :
394 2 : if (s_ipsc->lAlphaFieldBlanks(9)) {
395 2 : microCHP.availSched = Sched::GetScheduleAlwaysOn(state);
396 0 : } else if ((microCHP.availSched = Sched::GetSchedule(state, AlphArray(9))) == nullptr) {
397 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(9), AlphArray(9));
398 0 : ErrorsFound = true;
399 : }
400 2 : microCHP.A42Model.TengLast = 20.0; // inits
401 2 : microCHP.A42Model.TempCWOutLast = 20.0; // inits
402 : }
403 :
404 1 : if (ErrorsFound) {
405 0 : ShowFatalError(state, format("Errors found in processing input for {}", s_ipsc->cCurrentModuleObject));
406 : }
407 :
408 : // setup report variables
409 3 : for (int GeneratorNum = 1; GeneratorNum <= state.dataCHPElectGen->NumMicroCHPs; ++GeneratorNum) {
410 : }
411 :
412 1 : state.dataCHPElectGen->MyOneTimeFlag = false;
413 : }
414 1 : }
415 :
416 2 : void MicroCHPDataStruct::setupOutputVars(EnergyPlusData &state)
417 : {
418 4 : SetupOutputVariable(state,
419 : "Generator Off Mode Time",
420 : Constant::Units::s,
421 2 : this->A42Model.OffModeTime,
422 : OutputProcessor::TimeStepType::System,
423 : OutputProcessor::StoreType::Sum,
424 2 : this->Name);
425 :
426 4 : SetupOutputVariable(state,
427 : "Generator Standby Mode Time",
428 : Constant::Units::s,
429 2 : this->A42Model.StandyByModeTime,
430 : OutputProcessor::TimeStepType::System,
431 : OutputProcessor::StoreType::Sum,
432 2 : this->Name);
433 :
434 4 : SetupOutputVariable(state,
435 : "Generator Warm Up Mode Time",
436 : Constant::Units::s,
437 2 : this->A42Model.WarmUpModeTime,
438 : OutputProcessor::TimeStepType::System,
439 : OutputProcessor::StoreType::Sum,
440 2 : this->Name);
441 :
442 4 : SetupOutputVariable(state,
443 : "Generator Normal Operating Mode Time",
444 : Constant::Units::s,
445 2 : this->A42Model.NormalModeTime,
446 : OutputProcessor::TimeStepType::System,
447 : OutputProcessor::StoreType::Sum,
448 2 : this->Name);
449 :
450 4 : SetupOutputVariable(state,
451 : "Generator Cool Down Mode Time",
452 : Constant::Units::s,
453 2 : this->A42Model.CoolDownModeTime,
454 : OutputProcessor::TimeStepType::System,
455 : OutputProcessor::StoreType::Sum,
456 2 : this->Name);
457 :
458 4 : SetupOutputVariable(state,
459 : "Generator Produced AC Electricity Rate",
460 : Constant::Units::W,
461 2 : this->A42Model.ACPowerGen,
462 : OutputProcessor::TimeStepType::System,
463 : OutputProcessor::StoreType::Average,
464 2 : this->Name);
465 :
466 4 : SetupOutputVariable(state,
467 : "Generator Produced AC Electricity Energy",
468 : Constant::Units::J,
469 2 : this->A42Model.ACEnergyGen,
470 : OutputProcessor::TimeStepType::System,
471 : OutputProcessor::StoreType::Sum,
472 2 : this->Name,
473 : Constant::eResource::ElectricityProduced,
474 : OutputProcessor::Group::Plant,
475 : OutputProcessor::EndUseCat::Cogeneration);
476 :
477 4 : SetupOutputVariable(state,
478 : "Generator Produced Thermal Rate",
479 : Constant::Units::W,
480 2 : this->A42Model.QdotHR,
481 : OutputProcessor::TimeStepType::System,
482 : OutputProcessor::StoreType::Average,
483 2 : this->Name);
484 :
485 4 : SetupOutputVariable(state,
486 : "Generator Produced Thermal Energy",
487 : Constant::Units::J,
488 2 : this->A42Model.TotalHeatEnergyRec,
489 : OutputProcessor::TimeStepType::System,
490 : OutputProcessor::StoreType::Sum,
491 2 : this->Name,
492 : Constant::eResource::EnergyTransfer,
493 : OutputProcessor::Group::Plant,
494 : OutputProcessor::EndUseCat::Cogeneration);
495 :
496 4 : SetupOutputVariable(state,
497 : "Generator Electric Efficiency",
498 : Constant::Units::None,
499 2 : this->A42Model.ElecEff,
500 : OutputProcessor::TimeStepType::System,
501 : OutputProcessor::StoreType::Average,
502 2 : this->Name);
503 :
504 4 : SetupOutputVariable(state,
505 : "Generator Thermal Efficiency",
506 : Constant::Units::None,
507 2 : this->A42Model.ThermEff,
508 : OutputProcessor::TimeStepType::System,
509 : OutputProcessor::StoreType::Average,
510 2 : this->Name);
511 :
512 4 : SetupOutputVariable(state,
513 : "Generator Gross Input Heat Rate",
514 : Constant::Units::W,
515 2 : this->A42Model.Qgross,
516 : OutputProcessor::TimeStepType::System,
517 : OutputProcessor::StoreType::Average,
518 2 : this->Name);
519 :
520 4 : SetupOutputVariable(state,
521 : "Generator Steady State Engine Heat Generation Rate",
522 : Constant::Units::W,
523 2 : this->A42Model.Qgenss,
524 : OutputProcessor::TimeStepType::System,
525 : OutputProcessor::StoreType::Average,
526 2 : this->Name);
527 :
528 4 : SetupOutputVariable(state,
529 : "Generator Engine Heat Exchange Rate",
530 : Constant::Units::W,
531 2 : this->A42Model.QdotHX,
532 : OutputProcessor::TimeStepType::System,
533 : OutputProcessor::StoreType::Average,
534 2 : this->Name);
535 :
536 4 : SetupOutputVariable(state,
537 : "Generator Air Mass Flow Rate",
538 : Constant::Units::kg_s,
539 2 : this->A42Model.MdotAir,
540 : OutputProcessor::TimeStepType::System,
541 : OutputProcessor::StoreType::Average,
542 2 : this->Name);
543 :
544 4 : SetupOutputVariable(state,
545 : "Generator Fuel Molar Flow Rate",
546 : Constant::Units::kmol_s,
547 2 : this->A42Model.NdotFuel,
548 : OutputProcessor::TimeStepType::System,
549 : OutputProcessor::StoreType::Average,
550 2 : this->Name);
551 :
552 4 : SetupOutputVariable(state,
553 : "Generator Fuel Mass Flow Rate",
554 : Constant::Units::kg_s,
555 2 : this->A42Model.MdotFuel,
556 : OutputProcessor::TimeStepType::System,
557 : OutputProcessor::StoreType::Average,
558 2 : this->Name);
559 :
560 4 : SetupOutputVariable(state,
561 : "Generator Engine Temperature",
562 : Constant::Units::C,
563 2 : this->A42Model.Teng,
564 : OutputProcessor::TimeStepType::System,
565 : OutputProcessor::StoreType::Average,
566 2 : this->Name);
567 :
568 4 : SetupOutputVariable(state,
569 : "Generator Coolant Inlet Temperature",
570 : Constant::Units::C,
571 2 : this->A42Model.HeatRecInletTemp,
572 : OutputProcessor::TimeStepType::System,
573 : OutputProcessor::StoreType::Average,
574 2 : this->Name);
575 :
576 4 : SetupOutputVariable(state,
577 : "Generator Coolant Outlet Temperature",
578 : Constant::Units::C,
579 2 : this->A42Model.HeatRecOutletTemp,
580 : OutputProcessor::TimeStepType::System,
581 : OutputProcessor::StoreType::Average,
582 2 : this->Name);
583 :
584 : // this next one needs to be reconciled with non-gas fuel constituents.
585 : // need custom resourceTypeKey or something for user defined fuel compositions.
586 4 : SetupOutputVariable(state,
587 : "Generator Fuel HHV Basis Energy",
588 : Constant::Units::J,
589 2 : this->A42Model.FuelEnergyHHV,
590 : OutputProcessor::TimeStepType::System,
591 : OutputProcessor::StoreType::Sum,
592 2 : this->Name,
593 : Constant::eResource::NaturalGas,
594 : OutputProcessor::Group::Plant,
595 : OutputProcessor::EndUseCat::Cogeneration);
596 :
597 4 : SetupOutputVariable(state,
598 : "Generator Fuel HHV Basis Rate",
599 : Constant::Units::W,
600 2 : this->A42Model.FuelEnergyUseRateHHV,
601 : OutputProcessor::TimeStepType::System,
602 : OutputProcessor::StoreType::Average,
603 2 : this->Name);
604 :
605 4 : SetupOutputVariable(state,
606 : "Generator Fuel LHV Basis Energy",
607 : Constant::Units::J,
608 2 : this->A42Model.FuelEnergyLHV,
609 : OutputProcessor::TimeStepType::System,
610 : OutputProcessor::StoreType::Sum,
611 2 : this->Name);
612 :
613 4 : SetupOutputVariable(state,
614 : "Generator Fuel LHV Basis Rate",
615 : Constant::Units::W,
616 2 : this->A42Model.FuelEnergyUseRateLHV,
617 : OutputProcessor::TimeStepType::System,
618 : OutputProcessor::StoreType::Average,
619 2 : this->Name);
620 :
621 4 : SetupOutputVariable(state,
622 : "Generator Fuel Compressor Electricity Rate",
623 : Constant::Units::W,
624 2 : this->A42Model.FuelCompressPower,
625 : OutputProcessor::TimeStepType::System,
626 : OutputProcessor::StoreType::Average,
627 2 : this->Name);
628 :
629 4 : SetupOutputVariable(state,
630 : "Generator Fuel Compressor Electricity Energy",
631 : Constant::Units::J,
632 2 : this->A42Model.FuelCompressEnergy,
633 : OutputProcessor::TimeStepType::System,
634 : OutputProcessor::StoreType::Sum,
635 2 : this->Name);
636 :
637 4 : SetupOutputVariable(state,
638 : "Generator Fuel Compressor Skin Heat Loss Rate",
639 : Constant::Units::W,
640 2 : this->A42Model.FuelCompressSkinLoss,
641 : OutputProcessor::TimeStepType::System,
642 : OutputProcessor::StoreType::Average,
643 2 : this->Name);
644 :
645 4 : SetupOutputVariable(state,
646 : "Generator Zone Sensible Heat Transfer Rate",
647 : Constant::Units::W,
648 2 : this->A42Model.SkinLossPower,
649 : OutputProcessor::TimeStepType::System,
650 : OutputProcessor::StoreType::Average,
651 2 : this->Name);
652 :
653 4 : SetupOutputVariable(state,
654 : "Generator Zone Sensible Heat Transfer Energy",
655 : Constant::Units::J,
656 2 : this->A42Model.SkinLossEnergy,
657 : OutputProcessor::TimeStepType::System,
658 : OutputProcessor::StoreType::Sum,
659 2 : this->Name);
660 :
661 4 : SetupOutputVariable(state,
662 : "Generator Zone Convection Heat Transfer Rate",
663 : Constant::Units::W,
664 2 : this->A42Model.SkinLossConvect,
665 : OutputProcessor::TimeStepType::System,
666 : OutputProcessor::StoreType::Average,
667 2 : this->Name);
668 :
669 4 : SetupOutputVariable(state,
670 : "Generator Zone Radiation Heat Transfer Rate",
671 : Constant::Units::W,
672 2 : this->A42Model.SkinLossRadiat,
673 : OutputProcessor::TimeStepType::System,
674 : OutputProcessor::StoreType::Average,
675 2 : this->Name);
676 :
677 2 : if (this->ZoneID > 0) {
678 0 : SetupZoneInternalGain(state,
679 : this->ZoneID,
680 : this->Name,
681 : DataHeatBalance::IntGainType::GeneratorMicroCHP,
682 : &this->A42Model.SkinLossConvect,
683 : nullptr,
684 : &this->A42Model.SkinLossRadiat);
685 : }
686 2 : }
687 :
688 2 : void MicroCHPDataStruct::simulate(EnergyPlusData &state,
689 : [[maybe_unused]] const EnergyPlus::PlantLocation &calledFromLocation,
690 : bool FirstHVACIteration,
691 : [[maybe_unused]] Real64 &CurLoad,
692 : [[maybe_unused]] bool RunFlag)
693 : {
694 : // empty function to emulate current behavior as of conversion to using the PlantComponent calling structure.
695 : // calls from the plant side only update the nodes.
696 : // calls from the ElectricPowerServiceManger call the init, calc, and update worker functions
697 :
698 2 : PlantUtilities::UpdateComponentHeatRecoverySide(state,
699 : this->CWPlantLoc.loopNum,
700 : this->CWPlantLoc.loopSideNum,
701 : DataPlant::PlantEquipmentType::Generator_MicroCHP,
702 : this->PlantInletNodeID,
703 : this->PlantOutletNodeID,
704 : this->A42Model.QdotHR,
705 : this->A42Model.HeatRecInletTemp,
706 : this->A42Model.HeatRecOutletTemp,
707 : this->PlantMassFlowRate,
708 : FirstHVACIteration);
709 2 : }
710 :
711 2 : void MicroCHPDataStruct::onInitLoopEquip(EnergyPlusData &state, const EnergyPlus::PlantLocation &)
712 : {
713 : static constexpr std::string_view RoutineName("MicroCHPDataStruct::onInitLoopEquip");
714 :
715 2 : Real64 rho = state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum)
716 2 : .glycol->getDensity(state, state.dataLoopNodes->Node(this->PlantInletNodeID).Temp, RoutineName);
717 2 : if (this->A42Model.InternalFlowControl) { // got a curve
718 2 : this->PlantMassFlowRateMax =
719 2 : 2.0 * this->A42Model.WaterFlowCurve->value(state, this->A42Model.MaxElecPower, state.dataLoopNodes->Node(this->PlantInletNodeID).Temp);
720 0 : } else if (this->CWPlantLoc.loopSideNum == DataPlant::LoopSideLocation::Supply) {
721 0 : if (state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).MaxMassFlowRate > 0.0) {
722 0 : this->PlantMassFlowRateMax = state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).MaxMassFlowRate;
723 0 : } else if (state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).PlantSizNum > 0) {
724 0 : this->PlantMassFlowRateMax = state.dataSize->PlantSizData(this->CWPlantLoc.loopNum).DesVolFlowRate * rho;
725 : } else {
726 0 : this->PlantMassFlowRateMax = 2.0;
727 : }
728 :
729 0 : } else if (this->CWPlantLoc.loopSideNum == DataPlant::LoopSideLocation::Demand) {
730 0 : this->PlantMassFlowRateMax = 2.0; // would like to use plant loop max but not ready yet
731 : }
732 :
733 2 : PlantUtilities::RegisterPlantCompDesignFlow(state, this->PlantInletNodeID, this->PlantMassFlowRateMax / rho);
734 :
735 6 : this->A42Model.ElecEff = this->A42Model.ElecEffCurve->value(
736 2 : state, this->A42Model.MaxElecPower, this->PlantMassFlowRateMax, state.dataLoopNodes->Node(this->PlantInletNodeID).Temp);
737 :
738 6 : this->A42Model.ThermEff = this->A42Model.ThermalEffCurve->value(
739 2 : state, this->A42Model.MaxElecPower, this->PlantMassFlowRateMax, state.dataLoopNodes->Node(this->PlantInletNodeID).Temp);
740 :
741 2 : GeneratorDynamicsManager::SetupGeneratorControlStateManager(state, this->DynamicsControlID);
742 2 : }
743 :
744 2 : void MicroCHPDataStruct::InitMicroCHPNoNormalizeGenerators(EnergyPlusData &state)
745 : {
746 : // SUBROUTINE INFORMATION:
747 : // AUTHOR BGriffith
748 : // DATE WRITTEN March 2007
749 : // MODIFIED na
750 : // RE-ENGINEERED na
751 :
752 2 : this->oneTimeInit(state);
753 :
754 2 : if (!state.dataGlobal->SysSizingCalc && this->MySizeFlag && !this->MyPlantScanFlag && (state.dataPlnt->PlantFirstSizesOkayToFinalize)) {
755 0 : this->MySizeFlag = false;
756 : }
757 :
758 2 : if (this->MySizeFlag) return;
759 :
760 0 : int DynaCntrlNum = this->DynamicsControlID;
761 :
762 0 : if (state.dataGlobal->BeginEnvrnFlag && this->MyEnvrnFlag) {
763 : // reset to starting condition for different environment runperiods, design days
764 0 : this->A42Model.TengLast = 20.0;
765 0 : this->A42Model.TempCWOutLast = 20.0;
766 0 : this->A42Model.TimeElapsed = 0.0;
767 0 : this->A42Model.OpMode = DataGenerators::OperatingMode::Invalid;
768 0 : this->A42Model.OffModeTime = 0.0;
769 0 : this->A42Model.StandyByModeTime = 0.0;
770 0 : this->A42Model.WarmUpModeTime = 0.0;
771 0 : this->A42Model.NormalModeTime = 0.0;
772 0 : this->A42Model.CoolDownModeTime = 0.0;
773 0 : this->A42Model.Pnet = 0.0;
774 0 : this->A42Model.ElecEff = 0.0;
775 0 : this->A42Model.Qgross = 0.0;
776 0 : this->A42Model.ThermEff = 0.0;
777 0 : this->A42Model.Qgenss = 0.0;
778 0 : this->A42Model.NdotFuel = 0.0;
779 0 : this->A42Model.MdotFuel = 0.0;
780 0 : this->A42Model.Teng = 20.0;
781 0 : this->A42Model.TcwIn = 20.0;
782 0 : this->A42Model.TcwOut = 20.0;
783 0 : this->A42Model.MdotAir = 0.0;
784 0 : this->A42Model.QdotSkin = 0.0;
785 0 : this->A42Model.QdotConvZone = 0.0;
786 0 : this->A42Model.QdotRadZone = 0.0;
787 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).LastOpMode = DataGenerators::OperatingMode::Off;
788 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).CurrentOpMode = DataGenerators::OperatingMode::Off;
789 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).FractionalDayofLastShutDown = 0.0;
790 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).FractionalDayofLastStartUp = 0.0;
791 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).HasBeenOn = false;
792 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).DuringStartUp = false;
793 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).DuringShutDown = false;
794 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).FuelMdotLastTimestep = 0.0;
795 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).PelLastTimeStep = 0.0;
796 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).NumCycles = 0;
797 :
798 0 : state.dataGenerator->FuelSupply(this->FuelSupplyID).QskinLoss = 0.0;
799 :
800 0 : PlantUtilities::InitComponentNodes(state, 0.0, this->PlantMassFlowRateMax, this->PlantInletNodeID, this->PlantOutletNodeID);
801 : }
802 :
803 0 : if (!state.dataGlobal->BeginEnvrnFlag) {
804 0 : this->MyEnvrnFlag = true;
805 : }
806 :
807 : Real64 TimeElapsed =
808 0 : state.dataGlobal->HourOfDay + state.dataGlobal->TimeStep * state.dataGlobal->TimeStepZone + state.dataHVACGlobal->SysTimeElapsed;
809 0 : if (this->A42Model.TimeElapsed != TimeElapsed) {
810 : // The simulation has advanced to the next system timestep. Save conditions from the end of the previous system
811 : // timestep for use as the initial conditions of each iteration that does not advance the system timestep.
812 0 : this->A42Model.TengLast = this->A42Model.Teng;
813 0 : this->A42Model.TempCWOutLast = this->A42Model.TcwOut;
814 0 : this->A42Model.TimeElapsed = TimeElapsed;
815 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).LastOpMode = state.dataGenerator->GeneratorDynamics(DynaCntrlNum).CurrentOpMode;
816 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).FuelMdotLastTimestep = this->A42Model.MdotFuel;
817 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).PelLastTimeStep = this->A42Model.Pnet;
818 : }
819 :
820 0 : if (!this->A42Model.InternalFlowControl) {
821 :
822 0 : Real64 mdot = this->PlantMassFlowRateMax;
823 0 : PlantUtilities::SetComponentFlowRate(state, mdot, this->PlantInletNodeID, this->PlantOutletNodeID, this->CWPlantLoc);
824 0 : this->PlantMassFlowRate = mdot;
825 : }
826 : }
827 :
828 0 : void MicroCHPDataStruct::CalcMicroCHPNoNormalizeGeneratorModel(EnergyPlusData &state,
829 : bool const RunFlagElectCenter, // TRUE when Generator operating
830 : bool const RunFlagPlant,
831 : Real64 const MyElectricLoad, // Generator demand
832 : Real64 const MyThermalLoad)
833 : {
834 :
835 : // SUBROUTINE INFORMATION:
836 : // AUTHOR B Griffith
837 : // DATE WRITTEN July 2006
838 : // MODIFIED na
839 : // RE-ENGINEERED na
840 :
841 : // PURPOSE OF THIS SUBROUTINE:
842 : // Main calculation subroutine for the IEA Annex 42 model
843 :
844 : // METHODOLOGY EMPLOYED:
845 : // curve fit, dynamic control limits,
846 :
847 : // REFERENCES:
848 : // IEA Annex 42 FC-COGEN-SIM "A Generic Model Specification for Combustion-based Residential CHP Devices"
849 : // Alex Ferguson, Nick Kelly, Version 3, June 26, 2006
850 :
851 : static constexpr std::string_view RoutineName("CalcMicroCHPNoNormalizeGeneratorModel");
852 :
853 0 : DataGenerators::OperatingMode CurrentOpMode = DataGenerators::OperatingMode::Invalid;
854 : Real64 NdotFuel;
855 0 : Real64 AllowedLoad = 0.0;
856 0 : Real64 PLRforSubtimestepStartUp(1.0);
857 0 : Real64 PLRforSubtimestepShutDown(0.0);
858 :
859 0 : GeneratorDynamicsManager::ManageGeneratorControlState(state,
860 : this->DynamicsControlID,
861 : RunFlagElectCenter,
862 : RunFlagPlant,
863 : MyElectricLoad,
864 : MyThermalLoad,
865 : AllowedLoad,
866 : CurrentOpMode,
867 : PLRforSubtimestepStartUp,
868 : PLRforSubtimestepShutDown);
869 :
870 0 : Real64 Teng = this->A42Model.Teng;
871 0 : Real64 TcwOut = this->A42Model.TcwOut;
872 :
873 : Real64 thisAmbientTemp;
874 0 : if (this->ZoneID > 0) {
875 0 : thisAmbientTemp = state.dataZoneTempPredictorCorrector->zoneHeatBalance(this->ZoneID).MAT;
876 : } else { // outdoor location, no zone
877 0 : thisAmbientTemp = state.dataEnvrn->OutDryBulbTemp;
878 : }
879 :
880 0 : Real64 Pnetss = 0.0;
881 0 : Real64 Pstandby = 0.0; // power draw during standby, positive here means negative production
882 0 : Real64 Pcooler = 0.0; // power draw during cool down, positive here means negative production
883 0 : Real64 ElecEff = 0.0;
884 0 : Real64 MdotAir = 0.0;
885 0 : Real64 Qgenss = 0.0;
886 0 : Real64 MdotCW = 0.0;
887 0 : Real64 TcwIn = 0.0;
888 0 : Real64 MdotFuel = 0.0;
889 0 : Real64 Qgross = 0.0;
890 0 : Real64 ThermEff = 0.0;
891 :
892 0 : switch (CurrentOpMode) {
893 0 : case DataGenerators::OperatingMode::Off: { // same as standby in model spec but no Pnet standby electricity losses.
894 :
895 0 : Qgenss = 0.0;
896 0 : TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
897 0 : Pnetss = 0.0;
898 0 : Pstandby = 0.0;
899 0 : Pcooler = this->A42Model.PcoolDown * PLRforSubtimestepShutDown;
900 0 : ElecEff = 0.0;
901 0 : ThermEff = 0.0;
902 0 : Qgross = 0.0;
903 0 : NdotFuel = 0.0;
904 0 : MdotFuel = 0.0;
905 0 : MdotAir = 0.0;
906 :
907 0 : MdotCW = 0.0;
908 0 : PlantUtilities::SetComponentFlowRate(state, MdotCW, this->PlantInletNodeID, this->PlantOutletNodeID, this->CWPlantLoc);
909 0 : this->PlantMassFlowRate = MdotCW;
910 0 : } break;
911 0 : case DataGenerators::OperatingMode::Standby: {
912 0 : Qgenss = 0.0;
913 0 : TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
914 0 : Pnetss = 0.0;
915 0 : Pstandby = this->A42Model.Pstandby * (1.0 - PLRforSubtimestepShutDown);
916 0 : Pcooler = this->A42Model.PcoolDown * PLRforSubtimestepShutDown;
917 0 : ElecEff = 0.0;
918 0 : ThermEff = 0.0;
919 0 : Qgross = 0.0;
920 0 : NdotFuel = 0.0;
921 0 : MdotFuel = 0.0;
922 0 : MdotAir = 0.0;
923 :
924 0 : MdotCW = 0.0;
925 0 : PlantUtilities::SetComponentFlowRate(state, MdotCW, this->PlantInletNodeID, this->PlantOutletNodeID, this->CWPlantLoc);
926 0 : this->PlantMassFlowRate = MdotCW;
927 0 : } break;
928 0 : case DataGenerators::OperatingMode::WarmUp: {
929 0 : if (this->A42Model.WarmUpByTimeDelay) {
930 : // Internal combustion engine. This is just like normal operation but no net power yet.
931 0 : Pnetss = MyElectricLoad; // W
932 0 : Pstandby = 0.0;
933 0 : Pcooler = this->A42Model.PcoolDown * PLRforSubtimestepShutDown;
934 0 : TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
935 0 : MdotCW = state.dataLoopNodes->Node(this->PlantInletNodeID).MassFlowRate; // kg/s
936 0 : if (this->A42Model.InternalFlowControl) {
937 0 : MdotCW = GeneratorDynamicsManager::FuncDetermineCWMdotForInternalFlowControl(state, this->DynamicsControlID, Pnetss, TcwIn);
938 : }
939 0 : ElecEff = this->A42Model.ElecEffCurve->value(state, Pnetss, MdotCW, TcwIn);
940 0 : ElecEff = max(0.0, ElecEff); // protect against bad curve result
941 :
942 0 : if (ElecEff > 0.0) { // trap divide by bad thing
943 0 : Qgross = Pnetss / ElecEff; // W
944 : } else {
945 0 : Qgross = 0.0;
946 : }
947 0 : ThermEff = this->A42Model.ThermalEffCurve->value(state, Pnetss, MdotCW, TcwIn);
948 0 : ThermEff = max(0.0, ThermEff); // protect against bad curve result
949 :
950 0 : Qgenss = ThermEff * Qgross; // W
951 :
952 0 : MdotFuel = Qgross / (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0) *
953 0 : state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
954 : // kMol/s = (J/s) /(KJ/mol * 1000 J/KJ * 1000 mol/kmol)
955 :
956 0 : bool ConstrainedIncreasingNdot(false);
957 0 : bool ConstrainedDecreasingNdot(false);
958 0 : Real64 MdotFuelAllowed = 0.0;
959 :
960 0 : GeneratorDynamicsManager::ManageGeneratorFuelFlow(
961 : state, this->DynamicsControlID, MdotFuel, MdotFuelAllowed, ConstrainedIncreasingNdot, ConstrainedDecreasingNdot);
962 :
963 0 : if (ConstrainedIncreasingNdot || ConstrainedDecreasingNdot) { // recalculate Pnetss with new NdotFuel with iteration
964 0 : MdotFuel = MdotFuelAllowed;
965 0 : NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
966 0 : Qgross = NdotFuel * (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
967 :
968 0 : for (int i = 1; i <= 20; ++i) { // iterating here could add use of search method
969 0 : Pnetss = Qgross * ElecEff;
970 0 : if (this->A42Model.InternalFlowControl) {
971 0 : MdotCW = GeneratorDynamicsManager::FuncDetermineCWMdotForInternalFlowControl(state, this->DynamicsControlID, Pnetss, TcwIn);
972 : }
973 0 : ElecEff = this->A42Model.ElecEffCurve->value(state, Pnetss, MdotCW, TcwIn);
974 0 : ElecEff = max(0.0, ElecEff); // protect against bad curve result
975 : }
976 :
977 0 : ThermEff = this->A42Model.ThermalEffCurve->value(state, Pnetss, MdotCW, TcwIn);
978 0 : ThermEff = max(0.0, ThermEff); // protect against bad curve result
979 0 : Qgenss = ThermEff * Qgross; // W
980 : }
981 0 : Pnetss = 0.0; // no actually power produced here.
982 0 : NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
983 0 : MdotAir = this->A42Model.AirFlowCurve->value(state, MdotFuel);
984 0 : MdotAir = max(0.0, MdotAir); // protect against bad curve result
985 :
986 0 : } else if (this->A42Model.WarmUpByEngineTemp) {
987 : // Stirling engine mode warm up
988 : // find MdotFuelMax
989 0 : Real64 Pmax = this->A42Model.MaxElecPower;
990 0 : Pstandby = 0.0;
991 0 : Pcooler = this->A42Model.PcoolDown * PLRforSubtimestepShutDown; // could be here with part load in cool down
992 0 : TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
993 0 : MdotCW = state.dataLoopNodes->Node(this->PlantInletNodeID).MassFlowRate; // kg/s
994 0 : ElecEff = this->A42Model.ElecEffCurve->value(state, Pmax, MdotCW, TcwIn);
995 0 : ElecEff = max(0.0, ElecEff); // protect against bad curve result
996 0 : if (ElecEff > 0.0) { // trap divide by bad thing
997 0 : Qgross = Pmax / ElecEff; // W
998 : } else {
999 0 : Qgross = 0.0;
1000 : }
1001 0 : NdotFuel = Qgross / (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
1002 : // kMol/s = (J/s) /(KJ/mol * 1000 J/KJ * 1000 mol/kmol)
1003 0 : Real64 MdotFuelMax = NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1004 :
1005 : Real64 MdotFuelWarmup;
1006 0 : if (Teng > thisAmbientTemp) {
1007 0 : MdotFuelWarmup =
1008 0 : MdotFuelMax + this->A42Model.kf * MdotFuelMax * ((this->A42Model.TnomEngOp - thisAmbientTemp) / (Teng - thisAmbientTemp));
1009 : // check that numerical answer didn't blow up beyond limit, and reset if it did
1010 0 : if (MdotFuelWarmup > this->A42Model.Rfuelwarmup * MdotFuelMax) {
1011 0 : MdotFuelWarmup = this->A42Model.Rfuelwarmup * MdotFuelMax;
1012 : }
1013 : } else { // equal would divide by zero
1014 0 : MdotFuelWarmup = this->A42Model.Rfuelwarmup * MdotFuelMax;
1015 : }
1016 :
1017 0 : if (this->A42Model.TnomEngOp > thisAmbientTemp) {
1018 0 : Pnetss = Pmax * this->A42Model.kp * ((Teng - thisAmbientTemp) / (this->A42Model.TnomEngOp - thisAmbientTemp));
1019 : } else { // equal would divide by zero
1020 0 : Pnetss = Pmax;
1021 : }
1022 :
1023 0 : MdotFuel = MdotFuelWarmup;
1024 0 : NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1025 0 : MdotAir = this->A42Model.AirFlowCurve->value(state, MdotFuelWarmup);
1026 0 : MdotAir = max(0.0, MdotAir); // protect against bad curve result
1027 0 : Qgross = NdotFuel * (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
1028 0 : ThermEff = this->A42Model.ThermalEffCurve->value(state, Pmax, MdotCW, TcwIn);
1029 0 : Qgenss = ThermEff * Qgross; // W
1030 : }
1031 0 : } break;
1032 0 : case DataGenerators::OperatingMode::Normal: {
1033 0 : if (PLRforSubtimestepStartUp < 1.0) {
1034 0 : if (RunFlagElectCenter) Pnetss = MyElectricLoad; // W
1035 0 : if (RunFlagPlant) Pnetss = AllowedLoad;
1036 : } else {
1037 0 : Pnetss = AllowedLoad;
1038 : }
1039 0 : Pstandby = 0.0;
1040 0 : Pcooler = 0.0;
1041 0 : TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
1042 0 : MdotCW = state.dataLoopNodes->Node(this->PlantInletNodeID).MassFlowRate; // kg/s
1043 0 : if (this->A42Model.InternalFlowControl) {
1044 0 : MdotCW = GeneratorDynamicsManager::FuncDetermineCWMdotForInternalFlowControl(state, this->DynamicsControlID, Pnetss, TcwIn);
1045 : }
1046 :
1047 0 : ElecEff = this->A42Model.ElecEffCurve->value(state, Pnetss, MdotCW, TcwIn);
1048 0 : ElecEff = max(0.0, ElecEff); // protect against bad curve result
1049 :
1050 0 : if (ElecEff > 0.0) { // trap divide by bad thing
1051 0 : Qgross = Pnetss / ElecEff; // W
1052 : } else {
1053 0 : Qgross = 0.0;
1054 : }
1055 :
1056 0 : ThermEff = this->A42Model.ThermalEffCurve->value(state, Pnetss, MdotCW, TcwIn);
1057 0 : ThermEff = max(0.0, ThermEff); // protect against bad curve result
1058 0 : Qgenss = ThermEff * Qgross; // W
1059 0 : MdotFuel = Qgross / (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0) *
1060 0 : state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1061 : // kMol/s = (J/s) /(KJ/mol * 1000 J/KJ * 1000 mol/kmol)
1062 :
1063 0 : bool ConstrainedIncreasingNdot(false);
1064 0 : bool ConstrainedDecreasingNdot(false);
1065 0 : Real64 MdotFuelAllowed = 0.0;
1066 :
1067 0 : GeneratorDynamicsManager::ManageGeneratorFuelFlow(
1068 : state, this->DynamicsControlID, MdotFuel, MdotFuelAllowed, ConstrainedIncreasingNdot, ConstrainedDecreasingNdot);
1069 :
1070 0 : if (ConstrainedIncreasingNdot || ConstrainedDecreasingNdot) { // recalculate Pnetss with new NdotFuel with iteration
1071 0 : MdotFuel = MdotFuelAllowed;
1072 0 : NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1073 0 : Qgross = NdotFuel * (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
1074 :
1075 0 : for (int i = 1; i <= 20; ++i) { // iterating here, could add use of search method error signal
1076 0 : Pnetss = Qgross * ElecEff;
1077 0 : if (this->A42Model.InternalFlowControl) {
1078 0 : MdotCW = GeneratorDynamicsManager::FuncDetermineCWMdotForInternalFlowControl(state, this->DynamicsControlID, Pnetss, TcwIn);
1079 : }
1080 0 : ElecEff = this->A42Model.ElecEffCurve->value(state, Pnetss, MdotCW, TcwIn);
1081 0 : ElecEff = max(0.0, ElecEff); // protect against bad curve result
1082 : }
1083 :
1084 0 : ThermEff = this->A42Model.ThermalEffCurve->value(state, Pnetss, MdotCW, TcwIn);
1085 0 : ThermEff = max(0.0, ThermEff); // protect against bad curve result
1086 0 : Qgenss = ThermEff * Qgross; // W
1087 : }
1088 :
1089 0 : NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1090 0 : MdotAir = this->A42Model.AirFlowCurve->value(state, MdotFuel);
1091 0 : MdotAir = max(0.0, MdotAir); // protect against bad curve result
1092 0 : if (PLRforSubtimestepStartUp < 1.0) {
1093 0 : Pnetss = AllowedLoad;
1094 : }
1095 0 : } break;
1096 0 : case DataGenerators::OperatingMode::CoolDown: {
1097 0 : Pnetss = 0.0;
1098 0 : Pstandby = 0.0;
1099 0 : Pcooler = this->A42Model.PcoolDown;
1100 0 : TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
1101 0 : MdotCW = state.dataLoopNodes->Node(this->PlantInletNodeID).MassFlowRate; // kg/s
1102 0 : if (this->A42Model.InternalFlowControl) {
1103 0 : MdotCW = GeneratorDynamicsManager::FuncDetermineCWMdotForInternalFlowControl(state, this->DynamicsControlID, Pnetss, TcwIn);
1104 : }
1105 0 : NdotFuel = 0.0;
1106 0 : MdotFuel = 0.0;
1107 0 : MdotAir = 0.0;
1108 0 : ElecEff = 0.0;
1109 0 : ThermEff = 0.0;
1110 0 : Qgross = 0.0;
1111 0 : Qgenss = 0.0;
1112 0 : } break;
1113 0 : default:
1114 0 : break;
1115 : }
1116 :
1117 0 : for (int i = 1; i <= 20; ++i) { // sequential search with exit criteria
1118 : // calculate new value for engine temperature
1119 : // for Stirling in warmup, need to include dependency of Qgness on Teng
1120 0 : if ((this->A42Model.WarmUpByEngineTemp) && (CurrentOpMode == DataGenerators::OperatingMode::WarmUp)) {
1121 :
1122 0 : Real64 Pmax = this->A42Model.MaxElecPower;
1123 0 : TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
1124 0 : MdotCW = state.dataLoopNodes->Node(this->PlantInletNodeID).MassFlowRate; // kg/s
1125 0 : ElecEff = this->A42Model.ElecEffCurve->value(state, Pmax, MdotCW, TcwIn);
1126 0 : ElecEff = max(0.0, ElecEff); // protect against bad curve result
1127 0 : if (ElecEff > 0.0) { // trap divide by bad thing
1128 0 : Qgross = Pmax / ElecEff; // W
1129 : } else {
1130 0 : Qgross = 0.0;
1131 : }
1132 0 : NdotFuel = Qgross / (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
1133 : // kMol/s = (J/s) /(KJ/mol * 1000 J/KJ * 1000 mol/kmol)
1134 0 : Real64 MdotFuelMax = NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1135 :
1136 : Real64 MdotFuelWarmup;
1137 0 : if (Teng > thisAmbientTemp) {
1138 0 : MdotFuelWarmup =
1139 0 : MdotFuelMax + this->A42Model.kf * MdotFuelMax * ((this->A42Model.TnomEngOp - thisAmbientTemp) / (Teng - thisAmbientTemp));
1140 :
1141 : // check that numerical answer didn't blow up beyond limit, and reset if it did
1142 0 : if (MdotFuelWarmup > this->A42Model.Rfuelwarmup * MdotFuelMax) {
1143 0 : MdotFuelWarmup = this->A42Model.Rfuelwarmup * MdotFuelMax;
1144 : }
1145 0 : if (this->A42Model.TnomEngOp > thisAmbientTemp) {
1146 0 : Pnetss = Pmax * this->A42Model.kp * ((Teng - thisAmbientTemp) / (this->A42Model.TnomEngOp - thisAmbientTemp));
1147 : } else { // equal would divide by zero
1148 0 : Pnetss = Pmax;
1149 : }
1150 : } else { // equal would divide by zero
1151 0 : MdotFuelWarmup = this->A42Model.Rfuelwarmup * MdotFuelMax;
1152 : }
1153 0 : MdotFuel = MdotFuelWarmup;
1154 0 : NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1155 0 : MdotAir = this->A42Model.AirFlowCurve->value(state, MdotFuelWarmup);
1156 0 : MdotAir = max(0.0, MdotAir); // protect against bad curve result
1157 0 : Qgross = NdotFuel * (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
1158 0 : ThermEff = this->A42Model.ThermalEffCurve->value(state, Pmax, MdotCW, TcwIn);
1159 0 : ThermEff = max(0.0, ThermEff); // protect against bad curve result
1160 0 : Qgenss = ThermEff * Qgross; // W
1161 : }
1162 :
1163 0 : Real64 dt = state.dataHVACGlobal->TimeStepSysSec;
1164 :
1165 0 : Teng = FuncDetermineEngineTemp(
1166 : TcwOut, this->A42Model.MCeng, this->A42Model.UAhx, this->A42Model.UAskin, thisAmbientTemp, Qgenss, this->A42Model.TengLast, dt);
1167 :
1168 0 : Real64 Cp = state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).glycol->getSpecificHeat(state, TcwIn, RoutineName);
1169 :
1170 : TcwOut =
1171 0 : FuncDetermineCoolantWaterExitTemp(TcwIn, this->A42Model.MCcw, this->A42Model.UAhx, MdotCW * Cp, Teng, this->A42Model.TempCWOutLast, dt);
1172 :
1173 : // form balance and exit once met.
1174 0 : bool EnergyBalOK = CheckMicroCHPThermalBalance(this->A42Model.MaxElecPower,
1175 : TcwIn,
1176 : TcwOut,
1177 : Teng,
1178 : thisAmbientTemp,
1179 : this->A42Model.UAhx,
1180 : this->A42Model.UAskin,
1181 : Qgenss,
1182 : this->A42Model.MCeng,
1183 : this->A42Model.MCcw,
1184 : MdotCW * Cp);
1185 :
1186 0 : if (EnergyBalOK && (i > 4)) break;
1187 : }
1188 :
1189 0 : this->PlantMassFlowRate = MdotCW;
1190 0 : this->A42Model.Pnet = Pnetss - Pcooler - Pstandby;
1191 0 : this->A42Model.ElecEff = ElecEff;
1192 0 : this->A42Model.Qgross = Qgross;
1193 0 : this->A42Model.ThermEff = ThermEff;
1194 0 : this->A42Model.Qgenss = Qgenss;
1195 0 : this->A42Model.NdotFuel = NdotFuel;
1196 0 : this->A42Model.MdotFuel = MdotFuel;
1197 0 : this->A42Model.Teng = Teng;
1198 0 : this->A42Model.TcwOut = TcwOut;
1199 0 : this->A42Model.TcwIn = TcwIn;
1200 0 : this->A42Model.MdotAir = MdotAir;
1201 0 : this->A42Model.QdotSkin = this->A42Model.UAskin * (Teng - thisAmbientTemp);
1202 :
1203 0 : this->A42Model.OpMode = CurrentOpMode;
1204 0 : }
1205 :
1206 0 : Real64 FuncDetermineEngineTemp(Real64 const TcwOut, // hot water leaving temp
1207 : Real64 const MCeng, // Fictitious mass and heat capacity of engine
1208 : Real64 const UAHX, // Heat exchanger UA
1209 : Real64 const UAskin, // Skin losses UA
1210 : Real64 const Troom, // surrounding zone temperature C
1211 : Real64 const Qgenss, // steady state generator heat generation
1212 : Real64 const TengLast, // engine temp at previous time step
1213 : Real64 const time // elapsed time since previous evaluation
1214 : )
1215 : {
1216 :
1217 : // FUNCTION INFORMATION:
1218 : // AUTHOR B. Griffith
1219 : // DATE WRITTEN Feb. 2007
1220 : // MODIFIED na
1221 : // RE-ENGINEERED na
1222 :
1223 : // PURPOSE OF THIS FUNCTION:
1224 : // Calculate engine temperature,
1225 :
1226 : // METHODOLOGY EMPLOYED:
1227 : // model is dynamic in that previous condition affects current timestep
1228 : // solve ode for engine temp using analytical solution
1229 :
1230 0 : Real64 a = ((UAHX * TcwOut / MCeng) + (UAskin * Troom / MCeng) + (Qgenss / MCeng));
1231 0 : Real64 b = ((-1.0 * UAHX / MCeng) + (-1.0 * UAskin / MCeng));
1232 :
1233 0 : return (TengLast + a / b) * std::exp(b * time) - a / b;
1234 : }
1235 :
1236 0 : Real64 FuncDetermineCoolantWaterExitTemp(Real64 const TcwIn, // hot water inlet temp
1237 : Real64 const MCcw, // Fictitious mass and heat capacity of coolant hx
1238 : Real64 const UAHX, // Heat exchanger UA
1239 : Real64 const MdotCpcw, // mass flow and specific heat of coolant water
1240 : Real64 const Teng, // engine mass temperature C
1241 : Real64 const TcwoutLast, // coolant water leaving temp at previous time step
1242 : Real64 const time // elapsed time since previous evaluation
1243 : )
1244 : {
1245 :
1246 : // FUNCTION INFORMATION:
1247 : // AUTHOR B. Griffith
1248 : // DATE WRITTEN Feb. 2007
1249 : // MODIFIED na
1250 : // RE-ENGINEERED na
1251 :
1252 : // PURPOSE OF THIS FUNCTION:
1253 : // Calculate coolant water leaving temperature,
1254 :
1255 : // METHODOLOGY EMPLOYED:
1256 : // model is dynamic in that previous condition affects current timestep
1257 : // solve ode for coolant water outlet temp using analytical solution
1258 :
1259 0 : Real64 a = (MdotCpcw * TcwIn / MCcw) + (UAHX * Teng / MCcw);
1260 0 : Real64 b = ((-1.0 * MdotCpcw / MCcw) + (-1.0 * UAHX / MCcw));
1261 :
1262 0 : if (b * time < (-1.0 * Constant::MaxEXPArg)) {
1263 0 : return -a / b;
1264 : } else {
1265 0 : return (TcwoutLast + a / b) * std::exp(b * time) - a / b;
1266 : }
1267 : }
1268 :
1269 0 : bool CheckMicroCHPThermalBalance(Real64 const NomHeatGen, // nominal heat generation rate for scaling
1270 : Real64 const TcwIn, // hot water inlet temp
1271 : Real64 const TcwOut, // hot water leaving temp
1272 : Real64 const Teng, // engine mass temperature C
1273 : Real64 const Troom, // surrounding zone temperature C
1274 : Real64 const UAHX, // Heat exchanger UA
1275 : Real64 const UAskin, // Skin losses UA
1276 : Real64 const Qgenss, // steady state generator heat generation
1277 : Real64 const MCeng, // Fictitious mass and heat capacity of engine
1278 : Real64 const MCcw, // Fictitious mass and heat capacity of coolant hx
1279 : Real64 const MdotCpcw // mass flow and specific heat of coolant water
1280 : )
1281 : {
1282 :
1283 : // FUNCTION INFORMATION:
1284 : // AUTHOR B. Griffith
1285 : // DATE WRITTEN Feb. 2007
1286 : // MODIFIED na
1287 : // RE-ENGINEERED na
1288 :
1289 : // PURPOSE OF THIS FUNCTION:
1290 : // Check for energy balance to test if can exit iteration loop
1291 :
1292 : // METHODOLOGY EMPLOYED:
1293 : // put all terms of dynamic energy balances on RHS and compute magnitude of imbalance
1294 : // compare imbalance to scalable thresholds and make a boolean conclusion.
1295 :
1296 : // first compute derivatives using a + bT
1297 : // derivative of engine temp wrt time
1298 0 : Real64 a = ((UAHX * TcwOut / MCeng) + (UAskin * Troom / MCeng) + (Qgenss / MCeng));
1299 0 : Real64 b = ((-1.0 * UAHX / MCeng) + (-1.0 * UAskin / MCeng));
1300 0 : Real64 DTengDTime = a + b * Teng;
1301 :
1302 : // derivative of coolant exit temp wrt time
1303 0 : Real64 c = (MdotCpcw * TcwIn / MCcw) + (UAHX * Teng / MCcw);
1304 0 : Real64 d = ((-1.0 * MdotCpcw / MCcw) + (-1.0 * UAHX / MCcw));
1305 0 : Real64 DCoolOutTDtime = c + d * TcwOut;
1306 :
1307 : // energy imbalance for engine control volume
1308 0 : Real64 magImbalEng = UAHX * (TcwOut - Teng) + UAskin * (Troom - Teng) + Qgenss - MCeng * DTengDTime;
1309 :
1310 : // energy imbalance for coolant control volume
1311 0 : Real64 magImbalCooling = MdotCpcw * (TcwIn - TcwOut) + UAHX * (Teng - TcwOut) - MCcw * DCoolOutTDtime;
1312 :
1313 : // criteria for when to call energy balance okay
1314 0 : Real64 threshold = NomHeatGen / 10000000.0;
1315 :
1316 0 : return (threshold > magImbalEng) && (threshold > magImbalCooling);
1317 : }
1318 :
1319 249958 : void FigureMicroCHPZoneGains(EnergyPlusData &state)
1320 : {
1321 :
1322 : // SUBROUTINE INFORMATION:
1323 : // AUTHOR B. Griffith
1324 : // DATE WRITTEN July 2006
1325 : // MODIFIED na
1326 : // RE-ENGINEERED na
1327 :
1328 : // PURPOSE OF THIS SUBROUTINE:
1329 : // Couple equipment skin losses to the Zone Heat Balance
1330 :
1331 : // METHODOLOGY EMPLOYED:
1332 : // This routine adds up the various skin losses and then
1333 : // sets the values in the ZoneIntGain structure
1334 :
1335 249958 : if (state.dataCHPElectGen->NumMicroCHPs == 0) return;
1336 :
1337 0 : if (state.dataGlobal->BeginEnvrnFlag && state.dataCHPElectGen->MyEnvrnFlag) {
1338 0 : for (auto &e : state.dataGenerator->FuelSupply)
1339 0 : e.QskinLoss = 0.0;
1340 0 : for (auto &e : state.dataCHPElectGen->MicroCHP) {
1341 0 : e.A42Model.QdotSkin = 0.0;
1342 0 : e.A42Model.SkinLossConvect = 0.0;
1343 0 : e.A42Model.SkinLossRadiat = 0.0;
1344 : }
1345 0 : state.dataCHPElectGen->MyEnvrnFlag = false;
1346 : }
1347 :
1348 0 : if (!state.dataGlobal->BeginEnvrnFlag) state.dataCHPElectGen->MyEnvrnFlag = true;
1349 :
1350 0 : for (int CHPnum = 1; CHPnum <= state.dataCHPElectGen->NumMicroCHPs; ++CHPnum) {
1351 0 : Real64 TotalZoneHeatGain = state.dataGenerator->FuelSupply(state.dataCHPElectGen->MicroCHP(CHPnum).FuelSupplyID).QskinLoss +
1352 0 : state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.QdotSkin;
1353 :
1354 0 : state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.QdotConvZone =
1355 0 : TotalZoneHeatGain * (1 - state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.RadiativeFraction);
1356 0 : state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.SkinLossConvect = state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.QdotConvZone;
1357 0 : state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.QdotRadZone =
1358 0 : TotalZoneHeatGain * state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.RadiativeFraction;
1359 0 : state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.SkinLossRadiat = state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.QdotRadZone;
1360 : }
1361 : }
1362 :
1363 0 : void MicroCHPDataStruct::CalcUpdateHeatRecovery(EnergyPlusData &state) const
1364 : {
1365 :
1366 : // SUBROUTINE INFORMATION:
1367 : // AUTHOR B Griffith
1368 : // DATE WRITTEN Aug 2006
1369 : // MODIFIED na
1370 : // RE-ENGINEERED na
1371 :
1372 : // PURPOSE OF THIS SUBROUTINE:
1373 : // update plant loop interactions, do any calcs needed
1374 :
1375 : static constexpr std::string_view RoutineName("CalcUpdateHeatRecovery");
1376 :
1377 0 : PlantUtilities::SafeCopyPlantNode(state, this->PlantInletNodeID, this->PlantOutletNodeID);
1378 :
1379 0 : state.dataLoopNodes->Node(this->PlantOutletNodeID).Temp = this->A42Model.TcwOut;
1380 :
1381 0 : Real64 Cp = state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).glycol->getSpecificHeat(state, this->A42Model.TcwIn, RoutineName);
1382 :
1383 0 : state.dataLoopNodes->Node(this->PlantOutletNodeID).Enthalpy = this->A42Model.TcwOut * Cp;
1384 0 : }
1385 :
1386 2 : void MicroCHPDataStruct::getDesignCapacities(
1387 : [[maybe_unused]] EnergyPlusData &state, const EnergyPlus::PlantLocation &, Real64 &MaxLoad, Real64 &MinLoad, Real64 &OptLoad)
1388 : {
1389 2 : MaxLoad = state.dataGenerator->GeneratorDynamics(this->DynamicsControlID).QdotHXMax;
1390 2 : MinLoad = state.dataGenerator->GeneratorDynamics(this->DynamicsControlID).QdotHXMin;
1391 2 : OptLoad = state.dataGenerator->GeneratorDynamics(this->DynamicsControlID).QdotHXOpt;
1392 2 : }
1393 :
1394 0 : void MicroCHPDataStruct::UpdateMicroCHPGeneratorRecords(EnergyPlusData &state) // Generator number
1395 : {
1396 :
1397 : // SUBROUTINE INFORMATION:
1398 : // AUTHOR B. Griffith
1399 : // DATE WRITTEN July 2006
1400 : // MODIFIED na
1401 : // RE-ENGINEERED na
1402 :
1403 : // PURPOSE OF THIS SUBROUTINE:
1404 : // update variables in structures linked to output reports
1405 :
1406 : static constexpr std::string_view RoutineName("UpdateMicroCHPGeneratorRecords");
1407 :
1408 0 : this->A42Model.ACPowerGen = this->A42Model.Pnet; // electrical power produced [W]
1409 0 : this->A42Model.ACEnergyGen = this->A42Model.Pnet * state.dataHVACGlobal->TimeStepSysSec; // energy produced (J)
1410 0 : this->A42Model.QdotHX = this->A42Model.UAhx * (this->A42Model.Teng - this->A42Model.TcwOut); // heat recovered rate (W)
1411 :
1412 0 : Real64 Cp = state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).glycol->getSpecificHeat(state, this->A42Model.TcwIn, RoutineName);
1413 :
1414 0 : this->A42Model.QdotHR = this->PlantMassFlowRate * Cp * (this->A42Model.TcwOut - this->A42Model.TcwIn);
1415 0 : this->A42Model.TotalHeatEnergyRec = this->A42Model.QdotHR * state.dataHVACGlobal->TimeStepSysSec; // heat recovered energy (J)
1416 :
1417 0 : this->A42Model.HeatRecInletTemp = this->A42Model.TcwIn; // Heat Recovery Loop Inlet Temperature (C)
1418 0 : this->A42Model.HeatRecOutletTemp = this->A42Model.TcwOut; // Heat Recovery Loop Outlet Temperature (C)
1419 :
1420 0 : this->A42Model.FuelCompressPower = state.dataGenerator->FuelSupply(this->FuelSupplyID).PfuelCompEl;
1421 : // electrical power used by fuel supply compressor [W]
1422 0 : this->A42Model.FuelCompressEnergy =
1423 0 : state.dataGenerator->FuelSupply(this->FuelSupplyID).PfuelCompEl * state.dataHVACGlobal->TimeStepSys * Constant::rSecsInHour; // elect energy
1424 0 : this->A42Model.FuelCompressSkinLoss = state.dataGenerator->FuelSupply(this->FuelSupplyID).QskinLoss;
1425 : // heat rate of losses.by fuel supply compressor [W]
1426 0 : this->A42Model.FuelEnergyHHV = this->A42Model.NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).HHV *
1427 0 : state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec * state.dataHVACGlobal->TimeStepSys *
1428 : Constant::rSecsInHour;
1429 : // reporting: Fuel Energy used (W)
1430 0 : this->A42Model.FuelEnergyUseRateHHV = this->A42Model.NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).HHV *
1431 0 : state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1432 : // reporting: Fuel Energy used (J)
1433 0 : this->A42Model.FuelEnergyLHV =
1434 0 : this->A42Model.NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000000.0 * state.dataHVACGlobal->TimeStepSysSec;
1435 : // reporting: Fuel Energy used (W)
1436 0 : this->A42Model.FuelEnergyUseRateLHV = this->A42Model.NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000000.0;
1437 :
1438 0 : this->A42Model.SkinLossPower = this->A42Model.QdotConvZone + this->A42Model.QdotRadZone;
1439 0 : this->A42Model.SkinLossEnergy = (this->A42Model.QdotConvZone + this->A42Model.QdotRadZone) * state.dataHVACGlobal->TimeStepSysSec;
1440 0 : this->A42Model.SkinLossConvect = this->A42Model.QdotConvZone;
1441 0 : this->A42Model.SkinLossRadiat = this->A42Model.QdotRadZone;
1442 :
1443 : // update node data for air inlet (and outlet)
1444 0 : if (this->AirInletNodeID > 0) {
1445 0 : state.dataLoopNodes->Node(this->AirInletNodeID).MassFlowRate = this->A42Model.MdotAir;
1446 : }
1447 0 : if (this->AirOutletNodeID > 0) {
1448 0 : state.dataLoopNodes->Node(this->AirOutletNodeID).MassFlowRate = this->A42Model.MdotAir;
1449 0 : state.dataLoopNodes->Node(this->AirOutletNodeID).Temp = this->A42Model.Teng;
1450 : }
1451 0 : }
1452 2 : void MicroCHPDataStruct::oneTimeInit(EnergyPlusData &state)
1453 : {
1454 2 : if (this->myFlag) {
1455 2 : this->setupOutputVars(state);
1456 2 : this->myFlag = false;
1457 : }
1458 :
1459 2 : if (this->MyPlantScanFlag) {
1460 2 : if (allocated(state.dataPlnt->PlantLoop)) {
1461 2 : bool errFlag = false;
1462 4 : PlantUtilities::ScanPlantLoopsForObject(
1463 2 : state, this->Name, DataPlant::PlantEquipmentType::Generator_MicroCHP, this->CWPlantLoc, errFlag, _, _, _, _, _);
1464 :
1465 2 : if (errFlag) {
1466 0 : ShowFatalError(state, "InitMicroCHPNoNormalizeGenerators: Program terminated for previous conditions.");
1467 : }
1468 :
1469 2 : if (!this->A42Model.InternalFlowControl) {
1470 : // IF this is on the supply side and not internal flow control then reset flow priority to lower
1471 0 : if (this->CWPlantLoc.loopSideNum == DataPlant::LoopSideLocation::Supply) {
1472 0 : DataPlant::CompData::getPlantComponent(state, this->CWPlantLoc).FlowPriority = DataPlant::LoopFlowStatus::TakesWhatGets;
1473 : }
1474 : }
1475 :
1476 2 : this->MyPlantScanFlag = false;
1477 : }
1478 : }
1479 2 : }
1480 : } // namespace EnergyPlus::MicroCHPElectricGenerator
|