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 4 : }
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) {
759 2 : return;
760 : }
761 :
762 0 : int DynaCntrlNum = this->DynamicsControlID;
763 :
764 0 : if (state.dataGlobal->BeginEnvrnFlag && this->MyEnvrnFlag) {
765 : // reset to starting condition for different environment runperiods, design days
766 0 : this->A42Model.TengLast = 20.0;
767 0 : this->A42Model.TempCWOutLast = 20.0;
768 0 : this->A42Model.TimeElapsed = 0.0;
769 0 : this->A42Model.OpMode = DataGenerators::OperatingMode::Invalid;
770 0 : this->A42Model.OffModeTime = 0.0;
771 0 : this->A42Model.StandyByModeTime = 0.0;
772 0 : this->A42Model.WarmUpModeTime = 0.0;
773 0 : this->A42Model.NormalModeTime = 0.0;
774 0 : this->A42Model.CoolDownModeTime = 0.0;
775 0 : this->A42Model.Pnet = 0.0;
776 0 : this->A42Model.ElecEff = 0.0;
777 0 : this->A42Model.Qgross = 0.0;
778 0 : this->A42Model.ThermEff = 0.0;
779 0 : this->A42Model.Qgenss = 0.0;
780 0 : this->A42Model.NdotFuel = 0.0;
781 0 : this->A42Model.MdotFuel = 0.0;
782 0 : this->A42Model.Teng = 20.0;
783 0 : this->A42Model.TcwIn = 20.0;
784 0 : this->A42Model.TcwOut = 20.0;
785 0 : this->A42Model.MdotAir = 0.0;
786 0 : this->A42Model.QdotSkin = 0.0;
787 0 : this->A42Model.QdotConvZone = 0.0;
788 0 : this->A42Model.QdotRadZone = 0.0;
789 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).LastOpMode = DataGenerators::OperatingMode::Off;
790 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).CurrentOpMode = DataGenerators::OperatingMode::Off;
791 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).FractionalDayofLastShutDown = 0.0;
792 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).FractionalDayofLastStartUp = 0.0;
793 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).HasBeenOn = false;
794 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).DuringStartUp = false;
795 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).DuringShutDown = false;
796 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).FuelMdotLastTimestep = 0.0;
797 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).PelLastTimeStep = 0.0;
798 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).NumCycles = 0;
799 :
800 0 : state.dataGenerator->FuelSupply(this->FuelSupplyID).QskinLoss = 0.0;
801 :
802 0 : PlantUtilities::InitComponentNodes(state, 0.0, this->PlantMassFlowRateMax, this->PlantInletNodeID, this->PlantOutletNodeID);
803 : }
804 :
805 0 : if (!state.dataGlobal->BeginEnvrnFlag) {
806 0 : this->MyEnvrnFlag = true;
807 : }
808 :
809 : Real64 TimeElapsed =
810 0 : state.dataGlobal->HourOfDay + state.dataGlobal->TimeStep * state.dataGlobal->TimeStepZone + state.dataHVACGlobal->SysTimeElapsed;
811 0 : if (this->A42Model.TimeElapsed != TimeElapsed) {
812 : // The simulation has advanced to the next system timestep. Save conditions from the end of the previous system
813 : // timestep for use as the initial conditions of each iteration that does not advance the system timestep.
814 0 : this->A42Model.TengLast = this->A42Model.Teng;
815 0 : this->A42Model.TempCWOutLast = this->A42Model.TcwOut;
816 0 : this->A42Model.TimeElapsed = TimeElapsed;
817 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).LastOpMode = state.dataGenerator->GeneratorDynamics(DynaCntrlNum).CurrentOpMode;
818 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).FuelMdotLastTimestep = this->A42Model.MdotFuel;
819 0 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).PelLastTimeStep = this->A42Model.Pnet;
820 : }
821 :
822 0 : if (!this->A42Model.InternalFlowControl) {
823 :
824 0 : Real64 mdot = this->PlantMassFlowRateMax;
825 0 : PlantUtilities::SetComponentFlowRate(state, mdot, this->PlantInletNodeID, this->PlantOutletNodeID, this->CWPlantLoc);
826 0 : this->PlantMassFlowRate = mdot;
827 : }
828 : }
829 :
830 0 : void MicroCHPDataStruct::CalcMicroCHPNoNormalizeGeneratorModel(EnergyPlusData &state,
831 : bool const RunFlagElectCenter, // TRUE when Generator operating
832 : bool const RunFlagPlant,
833 : Real64 const MyElectricLoad, // Generator demand
834 : Real64 const MyThermalLoad)
835 : {
836 :
837 : // SUBROUTINE INFORMATION:
838 : // AUTHOR B Griffith
839 : // DATE WRITTEN July 2006
840 : // MODIFIED na
841 : // RE-ENGINEERED na
842 :
843 : // PURPOSE OF THIS SUBROUTINE:
844 : // Main calculation subroutine for the IEA Annex 42 model
845 :
846 : // METHODOLOGY EMPLOYED:
847 : // curve fit, dynamic control limits,
848 :
849 : // REFERENCES:
850 : // IEA Annex 42 FC-COGEN-SIM "A Generic Model Specification for Combustion-based Residential CHP Devices"
851 : // Alex Ferguson, Nick Kelly, Version 3, June 26, 2006
852 :
853 : static constexpr std::string_view RoutineName("CalcMicroCHPNoNormalizeGeneratorModel");
854 :
855 0 : DataGenerators::OperatingMode CurrentOpMode = DataGenerators::OperatingMode::Invalid;
856 : Real64 NdotFuel;
857 0 : Real64 AllowedLoad = 0.0;
858 0 : Real64 PLRforSubtimestepStartUp(1.0);
859 0 : Real64 PLRforSubtimestepShutDown(0.0);
860 :
861 0 : GeneratorDynamicsManager::ManageGeneratorControlState(state,
862 : this->DynamicsControlID,
863 : RunFlagElectCenter,
864 : RunFlagPlant,
865 : MyElectricLoad,
866 : MyThermalLoad,
867 : AllowedLoad,
868 : CurrentOpMode,
869 : PLRforSubtimestepStartUp,
870 : PLRforSubtimestepShutDown);
871 :
872 0 : Real64 Teng = this->A42Model.Teng;
873 0 : Real64 TcwOut = this->A42Model.TcwOut;
874 :
875 : Real64 thisAmbientTemp;
876 0 : if (this->ZoneID > 0) {
877 0 : thisAmbientTemp = state.dataZoneTempPredictorCorrector->zoneHeatBalance(this->ZoneID).MAT;
878 : } else { // outdoor location, no zone
879 0 : thisAmbientTemp = state.dataEnvrn->OutDryBulbTemp;
880 : }
881 :
882 0 : Real64 Pnetss = 0.0;
883 0 : Real64 Pstandby = 0.0; // power draw during standby, positive here means negative production
884 0 : Real64 Pcooler = 0.0; // power draw during cool down, positive here means negative production
885 0 : Real64 ElecEff = 0.0;
886 0 : Real64 MdotAir = 0.0;
887 0 : Real64 Qgenss = 0.0;
888 0 : Real64 MdotCW = 0.0;
889 0 : Real64 TcwIn = 0.0;
890 0 : Real64 MdotFuel = 0.0;
891 0 : Real64 Qgross = 0.0;
892 0 : Real64 ThermEff = 0.0;
893 :
894 0 : switch (CurrentOpMode) {
895 0 : case DataGenerators::OperatingMode::Off: { // same as standby in model spec but no Pnet standby electricity losses.
896 :
897 0 : Qgenss = 0.0;
898 0 : TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
899 0 : Pnetss = 0.0;
900 0 : Pstandby = 0.0;
901 0 : Pcooler = this->A42Model.PcoolDown * PLRforSubtimestepShutDown;
902 0 : ElecEff = 0.0;
903 0 : ThermEff = 0.0;
904 0 : Qgross = 0.0;
905 0 : NdotFuel = 0.0;
906 0 : MdotFuel = 0.0;
907 0 : MdotAir = 0.0;
908 :
909 0 : MdotCW = 0.0;
910 0 : PlantUtilities::SetComponentFlowRate(state, MdotCW, this->PlantInletNodeID, this->PlantOutletNodeID, this->CWPlantLoc);
911 0 : this->PlantMassFlowRate = MdotCW;
912 0 : } break;
913 0 : case DataGenerators::OperatingMode::Standby: {
914 0 : Qgenss = 0.0;
915 0 : TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
916 0 : Pnetss = 0.0;
917 0 : Pstandby = this->A42Model.Pstandby * (1.0 - PLRforSubtimestepShutDown);
918 0 : Pcooler = this->A42Model.PcoolDown * PLRforSubtimestepShutDown;
919 0 : ElecEff = 0.0;
920 0 : ThermEff = 0.0;
921 0 : Qgross = 0.0;
922 0 : NdotFuel = 0.0;
923 0 : MdotFuel = 0.0;
924 0 : MdotAir = 0.0;
925 :
926 0 : MdotCW = 0.0;
927 0 : PlantUtilities::SetComponentFlowRate(state, MdotCW, this->PlantInletNodeID, this->PlantOutletNodeID, this->CWPlantLoc);
928 0 : this->PlantMassFlowRate = MdotCW;
929 0 : } break;
930 0 : case DataGenerators::OperatingMode::WarmUp: {
931 0 : if (this->A42Model.WarmUpByTimeDelay) {
932 : // Internal combustion engine. This is just like normal operation but no net power yet.
933 0 : Pnetss = MyElectricLoad; // W
934 0 : Pstandby = 0.0;
935 0 : Pcooler = this->A42Model.PcoolDown * PLRforSubtimestepShutDown;
936 0 : TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
937 0 : MdotCW = state.dataLoopNodes->Node(this->PlantInletNodeID).MassFlowRate; // kg/s
938 0 : if (this->A42Model.InternalFlowControl) {
939 0 : MdotCW = GeneratorDynamicsManager::FuncDetermineCWMdotForInternalFlowControl(state, this->DynamicsControlID, Pnetss, TcwIn);
940 : }
941 0 : ElecEff = this->A42Model.ElecEffCurve->value(state, Pnetss, MdotCW, TcwIn);
942 0 : ElecEff = max(0.0, ElecEff); // protect against bad curve result
943 :
944 0 : if (ElecEff > 0.0) { // trap divide by bad thing
945 0 : Qgross = Pnetss / ElecEff; // W
946 : } else {
947 0 : Qgross = 0.0;
948 : }
949 0 : ThermEff = this->A42Model.ThermalEffCurve->value(state, Pnetss, MdotCW, TcwIn);
950 0 : ThermEff = max(0.0, ThermEff); // protect against bad curve result
951 :
952 0 : Qgenss = ThermEff * Qgross; // W
953 :
954 0 : MdotFuel = Qgross / (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0) *
955 0 : state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
956 : // kMol/s = (J/s) /(KJ/mol * 1000 J/KJ * 1000 mol/kmol)
957 :
958 0 : bool ConstrainedIncreasingNdot(false);
959 0 : bool ConstrainedDecreasingNdot(false);
960 0 : Real64 MdotFuelAllowed = 0.0;
961 :
962 0 : GeneratorDynamicsManager::ManageGeneratorFuelFlow(
963 : state, this->DynamicsControlID, MdotFuel, MdotFuelAllowed, ConstrainedIncreasingNdot, ConstrainedDecreasingNdot);
964 :
965 0 : if (ConstrainedIncreasingNdot || ConstrainedDecreasingNdot) { // recalculate Pnetss with new NdotFuel with iteration
966 0 : MdotFuel = MdotFuelAllowed;
967 0 : NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
968 0 : Qgross = NdotFuel * (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
969 :
970 0 : for (int i = 1; i <= 20; ++i) { // iterating here could add use of search method
971 0 : Pnetss = Qgross * ElecEff;
972 0 : if (this->A42Model.InternalFlowControl) {
973 0 : MdotCW = GeneratorDynamicsManager::FuncDetermineCWMdotForInternalFlowControl(state, this->DynamicsControlID, Pnetss, TcwIn);
974 : }
975 0 : ElecEff = this->A42Model.ElecEffCurve->value(state, Pnetss, MdotCW, TcwIn);
976 0 : ElecEff = max(0.0, ElecEff); // protect against bad curve result
977 : }
978 :
979 0 : ThermEff = this->A42Model.ThermalEffCurve->value(state, Pnetss, MdotCW, TcwIn);
980 0 : ThermEff = max(0.0, ThermEff); // protect against bad curve result
981 0 : Qgenss = ThermEff * Qgross; // W
982 : }
983 0 : Pnetss = 0.0; // no actually power produced here.
984 0 : NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
985 0 : MdotAir = this->A42Model.AirFlowCurve->value(state, MdotFuel);
986 0 : MdotAir = max(0.0, MdotAir); // protect against bad curve result
987 :
988 0 : } else if (this->A42Model.WarmUpByEngineTemp) {
989 : // Stirling engine mode warm up
990 : // find MdotFuelMax
991 0 : Real64 Pmax = this->A42Model.MaxElecPower;
992 0 : Pstandby = 0.0;
993 0 : Pcooler = this->A42Model.PcoolDown * PLRforSubtimestepShutDown; // could be here with part load in cool down
994 0 : TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
995 0 : MdotCW = state.dataLoopNodes->Node(this->PlantInletNodeID).MassFlowRate; // kg/s
996 0 : ElecEff = this->A42Model.ElecEffCurve->value(state, Pmax, MdotCW, TcwIn);
997 0 : ElecEff = max(0.0, ElecEff); // protect against bad curve result
998 0 : if (ElecEff > 0.0) { // trap divide by bad thing
999 0 : Qgross = Pmax / ElecEff; // W
1000 : } else {
1001 0 : Qgross = 0.0;
1002 : }
1003 0 : NdotFuel = Qgross / (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
1004 : // kMol/s = (J/s) /(KJ/mol * 1000 J/KJ * 1000 mol/kmol)
1005 0 : Real64 MdotFuelMax = NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1006 :
1007 : Real64 MdotFuelWarmup;
1008 0 : if (Teng > thisAmbientTemp) {
1009 0 : MdotFuelWarmup =
1010 0 : MdotFuelMax + this->A42Model.kf * MdotFuelMax * ((this->A42Model.TnomEngOp - thisAmbientTemp) / (Teng - thisAmbientTemp));
1011 : // check that numerical answer didn't blow up beyond limit, and reset if it did
1012 0 : if (MdotFuelWarmup > this->A42Model.Rfuelwarmup * MdotFuelMax) {
1013 0 : MdotFuelWarmup = this->A42Model.Rfuelwarmup * MdotFuelMax;
1014 : }
1015 : } else { // equal would divide by zero
1016 0 : MdotFuelWarmup = this->A42Model.Rfuelwarmup * MdotFuelMax;
1017 : }
1018 :
1019 0 : if (this->A42Model.TnomEngOp > thisAmbientTemp) {
1020 0 : Pnetss = Pmax * this->A42Model.kp * ((Teng - thisAmbientTemp) / (this->A42Model.TnomEngOp - thisAmbientTemp));
1021 : } else { // equal would divide by zero
1022 0 : Pnetss = Pmax;
1023 : }
1024 :
1025 0 : MdotFuel = MdotFuelWarmup;
1026 0 : NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1027 0 : MdotAir = this->A42Model.AirFlowCurve->value(state, MdotFuelWarmup);
1028 0 : MdotAir = max(0.0, MdotAir); // protect against bad curve result
1029 0 : Qgross = NdotFuel * (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
1030 0 : ThermEff = this->A42Model.ThermalEffCurve->value(state, Pmax, MdotCW, TcwIn);
1031 0 : Qgenss = ThermEff * Qgross; // W
1032 : }
1033 0 : } break;
1034 0 : case DataGenerators::OperatingMode::Normal: {
1035 0 : if (PLRforSubtimestepStartUp < 1.0) {
1036 0 : if (RunFlagElectCenter) {
1037 0 : Pnetss = MyElectricLoad; // W
1038 : }
1039 0 : if (RunFlagPlant) {
1040 0 : Pnetss = AllowedLoad;
1041 : }
1042 : } else {
1043 0 : Pnetss = AllowedLoad;
1044 : }
1045 0 : Pstandby = 0.0;
1046 0 : Pcooler = 0.0;
1047 0 : TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
1048 0 : MdotCW = state.dataLoopNodes->Node(this->PlantInletNodeID).MassFlowRate; // kg/s
1049 0 : if (this->A42Model.InternalFlowControl) {
1050 0 : MdotCW = GeneratorDynamicsManager::FuncDetermineCWMdotForInternalFlowControl(state, this->DynamicsControlID, Pnetss, TcwIn);
1051 : }
1052 :
1053 0 : ElecEff = this->A42Model.ElecEffCurve->value(state, Pnetss, MdotCW, TcwIn);
1054 0 : ElecEff = max(0.0, ElecEff); // protect against bad curve result
1055 :
1056 0 : if (ElecEff > 0.0) { // trap divide by bad thing
1057 0 : Qgross = Pnetss / ElecEff; // W
1058 : } else {
1059 0 : Qgross = 0.0;
1060 : }
1061 :
1062 0 : ThermEff = this->A42Model.ThermalEffCurve->value(state, Pnetss, MdotCW, TcwIn);
1063 0 : ThermEff = max(0.0, ThermEff); // protect against bad curve result
1064 0 : Qgenss = ThermEff * Qgross; // W
1065 0 : MdotFuel = Qgross / (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0) *
1066 0 : state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1067 : // kMol/s = (J/s) /(KJ/mol * 1000 J/KJ * 1000 mol/kmol)
1068 :
1069 0 : bool ConstrainedIncreasingNdot(false);
1070 0 : bool ConstrainedDecreasingNdot(false);
1071 0 : Real64 MdotFuelAllowed = 0.0;
1072 :
1073 0 : GeneratorDynamicsManager::ManageGeneratorFuelFlow(
1074 : state, this->DynamicsControlID, MdotFuel, MdotFuelAllowed, ConstrainedIncreasingNdot, ConstrainedDecreasingNdot);
1075 :
1076 0 : if (ConstrainedIncreasingNdot || ConstrainedDecreasingNdot) { // recalculate Pnetss with new NdotFuel with iteration
1077 0 : MdotFuel = MdotFuelAllowed;
1078 0 : NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1079 0 : Qgross = NdotFuel * (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
1080 :
1081 0 : for (int i = 1; i <= 20; ++i) { // iterating here, could add use of search method error signal
1082 0 : Pnetss = Qgross * ElecEff;
1083 0 : if (this->A42Model.InternalFlowControl) {
1084 0 : MdotCW = GeneratorDynamicsManager::FuncDetermineCWMdotForInternalFlowControl(state, this->DynamicsControlID, Pnetss, TcwIn);
1085 : }
1086 0 : ElecEff = this->A42Model.ElecEffCurve->value(state, Pnetss, MdotCW, TcwIn);
1087 0 : ElecEff = max(0.0, ElecEff); // protect against bad curve result
1088 : }
1089 :
1090 0 : ThermEff = this->A42Model.ThermalEffCurve->value(state, Pnetss, MdotCW, TcwIn);
1091 0 : ThermEff = max(0.0, ThermEff); // protect against bad curve result
1092 0 : Qgenss = ThermEff * Qgross; // W
1093 : }
1094 :
1095 0 : NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1096 0 : MdotAir = this->A42Model.AirFlowCurve->value(state, MdotFuel);
1097 0 : MdotAir = max(0.0, MdotAir); // protect against bad curve result
1098 0 : if (PLRforSubtimestepStartUp < 1.0) {
1099 0 : Pnetss = AllowedLoad;
1100 : }
1101 0 : } break;
1102 0 : case DataGenerators::OperatingMode::CoolDown: {
1103 0 : Pnetss = 0.0;
1104 0 : Pstandby = 0.0;
1105 0 : Pcooler = this->A42Model.PcoolDown;
1106 0 : TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
1107 0 : MdotCW = state.dataLoopNodes->Node(this->PlantInletNodeID).MassFlowRate; // kg/s
1108 0 : if (this->A42Model.InternalFlowControl) {
1109 0 : MdotCW = GeneratorDynamicsManager::FuncDetermineCWMdotForInternalFlowControl(state, this->DynamicsControlID, Pnetss, TcwIn);
1110 : }
1111 0 : NdotFuel = 0.0;
1112 0 : MdotFuel = 0.0;
1113 0 : MdotAir = 0.0;
1114 0 : ElecEff = 0.0;
1115 0 : ThermEff = 0.0;
1116 0 : Qgross = 0.0;
1117 0 : Qgenss = 0.0;
1118 0 : } break;
1119 0 : default:
1120 0 : break;
1121 : }
1122 :
1123 0 : for (int i = 1; i <= 20; ++i) { // sequential search with exit criteria
1124 : // calculate new value for engine temperature
1125 : // for Stirling in warmup, need to include dependency of Qgness on Teng
1126 0 : if ((this->A42Model.WarmUpByEngineTemp) && (CurrentOpMode == DataGenerators::OperatingMode::WarmUp)) {
1127 :
1128 0 : Real64 Pmax = this->A42Model.MaxElecPower;
1129 0 : TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
1130 0 : MdotCW = state.dataLoopNodes->Node(this->PlantInletNodeID).MassFlowRate; // kg/s
1131 0 : ElecEff = this->A42Model.ElecEffCurve->value(state, Pmax, MdotCW, TcwIn);
1132 0 : ElecEff = max(0.0, ElecEff); // protect against bad curve result
1133 0 : if (ElecEff > 0.0) { // trap divide by bad thing
1134 0 : Qgross = Pmax / ElecEff; // W
1135 : } else {
1136 0 : Qgross = 0.0;
1137 : }
1138 0 : NdotFuel = Qgross / (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
1139 : // kMol/s = (J/s) /(KJ/mol * 1000 J/KJ * 1000 mol/kmol)
1140 0 : Real64 MdotFuelMax = NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1141 :
1142 : Real64 MdotFuelWarmup;
1143 0 : if (Teng > thisAmbientTemp) {
1144 0 : MdotFuelWarmup =
1145 0 : MdotFuelMax + this->A42Model.kf * MdotFuelMax * ((this->A42Model.TnomEngOp - thisAmbientTemp) / (Teng - thisAmbientTemp));
1146 :
1147 : // check that numerical answer didn't blow up beyond limit, and reset if it did
1148 0 : if (MdotFuelWarmup > this->A42Model.Rfuelwarmup * MdotFuelMax) {
1149 0 : MdotFuelWarmup = this->A42Model.Rfuelwarmup * MdotFuelMax;
1150 : }
1151 0 : if (this->A42Model.TnomEngOp > thisAmbientTemp) {
1152 0 : Pnetss = Pmax * this->A42Model.kp * ((Teng - thisAmbientTemp) / (this->A42Model.TnomEngOp - thisAmbientTemp));
1153 : } else { // equal would divide by zero
1154 0 : Pnetss = Pmax;
1155 : }
1156 : } else { // equal would divide by zero
1157 0 : MdotFuelWarmup = this->A42Model.Rfuelwarmup * MdotFuelMax;
1158 : }
1159 0 : MdotFuel = MdotFuelWarmup;
1160 0 : NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1161 0 : MdotAir = this->A42Model.AirFlowCurve->value(state, MdotFuelWarmup);
1162 0 : MdotAir = max(0.0, MdotAir); // protect against bad curve result
1163 0 : Qgross = NdotFuel * (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
1164 0 : ThermEff = this->A42Model.ThermalEffCurve->value(state, Pmax, MdotCW, TcwIn);
1165 0 : ThermEff = max(0.0, ThermEff); // protect against bad curve result
1166 0 : Qgenss = ThermEff * Qgross; // W
1167 : }
1168 :
1169 0 : Real64 dt = state.dataHVACGlobal->TimeStepSysSec;
1170 :
1171 0 : Teng = FuncDetermineEngineTemp(
1172 : TcwOut, this->A42Model.MCeng, this->A42Model.UAhx, this->A42Model.UAskin, thisAmbientTemp, Qgenss, this->A42Model.TengLast, dt);
1173 :
1174 0 : Real64 Cp = state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).glycol->getSpecificHeat(state, TcwIn, RoutineName);
1175 :
1176 : TcwOut =
1177 0 : FuncDetermineCoolantWaterExitTemp(TcwIn, this->A42Model.MCcw, this->A42Model.UAhx, MdotCW * Cp, Teng, this->A42Model.TempCWOutLast, dt);
1178 :
1179 : // form balance and exit once met.
1180 0 : bool EnergyBalOK = CheckMicroCHPThermalBalance(this->A42Model.MaxElecPower,
1181 : TcwIn,
1182 : TcwOut,
1183 : Teng,
1184 : thisAmbientTemp,
1185 : this->A42Model.UAhx,
1186 : this->A42Model.UAskin,
1187 : Qgenss,
1188 : this->A42Model.MCeng,
1189 : this->A42Model.MCcw,
1190 : MdotCW * Cp);
1191 :
1192 0 : if (EnergyBalOK && (i > 4)) {
1193 0 : break;
1194 : }
1195 : }
1196 :
1197 0 : this->PlantMassFlowRate = MdotCW;
1198 0 : this->A42Model.Pnet = Pnetss - Pcooler - Pstandby;
1199 0 : this->A42Model.ElecEff = ElecEff;
1200 0 : this->A42Model.Qgross = Qgross;
1201 0 : this->A42Model.ThermEff = ThermEff;
1202 0 : this->A42Model.Qgenss = Qgenss;
1203 0 : this->A42Model.NdotFuel = NdotFuel;
1204 0 : this->A42Model.MdotFuel = MdotFuel;
1205 0 : this->A42Model.Teng = Teng;
1206 0 : this->A42Model.TcwOut = TcwOut;
1207 0 : this->A42Model.TcwIn = TcwIn;
1208 0 : this->A42Model.MdotAir = MdotAir;
1209 0 : this->A42Model.QdotSkin = this->A42Model.UAskin * (Teng - thisAmbientTemp);
1210 :
1211 0 : this->A42Model.OpMode = CurrentOpMode;
1212 0 : }
1213 :
1214 0 : Real64 FuncDetermineEngineTemp(Real64 const TcwOut, // hot water leaving temp
1215 : Real64 const MCeng, // Fictitious mass and heat capacity of engine
1216 : Real64 const UAHX, // Heat exchanger UA
1217 : Real64 const UAskin, // Skin losses UA
1218 : Real64 const Troom, // surrounding zone temperature C
1219 : Real64 const Qgenss, // steady state generator heat generation
1220 : Real64 const TengLast, // engine temp at previous time step
1221 : Real64 const time // elapsed time since previous evaluation
1222 : )
1223 : {
1224 :
1225 : // FUNCTION INFORMATION:
1226 : // AUTHOR B. Griffith
1227 : // DATE WRITTEN Feb. 2007
1228 : // MODIFIED na
1229 : // RE-ENGINEERED na
1230 :
1231 : // PURPOSE OF THIS FUNCTION:
1232 : // Calculate engine temperature,
1233 :
1234 : // METHODOLOGY EMPLOYED:
1235 : // model is dynamic in that previous condition affects current timestep
1236 : // solve ode for engine temp using analytical solution
1237 :
1238 0 : Real64 a = ((UAHX * TcwOut / MCeng) + (UAskin * Troom / MCeng) + (Qgenss / MCeng));
1239 0 : Real64 b = ((-1.0 * UAHX / MCeng) + (-1.0 * UAskin / MCeng));
1240 :
1241 0 : return (TengLast + a / b) * std::exp(b * time) - a / b;
1242 : }
1243 :
1244 0 : Real64 FuncDetermineCoolantWaterExitTemp(Real64 const TcwIn, // hot water inlet temp
1245 : Real64 const MCcw, // Fictitious mass and heat capacity of coolant hx
1246 : Real64 const UAHX, // Heat exchanger UA
1247 : Real64 const MdotCpcw, // mass flow and specific heat of coolant water
1248 : Real64 const Teng, // engine mass temperature C
1249 : Real64 const TcwoutLast, // coolant water leaving temp at previous time step
1250 : Real64 const time // elapsed time since previous evaluation
1251 : )
1252 : {
1253 :
1254 : // FUNCTION INFORMATION:
1255 : // AUTHOR B. Griffith
1256 : // DATE WRITTEN Feb. 2007
1257 : // MODIFIED na
1258 : // RE-ENGINEERED na
1259 :
1260 : // PURPOSE OF THIS FUNCTION:
1261 : // Calculate coolant water leaving temperature,
1262 :
1263 : // METHODOLOGY EMPLOYED:
1264 : // model is dynamic in that previous condition affects current timestep
1265 : // solve ode for coolant water outlet temp using analytical solution
1266 :
1267 0 : Real64 a = (MdotCpcw * TcwIn / MCcw) + (UAHX * Teng / MCcw);
1268 0 : Real64 b = ((-1.0 * MdotCpcw / MCcw) + (-1.0 * UAHX / MCcw));
1269 :
1270 0 : if (b * time < (-1.0 * Constant::MaxEXPArg)) {
1271 0 : return -a / b;
1272 : } else {
1273 0 : return (TcwoutLast + a / b) * std::exp(b * time) - a / b;
1274 : }
1275 : }
1276 :
1277 0 : bool CheckMicroCHPThermalBalance(Real64 const NomHeatGen, // nominal heat generation rate for scaling
1278 : Real64 const TcwIn, // hot water inlet temp
1279 : Real64 const TcwOut, // hot water leaving temp
1280 : Real64 const Teng, // engine mass temperature C
1281 : Real64 const Troom, // surrounding zone temperature C
1282 : Real64 const UAHX, // Heat exchanger UA
1283 : Real64 const UAskin, // Skin losses UA
1284 : Real64 const Qgenss, // steady state generator heat generation
1285 : Real64 const MCeng, // Fictitious mass and heat capacity of engine
1286 : Real64 const MCcw, // Fictitious mass and heat capacity of coolant hx
1287 : Real64 const MdotCpcw // mass flow and specific heat of coolant water
1288 : )
1289 : {
1290 :
1291 : // FUNCTION INFORMATION:
1292 : // AUTHOR B. Griffith
1293 : // DATE WRITTEN Feb. 2007
1294 : // MODIFIED na
1295 : // RE-ENGINEERED na
1296 :
1297 : // PURPOSE OF THIS FUNCTION:
1298 : // Check for energy balance to test if can exit iteration loop
1299 :
1300 : // METHODOLOGY EMPLOYED:
1301 : // put all terms of dynamic energy balances on RHS and compute magnitude of imbalance
1302 : // compare imbalance to scalable thresholds and make a boolean conclusion.
1303 :
1304 : // first compute derivatives using a + bT
1305 : // derivative of engine temp wrt time
1306 0 : Real64 a = ((UAHX * TcwOut / MCeng) + (UAskin * Troom / MCeng) + (Qgenss / MCeng));
1307 0 : Real64 b = ((-1.0 * UAHX / MCeng) + (-1.0 * UAskin / MCeng));
1308 0 : Real64 DTengDTime = a + b * Teng;
1309 :
1310 : // derivative of coolant exit temp wrt time
1311 0 : Real64 c = (MdotCpcw * TcwIn / MCcw) + (UAHX * Teng / MCcw);
1312 0 : Real64 d = ((-1.0 * MdotCpcw / MCcw) + (-1.0 * UAHX / MCcw));
1313 0 : Real64 DCoolOutTDtime = c + d * TcwOut;
1314 :
1315 : // energy imbalance for engine control volume
1316 0 : Real64 magImbalEng = UAHX * (TcwOut - Teng) + UAskin * (Troom - Teng) + Qgenss - MCeng * DTengDTime;
1317 :
1318 : // energy imbalance for coolant control volume
1319 0 : Real64 magImbalCooling = MdotCpcw * (TcwIn - TcwOut) + UAHX * (Teng - TcwOut) - MCcw * DCoolOutTDtime;
1320 :
1321 : // criteria for when to call energy balance okay
1322 0 : Real64 threshold = NomHeatGen / 10000000.0;
1323 :
1324 0 : return (threshold > magImbalEng) && (threshold > magImbalCooling);
1325 : }
1326 :
1327 249958 : void FigureMicroCHPZoneGains(EnergyPlusData &state)
1328 : {
1329 :
1330 : // SUBROUTINE INFORMATION:
1331 : // AUTHOR B. Griffith
1332 : // DATE WRITTEN July 2006
1333 : // MODIFIED na
1334 : // RE-ENGINEERED na
1335 :
1336 : // PURPOSE OF THIS SUBROUTINE:
1337 : // Couple equipment skin losses to the Zone Heat Balance
1338 :
1339 : // METHODOLOGY EMPLOYED:
1340 : // This routine adds up the various skin losses and then
1341 : // sets the values in the ZoneIntGain structure
1342 :
1343 249958 : if (state.dataCHPElectGen->NumMicroCHPs == 0) {
1344 249958 : return;
1345 : }
1346 :
1347 0 : if (state.dataGlobal->BeginEnvrnFlag && state.dataCHPElectGen->MyEnvrnFlag) {
1348 0 : for (auto &e : state.dataGenerator->FuelSupply) {
1349 0 : e.QskinLoss = 0.0;
1350 : }
1351 0 : for (auto &e : state.dataCHPElectGen->MicroCHP) {
1352 0 : e.A42Model.QdotSkin = 0.0;
1353 0 : e.A42Model.SkinLossConvect = 0.0;
1354 0 : e.A42Model.SkinLossRadiat = 0.0;
1355 0 : }
1356 0 : state.dataCHPElectGen->MyEnvrnFlag = false;
1357 : }
1358 :
1359 0 : if (!state.dataGlobal->BeginEnvrnFlag) {
1360 0 : state.dataCHPElectGen->MyEnvrnFlag = true;
1361 : }
1362 :
1363 0 : for (int CHPnum = 1; CHPnum <= state.dataCHPElectGen->NumMicroCHPs; ++CHPnum) {
1364 0 : Real64 TotalZoneHeatGain = state.dataGenerator->FuelSupply(state.dataCHPElectGen->MicroCHP(CHPnum).FuelSupplyID).QskinLoss +
1365 0 : state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.QdotSkin;
1366 :
1367 0 : state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.QdotConvZone =
1368 0 : TotalZoneHeatGain * (1 - state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.RadiativeFraction);
1369 0 : state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.SkinLossConvect = state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.QdotConvZone;
1370 0 : state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.QdotRadZone =
1371 0 : TotalZoneHeatGain * state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.RadiativeFraction;
1372 0 : state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.SkinLossRadiat = state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.QdotRadZone;
1373 : }
1374 : }
1375 :
1376 0 : void MicroCHPDataStruct::CalcUpdateHeatRecovery(EnergyPlusData &state) const
1377 : {
1378 :
1379 : // SUBROUTINE INFORMATION:
1380 : // AUTHOR B Griffith
1381 : // DATE WRITTEN Aug 2006
1382 : // MODIFIED na
1383 : // RE-ENGINEERED na
1384 :
1385 : // PURPOSE OF THIS SUBROUTINE:
1386 : // update plant loop interactions, do any calcs needed
1387 :
1388 : static constexpr std::string_view RoutineName("CalcUpdateHeatRecovery");
1389 :
1390 0 : PlantUtilities::SafeCopyPlantNode(state, this->PlantInletNodeID, this->PlantOutletNodeID);
1391 :
1392 0 : state.dataLoopNodes->Node(this->PlantOutletNodeID).Temp = this->A42Model.TcwOut;
1393 :
1394 0 : Real64 Cp = state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).glycol->getSpecificHeat(state, this->A42Model.TcwIn, RoutineName);
1395 :
1396 0 : state.dataLoopNodes->Node(this->PlantOutletNodeID).Enthalpy = this->A42Model.TcwOut * Cp;
1397 0 : }
1398 :
1399 2 : void MicroCHPDataStruct::getDesignCapacities(
1400 : [[maybe_unused]] EnergyPlusData &state, const EnergyPlus::PlantLocation &, Real64 &MaxLoad, Real64 &MinLoad, Real64 &OptLoad)
1401 : {
1402 2 : MaxLoad = state.dataGenerator->GeneratorDynamics(this->DynamicsControlID).QdotHXMax;
1403 2 : MinLoad = state.dataGenerator->GeneratorDynamics(this->DynamicsControlID).QdotHXMin;
1404 2 : OptLoad = state.dataGenerator->GeneratorDynamics(this->DynamicsControlID).QdotHXOpt;
1405 2 : }
1406 :
1407 0 : void MicroCHPDataStruct::UpdateMicroCHPGeneratorRecords(EnergyPlusData &state) // Generator number
1408 : {
1409 :
1410 : // SUBROUTINE INFORMATION:
1411 : // AUTHOR B. Griffith
1412 : // DATE WRITTEN July 2006
1413 : // MODIFIED na
1414 : // RE-ENGINEERED na
1415 :
1416 : // PURPOSE OF THIS SUBROUTINE:
1417 : // update variables in structures linked to output reports
1418 :
1419 : static constexpr std::string_view RoutineName("UpdateMicroCHPGeneratorRecords");
1420 :
1421 0 : this->A42Model.ACPowerGen = this->A42Model.Pnet; // electrical power produced [W]
1422 0 : this->A42Model.ACEnergyGen = this->A42Model.Pnet * state.dataHVACGlobal->TimeStepSysSec; // energy produced (J)
1423 0 : this->A42Model.QdotHX = this->A42Model.UAhx * (this->A42Model.Teng - this->A42Model.TcwOut); // heat recovered rate (W)
1424 :
1425 0 : Real64 Cp = state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).glycol->getSpecificHeat(state, this->A42Model.TcwIn, RoutineName);
1426 :
1427 0 : this->A42Model.QdotHR = this->PlantMassFlowRate * Cp * (this->A42Model.TcwOut - this->A42Model.TcwIn);
1428 0 : this->A42Model.TotalHeatEnergyRec = this->A42Model.QdotHR * state.dataHVACGlobal->TimeStepSysSec; // heat recovered energy (J)
1429 :
1430 0 : this->A42Model.HeatRecInletTemp = this->A42Model.TcwIn; // Heat Recovery Loop Inlet Temperature (C)
1431 0 : this->A42Model.HeatRecOutletTemp = this->A42Model.TcwOut; // Heat Recovery Loop Outlet Temperature (C)
1432 :
1433 0 : this->A42Model.FuelCompressPower = state.dataGenerator->FuelSupply(this->FuelSupplyID).PfuelCompEl;
1434 : // electrical power used by fuel supply compressor [W]
1435 0 : this->A42Model.FuelCompressEnergy =
1436 0 : state.dataGenerator->FuelSupply(this->FuelSupplyID).PfuelCompEl * state.dataHVACGlobal->TimeStepSys * Constant::rSecsInHour; // elect energy
1437 0 : this->A42Model.FuelCompressSkinLoss = state.dataGenerator->FuelSupply(this->FuelSupplyID).QskinLoss;
1438 : // heat rate of losses.by fuel supply compressor [W]
1439 0 : this->A42Model.FuelEnergyHHV = this->A42Model.NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).HHV *
1440 0 : state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec * state.dataHVACGlobal->TimeStepSys *
1441 : Constant::rSecsInHour;
1442 : // reporting: Fuel Energy used (W)
1443 0 : this->A42Model.FuelEnergyUseRateHHV = this->A42Model.NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).HHV *
1444 0 : state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1445 : // reporting: Fuel Energy used (J)
1446 0 : this->A42Model.FuelEnergyLHV =
1447 0 : this->A42Model.NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000000.0 * state.dataHVACGlobal->TimeStepSysSec;
1448 : // reporting: Fuel Energy used (W)
1449 0 : this->A42Model.FuelEnergyUseRateLHV = this->A42Model.NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000000.0;
1450 :
1451 0 : this->A42Model.SkinLossPower = this->A42Model.QdotConvZone + this->A42Model.QdotRadZone;
1452 0 : this->A42Model.SkinLossEnergy = (this->A42Model.QdotConvZone + this->A42Model.QdotRadZone) * state.dataHVACGlobal->TimeStepSysSec;
1453 0 : this->A42Model.SkinLossConvect = this->A42Model.QdotConvZone;
1454 0 : this->A42Model.SkinLossRadiat = this->A42Model.QdotRadZone;
1455 :
1456 : // update node data for air inlet (and outlet)
1457 0 : if (this->AirInletNodeID > 0) {
1458 0 : state.dataLoopNodes->Node(this->AirInletNodeID).MassFlowRate = this->A42Model.MdotAir;
1459 : }
1460 0 : if (this->AirOutletNodeID > 0) {
1461 0 : state.dataLoopNodes->Node(this->AirOutletNodeID).MassFlowRate = this->A42Model.MdotAir;
1462 0 : state.dataLoopNodes->Node(this->AirOutletNodeID).Temp = this->A42Model.Teng;
1463 : }
1464 0 : }
1465 2 : void MicroCHPDataStruct::oneTimeInit(EnergyPlusData &state)
1466 : {
1467 2 : if (this->myFlag) {
1468 2 : this->setupOutputVars(state);
1469 2 : this->myFlag = false;
1470 : }
1471 :
1472 2 : if (this->MyPlantScanFlag) {
1473 2 : if (allocated(state.dataPlnt->PlantLoop)) {
1474 2 : bool errFlag = false;
1475 4 : PlantUtilities::ScanPlantLoopsForObject(
1476 2 : state, this->Name, DataPlant::PlantEquipmentType::Generator_MicroCHP, this->CWPlantLoc, errFlag, _, _, _, _, _);
1477 :
1478 2 : if (errFlag) {
1479 0 : ShowFatalError(state, "InitMicroCHPNoNormalizeGenerators: Program terminated for previous conditions.");
1480 : }
1481 :
1482 2 : if (!this->A42Model.InternalFlowControl) {
1483 : // IF this is on the supply side and not internal flow control then reset flow priority to lower
1484 0 : if (this->CWPlantLoc.loopSideNum == DataPlant::LoopSideLocation::Supply) {
1485 0 : DataPlant::CompData::getPlantComponent(state, this->CWPlantLoc).FlowPriority = DataPlant::LoopFlowStatus::TakesWhatGets;
1486 : }
1487 : }
1488 :
1489 2 : this->MyPlantScanFlag = false;
1490 : }
1491 : }
1492 2 : }
1493 : } // namespace EnergyPlus::MicroCHPElectricGenerator
|