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