Line data Source code
1 : // EnergyPlus, Copyright (c) 1996-2023, 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 16024 : PlantComponent *MicroCHPDataStruct::factory(EnergyPlusData &state, std::string const &objectName)
106 : {
107 : // Process the input data
108 16024 : if (state.dataCHPElectGen->getMicroCHPInputFlag) {
109 2 : GetMicroCHPGeneratorInput(state);
110 2 : state.dataCHPElectGen->getMicroCHPInputFlag = false;
111 : }
112 :
113 : // Now look for this object
114 16024 : for (auto &thisMCHP : state.dataCHPElectGen->MicroCHP) {
115 16024 : if (thisMCHP.Name == objectName) {
116 16024 : return &thisMCHP;
117 : }
118 : }
119 : // If we didn't find it, fatal
120 : ShowFatalError(state, "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 4 : Array1D_string AlphArray(25); // character string data
142 4 : 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 2 : 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, "No " + state.dataIPShortCut->cCurrentModuleObject + " equipment specified in input file");
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 10 : 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 4 : 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 (UtilityRoutines::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 ((!(UtilityRoutines::SameString(AlphArray(4), "InternalControl"))) && (!(UtilityRoutines::SameString(AlphArray(4), "PlantControl")))) {
193 0 : ShowSevereError(state, "Invalid, " + state.dataIPShortCut->cAlphaFieldNames(4) + " = " + AlphArray(4));
194 0 : ShowContinueError(state, "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, "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, "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 (UtilityRoutines::SameString(AlphArray(7), "TimeDelay")) {
229 2 : state.dataCHPElectGen->MicroCHPParamInput(CHPParamNum).WarmUpByTimeDelay = true;
230 2 : state.dataCHPElectGen->MicroCHPParamInput(CHPParamNum).WarmUpByEngineTemp = false;
231 : }
232 6 : if ((!(UtilityRoutines::SameString(AlphArray(7), "NominalEngineTemperature"))) &&
233 4 : (!(UtilityRoutines::SameString(AlphArray(7), "TimeDelay")))) {
234 0 : ShowSevereError(state, "Invalid, " + state.dataIPShortCut->cAlphaFieldNames(7) + " = " + AlphArray(7));
235 0 : ShowContinueError(state, "Entered in " + state.dataIPShortCut->cCurrentModuleObject + '=' + AlphArray(1));
236 0 : ErrorsFound = true;
237 : }
238 2 : state.dataCHPElectGen->MicroCHPParamInput(CHPParamNum).kf = NumArray(13); // N13 Warmup Fuel Flow Rate Coefficient k_f
239 2 : state.dataCHPElectGen->MicroCHPParamInput(CHPParamNum).TnomEngOp = NumArray(14); // N14 Nominal Engine Operating Temperature [C]
240 2 : state.dataCHPElectGen->MicroCHPParamInput(CHPParamNum).kp = NumArray(15); // N15 Warmup Power Coefficient k_p
241 2 : state.dataCHPElectGen->MicroCHPParamInput(CHPParamNum).Rfuelwarmup = NumArray(16); // N16 Warm Up Fuel Flow Rate Limit Ratio
242 2 : state.dataCHPElectGen->MicroCHPParamInput(CHPParamNum).WarmUpDelay = NumArray(17); // N17 Warm Up Delay Time
243 :
244 2 : state.dataCHPElectGen->MicroCHPParamInput(CHPParamNum).PcoolDown = NumArray(18); // N18 Cool Down Power
245 :
246 2 : state.dataCHPElectGen->MicroCHPParamInput(CHPParamNum).CoolDownDelay = NumArray(19); // N19 Cool Down Delay Time in seconds
247 :
248 2 : if (UtilityRoutines::SameString(AlphArray(8), "MandatoryCoolDown")) {
249 1 : state.dataCHPElectGen->MicroCHPParamInput(CHPParamNum).MandatoryFullCoolDown = true;
250 1 : state.dataCHPElectGen->MicroCHPParamInput(CHPParamNum).WarmRestartOkay = false;
251 : }
252 5 : if ((!(UtilityRoutines::SameString(AlphArray(8), "MandatoryCoolDown"))) &&
253 3 : (!(UtilityRoutines::SameString(AlphArray(8), "OptionalCoolDown")))) {
254 0 : ShowSevereError(state, "Invalid, " + state.dataIPShortCut->cAlphaFieldNames(8) + " = " + AlphArray(8));
255 0 : ShowContinueError(state, "Entered in " + state.dataIPShortCut->cCurrentModuleObject + '=' + AlphArray(1));
256 0 : ErrorsFound = true;
257 : }
258 : }
259 :
260 2 : state.dataIPShortCut->cCurrentModuleObject = "Generator:MicroCHP";
261 2 : state.dataCHPElectGen->NumMicroCHPs =
262 2 : state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, state.dataIPShortCut->cCurrentModuleObject);
263 :
264 2 : if (state.dataCHPElectGen->NumMicroCHPs <= 0) {
265 : // shouldn't ever come here?
266 0 : ShowSevereError(state, "No " + state.dataIPShortCut->cCurrentModuleObject + " equipment specified in input file");
267 0 : ErrorsFound = true;
268 : }
269 :
270 2 : if (!(allocated(state.dataCHPElectGen->MicroCHP))) {
271 2 : state.dataCHPElectGen->MicroCHP.allocate(state.dataCHPElectGen->NumMicroCHPs); // inits handeled in derived type definitions
272 : }
273 :
274 : // load in Micro CHPs
275 4 : for (int GeneratorNum = 1; GeneratorNum <= state.dataCHPElectGen->NumMicroCHPs; ++GeneratorNum) {
276 10 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
277 2 : state.dataIPShortCut->cCurrentModuleObject,
278 : GeneratorNum,
279 : AlphArray,
280 : NumAlphas,
281 : NumArray,
282 : NumNums,
283 : IOStat,
284 : _,
285 2 : state.dataIPShortCut->lAlphaFieldBlanks,
286 2 : state.dataIPShortCut->cAlphaFieldNames,
287 2 : state.dataIPShortCut->cNumericFieldNames);
288 2 : UtilityRoutines::IsNameEmpty(state, AlphArray(1), state.dataIPShortCut->cCurrentModuleObject, ErrorsFound);
289 :
290 : // GENERATOR:MICRO CHP,
291 2 : state.dataCHPElectGen->MicroCHP(GeneratorNum).DynamicsControlID = GeneratorNum;
292 2 : state.dataCHPElectGen->MicroCHP(GeneratorNum).Name = AlphArray(1); // A1 Generator name
293 2 : state.dataCHPElectGen->MicroCHP(GeneratorNum).ParamObjName = AlphArray(2); // A2 Micro CHP Parameter Object Name
294 : // find input structure
295 2 : int thisParamID = UtilityRoutines::FindItemInList(AlphArray(2), state.dataCHPElectGen->MicroCHPParamInput);
296 2 : if (thisParamID != 0) {
297 4 : state.dataCHPElectGen->MicroCHP(GeneratorNum).A42Model =
298 4 : state.dataCHPElectGen->MicroCHPParamInput(thisParamID); // entire structure of input data assigned here!
299 : } else {
300 0 : ShowSevereError(state, "Invalid, " + state.dataIPShortCut->cAlphaFieldNames(2) + " = " + AlphArray(2));
301 0 : ShowContinueError(state, "Entered in " + state.dataIPShortCut->cCurrentModuleObject + '=' + AlphArray(1));
302 0 : ErrorsFound = true;
303 : }
304 :
305 2 : if (!state.dataIPShortCut->lAlphaFieldBlanks(3)) {
306 2 : state.dataCHPElectGen->MicroCHP(GeneratorNum).ZoneName = AlphArray(3); // A3 Zone Name
307 2 : state.dataCHPElectGen->MicroCHP(GeneratorNum).ZoneID =
308 2 : UtilityRoutines::FindItemInList(state.dataCHPElectGen->MicroCHP(GeneratorNum).ZoneName, state.dataHeatBal->Zone);
309 2 : if (state.dataCHPElectGen->MicroCHP(GeneratorNum).ZoneID == 0) {
310 0 : ShowSevereError(state, "Invalid, " + state.dataIPShortCut->cAlphaFieldNames(3) + " = " + AlphArray(3));
311 0 : ShowContinueError(state, "Entered in " + state.dataIPShortCut->cCurrentModuleObject + '=' + AlphArray(1));
312 0 : ErrorsFound = true;
313 : }
314 : } else {
315 0 : state.dataCHPElectGen->MicroCHP(GeneratorNum).ZoneID = 0;
316 : }
317 2 : state.dataCHPElectGen->MicroCHP(GeneratorNum).PlantInletNodeName = AlphArray(4); // A4 Cooling Water Inlet Node Name
318 2 : state.dataCHPElectGen->MicroCHP(GeneratorNum).PlantOutletNodeName = AlphArray(5); // A5 Cooling Water Outlet Node Name
319 : // find node ids for water path
320 2 : state.dataCHPElectGen->MicroCHP(GeneratorNum).PlantInletNodeID =
321 4 : NodeInputManager::GetOnlySingleNode(state,
322 2 : AlphArray(4),
323 : ErrorsFound,
324 : DataLoopNode::ConnectionObjectType::GeneratorMicroCHP,
325 2 : AlphArray(1),
326 : DataLoopNode::NodeFluidType::Water,
327 : DataLoopNode::ConnectionType::Inlet,
328 : NodeInputManager::CompFluidStream::Primary,
329 2 : DataLoopNode::ObjectIsNotParent);
330 2 : state.dataCHPElectGen->MicroCHP(GeneratorNum).PlantOutletNodeID =
331 4 : NodeInputManager::GetOnlySingleNode(state,
332 2 : AlphArray(5),
333 : ErrorsFound,
334 : DataLoopNode::ConnectionObjectType::GeneratorMicroCHP,
335 2 : AlphArray(1),
336 : DataLoopNode::NodeFluidType::Water,
337 : DataLoopNode::ConnectionType::Outlet,
338 : NodeInputManager::CompFluidStream::Primary,
339 2 : DataLoopNode::ObjectIsNotParent);
340 6 : BranchNodeConnections::TestCompSet(
341 4 : state, state.dataIPShortCut->cCurrentModuleObject, AlphArray(1), AlphArray(4), AlphArray(5), "Heat Recovery Nodes");
342 :
343 2 : state.dataCHPElectGen->MicroCHP(GeneratorNum).AirInletNodeName = AlphArray(6); // A6 Air Inlet Node Name
344 : // check the node connections
345 2 : state.dataCHPElectGen->MicroCHP(GeneratorNum).AirInletNodeID =
346 4 : NodeInputManager::GetOnlySingleNode(state,
347 2 : AlphArray(6),
348 : ErrorsFound,
349 : DataLoopNode::ConnectionObjectType::GeneratorMicroCHP,
350 2 : AlphArray(1),
351 : DataLoopNode::NodeFluidType::Air,
352 : DataLoopNode::ConnectionType::Inlet,
353 : NodeInputManager::CompFluidStream::Secondary,
354 2 : DataLoopNode::ObjectIsNotParent);
355 :
356 2 : state.dataCHPElectGen->MicroCHP(GeneratorNum).AirOutletNodeName = AlphArray(7); // A7 Air Outlet Node Name
357 2 : state.dataCHPElectGen->MicroCHP(GeneratorNum).AirOutletNodeID =
358 4 : NodeInputManager::GetOnlySingleNode(state,
359 2 : AlphArray(7),
360 : ErrorsFound,
361 : DataLoopNode::ConnectionObjectType::GeneratorMicroCHP,
362 2 : AlphArray(1),
363 : DataLoopNode::NodeFluidType::Air,
364 : DataLoopNode::ConnectionType::Outlet,
365 : NodeInputManager::CompFluidStream::Secondary,
366 2 : DataLoopNode::ObjectIsNotParent);
367 :
368 2 : state.dataCHPElectGen->MicroCHP(GeneratorNum).FuelSupplyID =
369 2 : UtilityRoutines::FindItemInList(AlphArray(8), state.dataGenerator->FuelSupply); // Fuel Supply ID
370 2 : if (state.dataCHPElectGen->MicroCHP(GeneratorNum).FuelSupplyID == 0) {
371 0 : ShowSevereError(state, "Invalid, " + state.dataIPShortCut->cAlphaFieldNames(8) + " = " + AlphArray(8));
372 0 : ShowContinueError(state, "Entered in " + state.dataIPShortCut->cCurrentModuleObject + '=' + AlphArray(1));
373 0 : ErrorsFound = true;
374 : }
375 :
376 2 : if (state.dataIPShortCut->lAlphaFieldBlanks(9)) {
377 0 : state.dataCHPElectGen->MicroCHP(GeneratorNum).AvailabilitySchedID = DataGlobalConstants::ScheduleAlwaysOn;
378 : } else {
379 2 : state.dataCHPElectGen->MicroCHP(GeneratorNum).AvailabilitySchedID = ScheduleManager::GetScheduleIndex(state, AlphArray(9));
380 2 : if (state.dataCHPElectGen->MicroCHP(GeneratorNum).AvailabilitySchedID == 0) {
381 0 : ShowSevereError(state, "Invalid, " + state.dataIPShortCut->cAlphaFieldNames(9) + " = " + AlphArray(9));
382 0 : ShowContinueError(state, "Entered in " + state.dataIPShortCut->cCurrentModuleObject + '=' + AlphArray(1));
383 0 : ErrorsFound = true;
384 : }
385 : }
386 2 : state.dataCHPElectGen->MicroCHP(GeneratorNum).A42Model.TengLast = 20.0; // inits
387 2 : state.dataCHPElectGen->MicroCHP(GeneratorNum).A42Model.TempCWOutLast = 20.0; // inits
388 : }
389 :
390 2 : if (ErrorsFound) {
391 0 : ShowFatalError(state, "Errors found in processing input for " + state.dataIPShortCut->cCurrentModuleObject);
392 : }
393 :
394 : // setup report variables
395 2 : for (int GeneratorNum = 1; GeneratorNum <= state.dataCHPElectGen->NumMicroCHPs; ++GeneratorNum) {
396 : }
397 :
398 2 : state.dataCHPElectGen->MyOneTimeFlag = false;
399 : }
400 2 : }
401 :
402 2 : void MicroCHPDataStruct::setupOutputVars(EnergyPlusData &state)
403 : {
404 4 : SetupOutputVariable(state,
405 : "Generator Off Mode Time",
406 : OutputProcessor::Unit::s,
407 : this->A42Model.OffModeTime,
408 : OutputProcessor::SOVTimeStepType::System,
409 : OutputProcessor::SOVStoreType::Summed,
410 2 : this->Name);
411 :
412 4 : SetupOutputVariable(state,
413 : "Generator Standby Mode Time",
414 : OutputProcessor::Unit::s,
415 : this->A42Model.StandyByModeTime,
416 : OutputProcessor::SOVTimeStepType::System,
417 : OutputProcessor::SOVStoreType::Summed,
418 2 : this->Name);
419 :
420 4 : SetupOutputVariable(state,
421 : "Generator Warm Up Mode Time",
422 : OutputProcessor::Unit::s,
423 : this->A42Model.WarmUpModeTime,
424 : OutputProcessor::SOVTimeStepType::System,
425 : OutputProcessor::SOVStoreType::Summed,
426 2 : this->Name);
427 :
428 4 : SetupOutputVariable(state,
429 : "Generator Normal Operating Mode Time",
430 : OutputProcessor::Unit::s,
431 : this->A42Model.NormalModeTime,
432 : OutputProcessor::SOVTimeStepType::System,
433 : OutputProcessor::SOVStoreType::Summed,
434 2 : this->Name);
435 :
436 4 : SetupOutputVariable(state,
437 : "Generator Cool Down Mode Time",
438 : OutputProcessor::Unit::s,
439 : this->A42Model.CoolDownModeTime,
440 : OutputProcessor::SOVTimeStepType::System,
441 : OutputProcessor::SOVStoreType::Summed,
442 2 : this->Name);
443 :
444 4 : SetupOutputVariable(state,
445 : "Generator Produced AC Electricity Rate",
446 : OutputProcessor::Unit::W,
447 : this->A42Model.ACPowerGen,
448 : OutputProcessor::SOVTimeStepType::System,
449 : OutputProcessor::SOVStoreType::Average,
450 2 : this->Name);
451 :
452 4 : SetupOutputVariable(state,
453 : "Generator Produced AC Electricity Energy",
454 : OutputProcessor::Unit::J,
455 : this->A42Model.ACEnergyGen,
456 : OutputProcessor::SOVTimeStepType::System,
457 : OutputProcessor::SOVStoreType::Summed,
458 : this->Name,
459 : _,
460 : "ElectricityProduced",
461 : "COGENERATION",
462 : _,
463 2 : "Plant");
464 :
465 4 : SetupOutputVariable(state,
466 : "Generator Produced Thermal Rate",
467 : OutputProcessor::Unit::W,
468 : this->A42Model.QdotHR,
469 : OutputProcessor::SOVTimeStepType::System,
470 : OutputProcessor::SOVStoreType::Average,
471 2 : this->Name);
472 :
473 4 : SetupOutputVariable(state,
474 : "Generator Produced Thermal Energy",
475 : OutputProcessor::Unit::J,
476 : this->A42Model.TotalHeatEnergyRec,
477 : OutputProcessor::SOVTimeStepType::System,
478 : OutputProcessor::SOVStoreType::Summed,
479 : this->Name,
480 : _,
481 : "ENERGYTRANSFER",
482 : "COGENERATION",
483 : _,
484 2 : "Plant");
485 :
486 4 : SetupOutputVariable(state,
487 : "Generator Electric Efficiency",
488 : OutputProcessor::Unit::None,
489 : this->A42Model.ElecEff,
490 : OutputProcessor::SOVTimeStepType::System,
491 : OutputProcessor::SOVStoreType::Average,
492 2 : this->Name);
493 :
494 4 : SetupOutputVariable(state,
495 : "Generator Thermal Efficiency",
496 : OutputProcessor::Unit::None,
497 : this->A42Model.ThermEff,
498 : OutputProcessor::SOVTimeStepType::System,
499 : OutputProcessor::SOVStoreType::Average,
500 2 : this->Name);
501 :
502 4 : SetupOutputVariable(state,
503 : "Generator Gross Input Heat Rate",
504 : OutputProcessor::Unit::W,
505 : this->A42Model.Qgross,
506 : OutputProcessor::SOVTimeStepType::System,
507 : OutputProcessor::SOVStoreType::Average,
508 2 : this->Name);
509 :
510 4 : SetupOutputVariable(state,
511 : "Generator Steady State Engine Heat Generation Rate",
512 : OutputProcessor::Unit::W,
513 : this->A42Model.Qgenss,
514 : OutputProcessor::SOVTimeStepType::System,
515 : OutputProcessor::SOVStoreType::Average,
516 2 : this->Name);
517 :
518 4 : SetupOutputVariable(state,
519 : "Generator Engine Heat Exchange Rate",
520 : OutputProcessor::Unit::W,
521 : this->A42Model.QdotHX,
522 : OutputProcessor::SOVTimeStepType::System,
523 : OutputProcessor::SOVStoreType::Average,
524 2 : this->Name);
525 :
526 4 : SetupOutputVariable(state,
527 : "Generator Air Mass Flow Rate",
528 : OutputProcessor::Unit::kg_s,
529 : this->A42Model.MdotAir,
530 : OutputProcessor::SOVTimeStepType::System,
531 : OutputProcessor::SOVStoreType::Average,
532 2 : this->Name);
533 :
534 4 : SetupOutputVariable(state,
535 : "Generator Fuel Molar Flow Rate",
536 : OutputProcessor::Unit::kmol_s,
537 : this->A42Model.NdotFuel,
538 : OutputProcessor::SOVTimeStepType::System,
539 : OutputProcessor::SOVStoreType::Average,
540 2 : this->Name);
541 :
542 4 : SetupOutputVariable(state,
543 : "Generator Fuel Mass Flow Rate",
544 : OutputProcessor::Unit::kg_s,
545 : this->A42Model.MdotFuel,
546 : OutputProcessor::SOVTimeStepType::System,
547 : OutputProcessor::SOVStoreType::Average,
548 2 : this->Name);
549 :
550 4 : SetupOutputVariable(state,
551 : "Generator Engine Temperature",
552 : OutputProcessor::Unit::C,
553 : this->A42Model.Teng,
554 : OutputProcessor::SOVTimeStepType::System,
555 : OutputProcessor::SOVStoreType::Average,
556 2 : this->Name);
557 :
558 4 : SetupOutputVariable(state,
559 : "Generator Coolant Inlet Temperature",
560 : OutputProcessor::Unit::C,
561 : this->A42Model.HeatRecInletTemp,
562 : OutputProcessor::SOVTimeStepType::System,
563 : OutputProcessor::SOVStoreType::Average,
564 2 : this->Name);
565 :
566 4 : SetupOutputVariable(state,
567 : "Generator Coolant Outlet Temperature",
568 : OutputProcessor::Unit::C,
569 : this->A42Model.HeatRecOutletTemp,
570 : OutputProcessor::SOVTimeStepType::System,
571 : OutputProcessor::SOVStoreType::Average,
572 2 : this->Name);
573 :
574 : // this next one needs to be reconciled with non-gas fuel constituents.
575 : // need custom resourceTypeKey or something for user defined fuel compositions.
576 4 : SetupOutputVariable(state,
577 : "Generator Fuel HHV Basis Energy",
578 : OutputProcessor::Unit::J,
579 : this->A42Model.FuelEnergyHHV,
580 : OutputProcessor::SOVTimeStepType::System,
581 : OutputProcessor::SOVStoreType::Summed,
582 : this->Name,
583 : _,
584 : "NaturalGas",
585 : "COGENERATION",
586 : _,
587 2 : "Plant");
588 :
589 4 : SetupOutputVariable(state,
590 : "Generator Fuel HHV Basis Rate",
591 : OutputProcessor::Unit::W,
592 : this->A42Model.FuelEnergyUseRateHHV,
593 : OutputProcessor::SOVTimeStepType::System,
594 : OutputProcessor::SOVStoreType::Average,
595 2 : this->Name);
596 :
597 4 : SetupOutputVariable(state,
598 : "Generator Fuel LHV Basis Energy",
599 : OutputProcessor::Unit::J,
600 : this->A42Model.FuelEnergyLHV,
601 : OutputProcessor::SOVTimeStepType::System,
602 : OutputProcessor::SOVStoreType::Summed,
603 2 : this->Name);
604 :
605 4 : SetupOutputVariable(state,
606 : "Generator Fuel LHV Basis Rate",
607 : OutputProcessor::Unit::W,
608 : this->A42Model.FuelEnergyUseRateLHV,
609 : OutputProcessor::SOVTimeStepType::System,
610 : OutputProcessor::SOVStoreType::Average,
611 2 : this->Name);
612 :
613 4 : SetupOutputVariable(state,
614 : "Generator Fuel Compressor Electricity Rate",
615 : OutputProcessor::Unit::W,
616 : this->A42Model.FuelCompressPower,
617 : OutputProcessor::SOVTimeStepType::System,
618 : OutputProcessor::SOVStoreType::Average,
619 2 : this->Name);
620 :
621 4 : SetupOutputVariable(state,
622 : "Generator Fuel Compressor Electricity Energy",
623 : OutputProcessor::Unit::J,
624 : this->A42Model.FuelCompressEnergy,
625 : OutputProcessor::SOVTimeStepType::System,
626 : OutputProcessor::SOVStoreType::Summed,
627 2 : this->Name);
628 :
629 4 : SetupOutputVariable(state,
630 : "Generator Fuel Compressor Skin Heat Loss Rate",
631 : OutputProcessor::Unit::W,
632 : this->A42Model.FuelCompressSkinLoss,
633 : OutputProcessor::SOVTimeStepType::System,
634 : OutputProcessor::SOVStoreType::Average,
635 2 : this->Name);
636 :
637 4 : SetupOutputVariable(state,
638 : "Generator Zone Sensible Heat Transfer Rate",
639 : OutputProcessor::Unit::W,
640 : this->A42Model.SkinLossPower,
641 : OutputProcessor::SOVTimeStepType::System,
642 : OutputProcessor::SOVStoreType::Average,
643 2 : this->Name);
644 :
645 4 : SetupOutputVariable(state,
646 : "Generator Zone Sensible Heat Transfer Energy",
647 : OutputProcessor::Unit::J,
648 : this->A42Model.SkinLossEnergy,
649 : OutputProcessor::SOVTimeStepType::System,
650 : OutputProcessor::SOVStoreType::Summed,
651 2 : this->Name);
652 :
653 4 : SetupOutputVariable(state,
654 : "Generator Zone Convection Heat Transfer Rate",
655 : OutputProcessor::Unit::W,
656 : this->A42Model.SkinLossConvect,
657 : OutputProcessor::SOVTimeStepType::System,
658 : OutputProcessor::SOVStoreType::Average,
659 2 : this->Name);
660 :
661 4 : SetupOutputVariable(state,
662 : "Generator Zone Radiation Heat Transfer Rate",
663 : OutputProcessor::Unit::W,
664 : this->A42Model.SkinLossRadiat,
665 : OutputProcessor::SOVTimeStepType::System,
666 : OutputProcessor::SOVStoreType::Average,
667 2 : this->Name);
668 :
669 2 : if (this->ZoneID > 0) {
670 2 : SetupZoneInternalGain(state,
671 : this->ZoneID,
672 : this->Name,
673 : DataHeatBalance::IntGainType::GeneratorMicroCHP,
674 : &this->A42Model.SkinLossConvect,
675 : nullptr,
676 : &this->A42Model.SkinLossRadiat);
677 : }
678 2 : }
679 :
680 80743 : void MicroCHPDataStruct::simulate(EnergyPlusData &state,
681 : [[maybe_unused]] const EnergyPlus::PlantLocation &calledFromLocation,
682 : bool FirstHVACIteration,
683 : [[maybe_unused]] Real64 &CurLoad,
684 : [[maybe_unused]] bool RunFlag)
685 : {
686 : // empty function to emulate current behavior as of conversion to using the PlantComponent calling structure.
687 : // calls from the plant side only update the nodes.
688 : // calls from the ElectricPowerServiceManger call the init, calc, and update worker functions
689 :
690 80743 : PlantUtilities::UpdateComponentHeatRecoverySide(state,
691 : this->CWPlantLoc.loopNum,
692 : this->CWPlantLoc.loopSideNum,
693 : DataPlant::PlantEquipmentType::Generator_MicroCHP,
694 : this->PlantInletNodeID,
695 : this->PlantOutletNodeID,
696 : this->A42Model.QdotHR,
697 : this->A42Model.HeatRecInletTemp,
698 : this->A42Model.HeatRecOutletTemp,
699 : this->PlantMassFlowRate,
700 : FirstHVACIteration);
701 80743 : }
702 :
703 10 : void MicroCHPDataStruct::onInitLoopEquip(EnergyPlusData &state, const EnergyPlus::PlantLocation &)
704 : {
705 : static constexpr std::string_view RoutineName("MicroCHPDataStruct::onInitLoopEquip");
706 :
707 30 : Real64 rho = FluidProperties::GetDensityGlycol(state,
708 10 : state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).FluidName,
709 10 : state.dataLoopNodes->Node(this->PlantInletNodeID).Temp,
710 10 : state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).FluidIndex,
711 10 : RoutineName);
712 10 : if (this->A42Model.InternalFlowControl) { // got a curve
713 5 : this->PlantMassFlowRateMax =
714 15 : 2.0 * Curve::CurveValue(
715 5 : state, this->A42Model.WaterFlowCurveID, this->A42Model.MaxElecPower, state.dataLoopNodes->Node(this->PlantInletNodeID).Temp);
716 5 : } else if (this->CWPlantLoc.loopSideNum == DataPlant::LoopSideLocation::Supply) {
717 5 : if (state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).MaxMassFlowRate > 0.0) {
718 4 : this->PlantMassFlowRateMax = state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).MaxMassFlowRate;
719 1 : } else if (state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).PlantSizNum > 0) {
720 0 : this->PlantMassFlowRateMax = state.dataSize->PlantSizData(this->CWPlantLoc.loopNum).DesVolFlowRate * rho;
721 : } else {
722 1 : this->PlantMassFlowRateMax = 2.0;
723 : }
724 :
725 0 : } else if (this->CWPlantLoc.loopSideNum == DataPlant::LoopSideLocation::Demand) {
726 0 : this->PlantMassFlowRateMax = 2.0; // would like to use plant loop max but not ready yet
727 : }
728 :
729 10 : PlantUtilities::RegisterPlantCompDesignFlow(state, this->PlantInletNodeID, this->PlantMassFlowRateMax / rho);
730 :
731 20 : this->A42Model.ElecEff = Curve::CurveValue(state,
732 : this->A42Model.ElecEffCurveID,
733 : this->A42Model.MaxElecPower,
734 : this->PlantMassFlowRateMax,
735 10 : state.dataLoopNodes->Node(this->PlantInletNodeID).Temp);
736 :
737 20 : this->A42Model.ThermEff = Curve::CurveValue(state,
738 : this->A42Model.ThermalEffCurveID,
739 : this->A42Model.MaxElecPower,
740 : this->PlantMassFlowRateMax,
741 10 : state.dataLoopNodes->Node(this->PlantInletNodeID).Temp);
742 :
743 10 : GeneratorDynamicsManager::SetupGeneratorControlStateManager(state, this->DynamicsControlID);
744 10 : }
745 :
746 16022 : void MicroCHPDataStruct::InitMicroCHPNoNormalizeGenerators(EnergyPlusData &state)
747 : {
748 : // SUBROUTINE INFORMATION:
749 : // AUTHOR BGriffith
750 : // DATE WRITTEN March 2007
751 : // MODIFIED na
752 : // RE-ENGINEERED na
753 :
754 16022 : this->oneTimeInit(state);
755 :
756 16022 : if (!state.dataGlobal->SysSizingCalc && this->MySizeFlag && !this->MyPlantScanFlag && (state.dataPlnt->PlantFirstSizesOkayToFinalize)) {
757 2 : this->MySizeFlag = false;
758 : }
759 :
760 16022 : if (this->MySizeFlag) return;
761 :
762 13932 : int DynaCntrlNum = this->DynamicsControlID;
763 :
764 13932 : if (state.dataGlobal->BeginEnvrnFlag && this->MyEnvrnFlag) {
765 : // reset to starting condition for different environment runperiods, design days
766 162 : this->A42Model.TengLast = 20.0;
767 162 : this->A42Model.TempCWOutLast = 20.0;
768 162 : this->A42Model.TimeElapsed = 0.0;
769 162 : this->A42Model.OpMode = DataGenerators::OperatingMode::Invalid;
770 162 : this->A42Model.OffModeTime = 0.0;
771 162 : this->A42Model.StandyByModeTime = 0.0;
772 162 : this->A42Model.WarmUpModeTime = 0.0;
773 162 : this->A42Model.NormalModeTime = 0.0;
774 162 : this->A42Model.CoolDownModeTime = 0.0;
775 162 : this->A42Model.Pnet = 0.0;
776 162 : this->A42Model.ElecEff = 0.0;
777 162 : this->A42Model.Qgross = 0.0;
778 162 : this->A42Model.ThermEff = 0.0;
779 162 : this->A42Model.Qgenss = 0.0;
780 162 : this->A42Model.NdotFuel = 0.0;
781 162 : this->A42Model.MdotFuel = 0.0;
782 162 : this->A42Model.Teng = 20.0;
783 162 : this->A42Model.TcwIn = 20.0;
784 162 : this->A42Model.TcwOut = 20.0;
785 162 : this->A42Model.MdotAir = 0.0;
786 162 : this->A42Model.QdotSkin = 0.0;
787 162 : this->A42Model.QdotConvZone = 0.0;
788 162 : this->A42Model.QdotRadZone = 0.0;
789 162 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).LastOpMode = DataGenerators::OperatingMode::Off;
790 162 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).CurrentOpMode = DataGenerators::OperatingMode::Off;
791 162 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).FractionalDayofLastShutDown = 0.0;
792 162 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).FractionalDayofLastStartUp = 0.0;
793 162 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).HasBeenOn = false;
794 162 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).DuringStartUp = false;
795 162 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).DuringShutDown = false;
796 162 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).FuelMdotLastTimestep = 0.0;
797 162 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).PelLastTimeStep = 0.0;
798 162 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).NumCycles = 0;
799 :
800 162 : state.dataGenerator->FuelSupply(this->FuelSupplyID).QskinLoss = 0.0;
801 :
802 162 : PlantUtilities::InitComponentNodes(state, 0.0, this->PlantMassFlowRateMax, this->PlantInletNodeID, this->PlantOutletNodeID);
803 : }
804 :
805 13932 : if (!state.dataGlobal->BeginEnvrnFlag) {
806 13770 : this->MyEnvrnFlag = true;
807 : }
808 :
809 : Real64 TimeElapsed =
810 13932 : state.dataGlobal->HourOfDay + state.dataGlobal->TimeStep * state.dataGlobal->TimeStepZone + state.dataHVACGlobal->SysTimeElapsed;
811 13932 : 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 4648 : this->A42Model.TengLast = this->A42Model.Teng;
815 4648 : this->A42Model.TempCWOutLast = this->A42Model.TcwOut;
816 4648 : this->A42Model.TimeElapsed = TimeElapsed;
817 4648 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).LastOpMode = state.dataGenerator->GeneratorDynamics(DynaCntrlNum).CurrentOpMode;
818 4648 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).FuelMdotLastTimestep = this->A42Model.MdotFuel;
819 4648 : state.dataGenerator->GeneratorDynamics(DynaCntrlNum).PelLastTimeStep = this->A42Model.Pnet;
820 : }
821 :
822 13932 : if (!this->A42Model.InternalFlowControl) {
823 :
824 6134 : Real64 mdot = this->PlantMassFlowRateMax;
825 6134 : PlantUtilities::SetComponentFlowRate(state, mdot, this->PlantInletNodeID, this->PlantOutletNodeID, this->CWPlantLoc);
826 6134 : this->PlantMassFlowRate = mdot;
827 : }
828 : }
829 :
830 13932 : 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 : bool const FirstHVACIteration)
836 : {
837 :
838 : // SUBROUTINE INFORMATION:
839 : // AUTHOR B Griffith
840 : // DATE WRITTEN July 2006
841 : // MODIFIED na
842 : // RE-ENGINEERED na
843 :
844 : // PURPOSE OF THIS SUBROUTINE:
845 : // Main calculation subroutine for the IEA Annex 42 model
846 :
847 : // METHODOLOGY EMPLOYED:
848 : // curve fit, dynamic control limits,
849 :
850 : // REFERENCES:
851 : // IEA Annex 42 FC-COGEN-SIM "A Generic Model Specification for Combustion-based Residential CHP Devices"
852 : // Alex Ferguson, Nick Kelly, Version 3, June 26, 2006
853 :
854 : static constexpr std::string_view RoutineName("CalcMicroCHPNoNormalizeGeneratorModel");
855 :
856 13932 : DataGenerators::OperatingMode CurrentOpMode = DataGenerators::OperatingMode::Invalid;
857 13932 : Real64 AllowedLoad = 0.0;
858 13932 : Real64 PLRforSubtimestepStartUp(1.0);
859 13932 : Real64 PLRforSubtimestepShutDown(0.0);
860 13932 : bool RunFlag(false);
861 :
862 13932 : GeneratorDynamicsManager::ManageGeneratorControlState(state,
863 : GeneratorType::MicroCHP,
864 : this->Name,
865 : this->DynamicsControlID,
866 : RunFlagElectCenter,
867 : RunFlagPlant,
868 : MyElectricLoad,
869 : MyThermalLoad,
870 : AllowedLoad,
871 : CurrentOpMode,
872 : PLRforSubtimestepStartUp,
873 : PLRforSubtimestepShutDown,
874 : FirstHVACIteration);
875 :
876 13932 : if (RunFlagElectCenter || RunFlagPlant) RunFlag = true;
877 :
878 13932 : Real64 Teng = this->A42Model.Teng;
879 13932 : Real64 TcwOut = this->A42Model.TcwOut;
880 :
881 : Real64 thisAmbientTemp;
882 13932 : if (this->ZoneID > 0) {
883 13932 : thisAmbientTemp = state.dataZoneTempPredictorCorrector->zoneHeatBalance(this->ZoneID).MAT;
884 : } else { // outdoor location, no zone
885 0 : thisAmbientTemp = state.dataEnvrn->OutDryBulbTemp;
886 : }
887 :
888 13932 : Real64 Pnetss = 0.0;
889 13932 : Real64 Pstandby = 0.0; // power draw during standby, positive here means negative production
890 13932 : Real64 Pcooler = 0.0; // power draw during cool down, positive here means negative production
891 13932 : Real64 NdotFuel = 0.0;
892 13932 : Real64 ElecEff = 0.0;
893 13932 : Real64 MdotAir = 0.0;
894 13932 : Real64 Qgenss = 0.0;
895 13932 : Real64 MdotCW = 0.0;
896 13932 : Real64 TcwIn = 0.0;
897 13932 : Real64 MdotFuel = 0.0;
898 13932 : Real64 Qgross = 0.0;
899 13932 : Real64 ThermEff = 0.0;
900 :
901 13932 : switch (CurrentOpMode) {
902 5630 : case DataGenerators::OperatingMode::Off: { // same as standby in model spec but no Pnet standby electicity losses.
903 :
904 5630 : Qgenss = 0.0;
905 5630 : MdotCW = state.dataLoopNodes->Node(this->PlantInletNodeID).MassFlowRate; // kg/s
906 5630 : TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
907 5630 : Pnetss = 0.0;
908 5630 : Pstandby = 0.0;
909 5630 : Pcooler = this->A42Model.PcoolDown * PLRforSubtimestepShutDown;
910 5630 : ElecEff = 0.0;
911 5630 : ThermEff = 0.0;
912 5630 : Qgross = 0.0;
913 5630 : NdotFuel = 0.0;
914 5630 : MdotFuel = 0.0;
915 5630 : MdotAir = 0.0;
916 :
917 5630 : MdotCW = 0.0;
918 5630 : PlantUtilities::SetComponentFlowRate(state, MdotCW, this->PlantInletNodeID, this->PlantOutletNodeID, this->CWPlantLoc);
919 5630 : this->PlantMassFlowRate = MdotCW;
920 5630 : } break;
921 3967 : case DataGenerators::OperatingMode::Standby: {
922 3967 : Qgenss = 0.0;
923 3967 : MdotCW = state.dataLoopNodes->Node(this->PlantInletNodeID).MassFlowRate; // kg/s
924 3967 : TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
925 3967 : Pnetss = 0.0;
926 3967 : Pstandby = this->A42Model.Pstandby * (1.0 - PLRforSubtimestepShutDown);
927 3967 : Pcooler = this->A42Model.PcoolDown * PLRforSubtimestepShutDown;
928 3967 : ElecEff = 0.0;
929 3967 : ThermEff = 0.0;
930 3967 : Qgross = 0.0;
931 3967 : NdotFuel = 0.0;
932 3967 : MdotFuel = 0.0;
933 3967 : MdotAir = 0.0;
934 :
935 3967 : MdotCW = 0.0;
936 3967 : PlantUtilities::SetComponentFlowRate(state, MdotCW, this->PlantInletNodeID, this->PlantOutletNodeID, this->CWPlantLoc);
937 3967 : this->PlantMassFlowRate = MdotCW;
938 3967 : } break;
939 0 : case DataGenerators::OperatingMode::WarmUp: {
940 0 : if (this->A42Model.WarmUpByTimeDelay) {
941 : // Internal combustion engine. This is just like normal operation but no net power yet.
942 0 : Pnetss = MyElectricLoad; // W
943 0 : Pstandby = 0.0;
944 0 : Pcooler = this->A42Model.PcoolDown * PLRforSubtimestepShutDown;
945 0 : TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
946 0 : MdotCW = state.dataLoopNodes->Node(this->PlantInletNodeID).MassFlowRate; // kg/s
947 0 : if (this->A42Model.InternalFlowControl) {
948 0 : MdotCW = GeneratorDynamicsManager::FuncDetermineCWMdotForInternalFlowControl(state, this->DynamicsControlID, Pnetss, TcwIn);
949 : }
950 0 : ElecEff = Curve::CurveValue(state, this->A42Model.ElecEffCurveID, Pnetss, MdotCW, TcwIn);
951 0 : ElecEff = max(0.0, ElecEff); // protect against bad curve result
952 :
953 0 : if (ElecEff > 0.0) { // trap divide by bad thing
954 0 : Qgross = Pnetss / ElecEff; // W
955 : } else {
956 0 : Qgross = 0.0;
957 : }
958 0 : ThermEff = Curve::CurveValue(state, this->A42Model.ThermalEffCurveID, Pnetss, MdotCW, TcwIn);
959 0 : ThermEff = max(0.0, ThermEff); // protect against bad curve result
960 :
961 0 : Qgenss = ThermEff * Qgross; // W
962 :
963 0 : MdotFuel = Qgross / (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0) *
964 0 : state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
965 : // kMol/s = (J/s) /(KJ/mol * 1000 J/KJ * 1000 mol/kmol)
966 :
967 0 : bool ConstrainedIncreasingNdot(false);
968 0 : bool ConstrainedDecreasingNdot(false);
969 0 : Real64 MdotFuelAllowed = 0.0;
970 :
971 0 : GeneratorDynamicsManager::ManageGeneratorFuelFlow(state,
972 : GeneratorType::MicroCHP,
973 : this->Name,
974 : this->DynamicsControlID,
975 : RunFlag,
976 : MdotFuel,
977 : MdotFuelAllowed,
978 : ConstrainedIncreasingNdot,
979 : ConstrainedDecreasingNdot);
980 :
981 0 : if (ConstrainedIncreasingNdot || ConstrainedDecreasingNdot) { // recalculate Pnetss with new NdotFuel with iteration
982 0 : MdotFuel = MdotFuelAllowed;
983 0 : NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
984 0 : Qgross = NdotFuel * (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
985 :
986 0 : for (int i = 1; i <= 20; ++i) { // iterating here could add use of seach method
987 0 : Pnetss = Qgross * ElecEff;
988 0 : if (this->A42Model.InternalFlowControl) {
989 0 : MdotCW = GeneratorDynamicsManager::FuncDetermineCWMdotForInternalFlowControl(state, this->DynamicsControlID, Pnetss, TcwIn);
990 : }
991 0 : ElecEff = Curve::CurveValue(state, this->A42Model.ElecEffCurveID, Pnetss, MdotCW, TcwIn);
992 0 : ElecEff = max(0.0, ElecEff); // protect against bad curve result
993 : }
994 :
995 0 : ThermEff = Curve::CurveValue(state, this->A42Model.ThermalEffCurveID, Pnetss, MdotCW, TcwIn);
996 0 : ThermEff = max(0.0, ThermEff); // protect against bad curve result
997 0 : Qgenss = ThermEff * Qgross; // W
998 : }
999 0 : Pnetss = 0.0; // no actually power produced here.
1000 0 : NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1001 0 : MdotAir = Curve::CurveValue(state, this->A42Model.AirFlowCurveID, MdotFuel);
1002 0 : MdotAir = max(0.0, MdotAir); // protect against bad curve result
1003 :
1004 0 : } else if (this->A42Model.WarmUpByEngineTemp) {
1005 : // Stirling engine mode warm up
1006 : // find MdotFuelMax
1007 0 : Real64 Pmax = this->A42Model.MaxElecPower;
1008 0 : Pstandby = 0.0;
1009 0 : Pcooler = this->A42Model.PcoolDown * PLRforSubtimestepShutDown; // could be here with part load in cool down
1010 0 : TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
1011 0 : MdotCW = state.dataLoopNodes->Node(this->PlantInletNodeID).MassFlowRate; // kg/s
1012 0 : ElecEff = Curve::CurveValue(state, this->A42Model.ElecEffCurveID, Pmax, MdotCW, TcwIn);
1013 0 : ElecEff = max(0.0, ElecEff); // protect against bad curve result
1014 0 : if (ElecEff > 0.0) { // trap divide by bad thing
1015 0 : Qgross = Pmax / ElecEff; // W
1016 : } else {
1017 0 : Qgross = 0.0;
1018 : }
1019 0 : NdotFuel = Qgross / (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
1020 : // kMol/s = (J/s) /(KJ/mol * 1000 J/KJ * 1000 mol/kmol)
1021 0 : Real64 MdotFuelMax = NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1022 :
1023 : Real64 MdotFuelWarmup;
1024 0 : if (Teng > thisAmbientTemp) {
1025 0 : MdotFuelWarmup =
1026 0 : MdotFuelMax + this->A42Model.kf * MdotFuelMax * ((this->A42Model.TnomEngOp - thisAmbientTemp) / (Teng - thisAmbientTemp));
1027 : // check that numerical answer didn't blow up beyond limit, and reset if it did
1028 0 : if (MdotFuelWarmup > this->A42Model.Rfuelwarmup * MdotFuelMax) {
1029 0 : MdotFuelWarmup = this->A42Model.Rfuelwarmup * MdotFuelMax;
1030 : }
1031 : } else { // equal would divide by zero
1032 0 : MdotFuelWarmup = this->A42Model.Rfuelwarmup * MdotFuelMax;
1033 : }
1034 :
1035 0 : if (this->A42Model.TnomEngOp > thisAmbientTemp) {
1036 0 : Pnetss = Pmax * this->A42Model.kp * ((Teng - thisAmbientTemp) / (this->A42Model.TnomEngOp - thisAmbientTemp));
1037 : } else { // equal would divide by zero
1038 0 : Pnetss = Pmax;
1039 : }
1040 :
1041 0 : MdotFuel = MdotFuelWarmup;
1042 0 : NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1043 0 : MdotAir = Curve::CurveValue(state, this->A42Model.AirFlowCurveID, MdotFuelWarmup);
1044 0 : MdotAir = max(0.0, MdotAir); // protect against bad curve result
1045 0 : Qgross = NdotFuel * (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
1046 0 : ThermEff = Curve::CurveValue(state, this->A42Model.ThermalEffCurveID, Pmax, MdotCW, TcwIn);
1047 0 : Qgenss = ThermEff * Qgross; // W
1048 : }
1049 0 : NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1050 0 : } break;
1051 4335 : case DataGenerators::OperatingMode::Normal: {
1052 4335 : if (PLRforSubtimestepStartUp < 1.0) {
1053 1223 : if (RunFlagElectCenter) Pnetss = MyElectricLoad; // W
1054 1223 : if (RunFlagPlant) Pnetss = AllowedLoad;
1055 : } else {
1056 3112 : Pnetss = AllowedLoad;
1057 : }
1058 4335 : Pstandby = 0.0;
1059 4335 : Pcooler = 0.0;
1060 4335 : TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
1061 4335 : MdotCW = state.dataLoopNodes->Node(this->PlantInletNodeID).MassFlowRate; // kg/s
1062 4335 : if (this->A42Model.InternalFlowControl) {
1063 3831 : MdotCW = GeneratorDynamicsManager::FuncDetermineCWMdotForInternalFlowControl(state, this->DynamicsControlID, Pnetss, TcwIn);
1064 : }
1065 :
1066 4335 : ElecEff = Curve::CurveValue(state, this->A42Model.ElecEffCurveID, Pnetss, MdotCW, TcwIn);
1067 4335 : ElecEff = max(0.0, ElecEff); // protect against bad curve result
1068 :
1069 4335 : if (ElecEff > 0.0) { // trap divide by bad thing
1070 4335 : Qgross = Pnetss / ElecEff; // W
1071 : } else {
1072 0 : Qgross = 0.0;
1073 : }
1074 :
1075 4335 : ThermEff = Curve::CurveValue(state, this->A42Model.ThermalEffCurveID, Pnetss, MdotCW, TcwIn);
1076 4335 : ThermEff = max(0.0, ThermEff); // protect against bad curve result
1077 4335 : Qgenss = ThermEff * Qgross; // W
1078 8670 : MdotFuel = Qgross / (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0) *
1079 4335 : state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1080 : // kMol/s = (J/s) /(KJ/mol * 1000 J/KJ * 1000 mol/kmol)
1081 :
1082 4335 : bool ConstrainedIncreasingNdot(false);
1083 4335 : bool ConstrainedDecreasingNdot(false);
1084 4335 : Real64 MdotFuelAllowed = 0.0;
1085 :
1086 4335 : GeneratorDynamicsManager::ManageGeneratorFuelFlow(state,
1087 : GeneratorType::MicroCHP,
1088 : this->Name,
1089 : this->DynamicsControlID,
1090 : RunFlag,
1091 : MdotFuel,
1092 : MdotFuelAllowed,
1093 : ConstrainedIncreasingNdot,
1094 : ConstrainedDecreasingNdot);
1095 :
1096 4335 : if (ConstrainedIncreasingNdot || ConstrainedDecreasingNdot) { // recalculate Pnetss with new NdotFuel with iteration
1097 0 : MdotFuel = MdotFuelAllowed;
1098 0 : NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1099 0 : Qgross = NdotFuel * (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
1100 :
1101 0 : for (int i = 1; i <= 20; ++i) { // iterating here, could add use of seach method error signal
1102 0 : Pnetss = Qgross * ElecEff;
1103 0 : if (this->A42Model.InternalFlowControl) {
1104 0 : MdotCW = GeneratorDynamicsManager::FuncDetermineCWMdotForInternalFlowControl(state, this->DynamicsControlID, Pnetss, TcwIn);
1105 : }
1106 0 : ElecEff = Curve::CurveValue(state, this->A42Model.ElecEffCurveID, Pnetss, MdotCW, TcwIn);
1107 0 : ElecEff = max(0.0, ElecEff); // protect against bad curve result
1108 : }
1109 :
1110 0 : ThermEff = Curve::CurveValue(state, this->A42Model.ThermalEffCurveID, Pnetss, MdotCW, TcwIn);
1111 0 : ThermEff = max(0.0, ThermEff); // protect against bad curve result
1112 0 : Qgenss = ThermEff * Qgross; // W
1113 : }
1114 :
1115 4335 : NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1116 4335 : MdotAir = Curve::CurveValue(state, this->A42Model.AirFlowCurveID, MdotFuel);
1117 4335 : MdotAir = max(0.0, MdotAir); // protect against bad curve result
1118 4335 : if (PLRforSubtimestepStartUp < 1.0) {
1119 1223 : Pnetss = AllowedLoad;
1120 4335 : }
1121 4335 : } break;
1122 0 : case DataGenerators::OperatingMode::CoolDown: {
1123 0 : Pnetss = 0.0;
1124 0 : Pstandby = 0.0;
1125 0 : Pcooler = this->A42Model.PcoolDown;
1126 0 : TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
1127 0 : MdotCW = state.dataLoopNodes->Node(this->PlantInletNodeID).MassFlowRate; // kg/s
1128 0 : if (this->A42Model.InternalFlowControl) {
1129 0 : MdotCW = GeneratorDynamicsManager::FuncDetermineCWMdotForInternalFlowControl(state, this->DynamicsControlID, Pnetss, TcwIn);
1130 : }
1131 0 : NdotFuel = 0.0;
1132 0 : MdotFuel = 0.0;
1133 0 : MdotAir = 0.0;
1134 0 : ElecEff = 0.0;
1135 0 : ThermEff = 0.0;
1136 0 : Qgross = 0.0;
1137 0 : Qgenss = 0.0;
1138 0 : } break;
1139 0 : default:
1140 0 : break;
1141 : }
1142 :
1143 69660 : for (int i = 1; i <= 20; ++i) { // sequential search with exit criteria
1144 : // calculate new value for engine temperature
1145 : // for Stirling in warmup, need to include dependency of Qgness on Teng
1146 69660 : if ((this->A42Model.WarmUpByEngineTemp) && (CurrentOpMode == DataGenerators::OperatingMode::WarmUp)) {
1147 :
1148 0 : Real64 Pmax = this->A42Model.MaxElecPower;
1149 0 : TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
1150 0 : MdotCW = state.dataLoopNodes->Node(this->PlantInletNodeID).MassFlowRate; // kg/s
1151 0 : ElecEff = Curve::CurveValue(state, this->A42Model.ElecEffCurveID, Pmax, MdotCW, TcwIn);
1152 0 : ElecEff = max(0.0, ElecEff); // protect against bad curve result
1153 0 : if (ElecEff > 0.0) { // trap divide by bad thing
1154 0 : Qgross = Pmax / ElecEff; // W
1155 : } else {
1156 0 : Qgross = 0.0;
1157 : }
1158 0 : NdotFuel = Qgross / (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
1159 : // kMol/s = (J/s) /(KJ/mol * 1000 J/KJ * 1000 mol/kmol)
1160 0 : Real64 MdotFuelMax = NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1161 :
1162 : Real64 MdotFuelWarmup;
1163 0 : if (Teng > thisAmbientTemp) {
1164 0 : MdotFuelWarmup =
1165 0 : MdotFuelMax + this->A42Model.kf * MdotFuelMax * ((this->A42Model.TnomEngOp - thisAmbientTemp) / (Teng - thisAmbientTemp));
1166 :
1167 : // check that numerical answer didn't blow up beyond limit, and reset if it did
1168 0 : if (MdotFuelWarmup > this->A42Model.Rfuelwarmup * MdotFuelMax) {
1169 0 : MdotFuelWarmup = this->A42Model.Rfuelwarmup * MdotFuelMax;
1170 : }
1171 0 : if (this->A42Model.TnomEngOp > thisAmbientTemp) {
1172 0 : Pnetss = Pmax * this->A42Model.kp * ((Teng - thisAmbientTemp) / (this->A42Model.TnomEngOp - thisAmbientTemp));
1173 : } else { // equal would divide by zero
1174 0 : Pnetss = Pmax;
1175 : }
1176 : } else { // equal would divide by zero
1177 0 : MdotFuelWarmup = this->A42Model.Rfuelwarmup * MdotFuelMax;
1178 : }
1179 0 : MdotFuel = MdotFuelWarmup;
1180 0 : NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1181 0 : MdotAir = Curve::CurveValue(state, this->A42Model.AirFlowCurveID, MdotFuelWarmup);
1182 0 : MdotAir = max(0.0, MdotAir); // protect against bad curve result
1183 0 : Qgross = NdotFuel * (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
1184 0 : ThermEff = Curve::CurveValue(state, this->A42Model.ThermalEffCurveID, Pmax, MdotCW, TcwIn);
1185 0 : ThermEff = max(0.0, ThermEff); // protect against bad curve result
1186 0 : Qgenss = ThermEff * Qgross; // W
1187 : }
1188 :
1189 69660 : Real64 dt = state.dataHVACGlobal->TimeStepSys * DataGlobalConstants::SecInHour;
1190 :
1191 69660 : Teng = FuncDetermineEngineTemp(
1192 : TcwOut, this->A42Model.MCeng, this->A42Model.UAhx, this->A42Model.UAskin, thisAmbientTemp, Qgenss, this->A42Model.TengLast, dt);
1193 :
1194 139320 : Real64 Cp = FluidProperties::GetSpecificHeatGlycol(state,
1195 69660 : state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).FluidName,
1196 : TcwIn,
1197 69660 : state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).FluidIndex,
1198 69660 : RoutineName);
1199 :
1200 69660 : TcwOut =
1201 69660 : FuncDetermineCoolantWaterExitTemp(TcwIn, this->A42Model.MCcw, this->A42Model.UAhx, MdotCW * Cp, Teng, this->A42Model.TempCWOutLast, dt);
1202 :
1203 : // form balance and exit once met.
1204 69660 : bool EnergyBalOK = CheckMicroCHPThermalBalance(this->A42Model.MaxElecPower,
1205 : TcwIn,
1206 : TcwOut,
1207 : Teng,
1208 : thisAmbientTemp,
1209 : this->A42Model.UAhx,
1210 : this->A42Model.UAskin,
1211 : Qgenss,
1212 : this->A42Model.MCeng,
1213 : this->A42Model.MCcw,
1214 69660 : MdotCW * Cp);
1215 :
1216 69660 : if (EnergyBalOK && (i > 4)) break;
1217 : }
1218 :
1219 13932 : this->PlantMassFlowRate = MdotCW;
1220 13932 : this->A42Model.Pnet = Pnetss - Pcooler - Pstandby;
1221 13932 : this->A42Model.ElecEff = ElecEff;
1222 13932 : this->A42Model.Qgross = Qgross;
1223 13932 : this->A42Model.ThermEff = ThermEff;
1224 13932 : this->A42Model.Qgenss = Qgenss;
1225 13932 : this->A42Model.NdotFuel = NdotFuel;
1226 13932 : this->A42Model.MdotFuel = MdotFuel;
1227 13932 : this->A42Model.Teng = Teng;
1228 13932 : this->A42Model.TcwOut = TcwOut;
1229 13932 : this->A42Model.TcwIn = TcwIn;
1230 13932 : this->A42Model.MdotAir = MdotAir;
1231 13932 : this->A42Model.QdotSkin = this->A42Model.UAskin * (Teng - thisAmbientTemp);
1232 :
1233 13932 : this->A42Model.OpMode = CurrentOpMode;
1234 13932 : }
1235 :
1236 69660 : Real64 FuncDetermineEngineTemp(Real64 const TcwOut, // hot water leaving temp
1237 : Real64 const MCeng, // Fictitious mass and heat capacity of engine
1238 : Real64 const UAHX, // Heat exchanger UA
1239 : Real64 const UAskin, // Skin losses UA
1240 : Real64 const Troom, // surrounding zone temperature C
1241 : Real64 const Qgenss, // steady state generator heat generation
1242 : Real64 const TengLast, // engine temp at previous time step
1243 : Real64 const time // elapsed time since previous evaluation
1244 : )
1245 : {
1246 :
1247 : // FUNCTION INFORMATION:
1248 : // AUTHOR B. Griffith
1249 : // DATE WRITTEN Feb. 2007
1250 : // MODIFIED na
1251 : // RE-ENGINEERED na
1252 :
1253 : // PURPOSE OF THIS FUNCTION:
1254 : // Calculate engine temperaure,
1255 :
1256 : // METHODOLOGY EMPLOYED:
1257 : // model is dynamic in that previous condition affects current timestep
1258 : // solve ode for engine temp using analytical solution
1259 :
1260 69660 : Real64 a = ((UAHX * TcwOut / MCeng) + (UAskin * Troom / MCeng) + (Qgenss / MCeng));
1261 69660 : Real64 b = ((-1.0 * UAHX / MCeng) + (-1.0 * UAskin / MCeng));
1262 :
1263 69660 : return (TengLast + a / b) * std::exp(b * time) - a / b;
1264 : }
1265 :
1266 69660 : Real64 FuncDetermineCoolantWaterExitTemp(Real64 const TcwIn, // hot water inlet temp
1267 : Real64 const MCcw, // Fictitious mass and heat capacity of coolant hx
1268 : Real64 const UAHX, // Heat exchanger UA
1269 : Real64 const MdotCpcw, // mass flow and specific heat of coolant water
1270 : Real64 const Teng, // engine mass temperature C
1271 : Real64 const TcwoutLast, // coolant water leaving temp at previous time step
1272 : Real64 const time // elapsed time since previous evaluation
1273 : )
1274 : {
1275 :
1276 : // FUNCTION INFORMATION:
1277 : // AUTHOR B. Griffith
1278 : // DATE WRITTEN Feb. 2007
1279 : // MODIFIED na
1280 : // RE-ENGINEERED na
1281 :
1282 : // PURPOSE OF THIS FUNCTION:
1283 : // Calculate coolan water leaving temperaure,
1284 :
1285 : // METHODOLOGY EMPLOYED:
1286 : // model is dynamic in that previous condition affects current timestep
1287 : // solve ode for coolant water outlet temp using analytical solution
1288 :
1289 69660 : Real64 a = (MdotCpcw * TcwIn / MCcw) + (UAHX * Teng / MCcw);
1290 69660 : Real64 b = ((-1.0 * MdotCpcw / MCcw) + (-1.0 * UAHX / MCcw));
1291 :
1292 69660 : if (b * time < (-1.0 * DataGlobalConstants::MaxEXPArg)) {
1293 19170 : return -a / b;
1294 : } else {
1295 50490 : return (TcwoutLast + a / b) * std::exp(b * time) - a / b;
1296 : }
1297 : }
1298 :
1299 69660 : bool CheckMicroCHPThermalBalance(Real64 const NomHeatGen, // nominal heat generation rate for scaling
1300 : Real64 const TcwIn, // hot water inlet temp
1301 : Real64 const TcwOut, // hot water leaving temp
1302 : Real64 const Teng, // engine mass temperature C
1303 : Real64 const Troom, // surrounding zone temperature C
1304 : Real64 const UAHX, // Heat exchanger UA
1305 : Real64 const UAskin, // Skin losses UA
1306 : Real64 const Qgenss, // steady state generator heat generation
1307 : Real64 const MCeng, // Fictitious mass and heat capacity of engine
1308 : Real64 const MCcw, // Fictitious mass and heat capacity of coolant hx
1309 : Real64 const MdotCpcw // mass flow and specific heat of coolant water
1310 : )
1311 : {
1312 :
1313 : // FUNCTION INFORMATION:
1314 : // AUTHOR B. Griffith
1315 : // DATE WRITTEN Feb. 2007
1316 : // MODIFIED na
1317 : // RE-ENGINEERED na
1318 :
1319 : // PURPOSE OF THIS FUNCTION:
1320 : // Check for energy balance to test if can exit iteration loop
1321 :
1322 : // METHODOLOGY EMPLOYED:
1323 : // put all terms of dynamic energy balances on RHS and compute magnitude of imbalance
1324 : // compare imbalance to scalable thresholds and make a boolean conclusion.
1325 :
1326 : // first compute derivatives using a + bT
1327 : // derivative of engine temp wrt time
1328 69660 : Real64 a = ((UAHX * TcwOut / MCeng) + (UAskin * Troom / MCeng) + (Qgenss / MCeng));
1329 69660 : Real64 b = ((-1.0 * UAHX / MCeng) + (-1.0 * UAskin / MCeng));
1330 69660 : Real64 DTengDTime = a + b * Teng;
1331 :
1332 : // derivative of coolant exit temp wrt time
1333 69660 : Real64 c = (MdotCpcw * TcwIn / MCcw) + (UAHX * Teng / MCcw);
1334 69660 : Real64 d = ((-1.0 * MdotCpcw / MCcw) + (-1.0 * UAHX / MCcw));
1335 69660 : Real64 DCoolOutTDtime = c + d * TcwOut;
1336 :
1337 : // energy imbalance for engine control volume
1338 69660 : Real64 magImbalEng = UAHX * (TcwOut - Teng) + UAskin * (Troom - Teng) + Qgenss - MCeng * DTengDTime;
1339 :
1340 : // energy imbalance for coolant control volume
1341 69660 : Real64 magImbalCooling = MdotCpcw * (TcwIn - TcwOut) + UAHX * (Teng - TcwOut) - MCcw * DCoolOutTDtime;
1342 :
1343 : // criteria for when to call energy balance okay
1344 69660 : Real64 threshold = NomHeatGen / 10000000.0;
1345 :
1346 69660 : return (threshold > magImbalEng) && (threshold > magImbalCooling);
1347 : }
1348 :
1349 2568509 : void FigureMicroCHPZoneGains(EnergyPlusData &state)
1350 : {
1351 :
1352 : // SUBROUTINE INFORMATION:
1353 : // AUTHOR B. Griffith
1354 : // DATE WRITTEN July 2006
1355 : // MODIFIED na
1356 : // RE-ENGINEERED na
1357 :
1358 : // PURPOSE OF THIS SUBROUTINE:
1359 : // Couple equipment skin losses to the Zone Heat Balance
1360 :
1361 : // METHODOLOGY EMPLOYED:
1362 : // This routine adds up the various skin losses and then
1363 : // sets the values in the ZoneIntGain structure
1364 :
1365 2568509 : if (state.dataCHPElectGen->NumMicroCHPs == 0) return;
1366 :
1367 6502 : if (state.dataGlobal->BeginEnvrnFlag && state.dataCHPElectGen->MyEnvrnFlag) {
1368 32 : for (auto &e : state.dataGenerator->FuelSupply)
1369 20 : e.QskinLoss = 0.0;
1370 24 : for (auto &e : state.dataCHPElectGen->MicroCHP) {
1371 12 : e.A42Model.QdotSkin = 0.0;
1372 12 : e.A42Model.SkinLossConvect = 0.0;
1373 12 : e.A42Model.SkinLossRadiat = 0.0;
1374 : }
1375 12 : state.dataCHPElectGen->MyEnvrnFlag = false;
1376 : }
1377 :
1378 6502 : if (!state.dataGlobal->BeginEnvrnFlag) state.dataCHPElectGen->MyEnvrnFlag = true;
1379 :
1380 13004 : for (int CHPnum = 1; CHPnum <= state.dataCHPElectGen->NumMicroCHPs; ++CHPnum) {
1381 6502 : Real64 TotalZoneHeatGain = state.dataGenerator->FuelSupply(state.dataCHPElectGen->MicroCHP(CHPnum).FuelSupplyID).QskinLoss +
1382 6502 : state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.QdotSkin;
1383 :
1384 6502 : state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.QdotConvZone =
1385 6502 : TotalZoneHeatGain * (1 - state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.RadiativeFraction);
1386 6502 : state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.SkinLossConvect = state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.QdotConvZone;
1387 6502 : state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.QdotRadZone =
1388 6502 : TotalZoneHeatGain * state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.RadiativeFraction;
1389 6502 : state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.SkinLossRadiat = state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.QdotRadZone;
1390 : }
1391 : }
1392 :
1393 13932 : void MicroCHPDataStruct::CalcUpdateHeatRecovery(EnergyPlusData &state) const
1394 : {
1395 :
1396 : // SUBROUTINE INFORMATION:
1397 : // AUTHOR B Griffith
1398 : // DATE WRITTEN Aug 2006
1399 : // MODIFIED na
1400 : // RE-ENGINEERED na
1401 :
1402 : // PURPOSE OF THIS SUBROUTINE:
1403 : // update plant loop interactions, do any calcs needed
1404 :
1405 : static constexpr std::string_view RoutineName("CalcUpdateHeatRecovery");
1406 :
1407 13932 : PlantUtilities::SafeCopyPlantNode(state, this->PlantInletNodeID, this->PlantOutletNodeID);
1408 :
1409 13932 : state.dataLoopNodes->Node(this->PlantOutletNodeID).Temp = this->A42Model.TcwOut;
1410 :
1411 27864 : Real64 Cp = FluidProperties::GetSpecificHeatGlycol(state,
1412 13932 : state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).FluidName,
1413 13932 : this->A42Model.TcwIn,
1414 13932 : state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).FluidIndex,
1415 13932 : RoutineName);
1416 :
1417 13932 : state.dataLoopNodes->Node(this->PlantOutletNodeID).Enthalpy = this->A42Model.TcwOut * Cp;
1418 13932 : }
1419 :
1420 10 : void MicroCHPDataStruct::getDesignCapacities(
1421 : [[maybe_unused]] EnergyPlusData &state, const EnergyPlus::PlantLocation &, Real64 &MaxLoad, Real64 &MinLoad, Real64 &OptLoad)
1422 : {
1423 10 : MaxLoad = state.dataGenerator->GeneratorDynamics(this->DynamicsControlID).QdotHXMax;
1424 10 : MinLoad = state.dataGenerator->GeneratorDynamics(this->DynamicsControlID).QdotHXMin;
1425 10 : OptLoad = state.dataGenerator->GeneratorDynamics(this->DynamicsControlID).QdotHXOpt;
1426 10 : }
1427 :
1428 13932 : void MicroCHPDataStruct::UpdateMicroCHPGeneratorRecords(EnergyPlusData &state) // Generator number
1429 : {
1430 :
1431 : // SUBROUTINE INFORMATION:
1432 : // AUTHOR B. Griffith
1433 : // DATE WRITTEN July 2006
1434 : // MODIFIED na
1435 : // RE-ENGINEERED na
1436 :
1437 : // PURPOSE OF THIS SUBROUTINE:
1438 : // update variables in structures linked to output reports
1439 :
1440 : static constexpr std::string_view RoutineName("UpdateMicroCHPGeneratorRecords");
1441 :
1442 13932 : this->A42Model.ACPowerGen = this->A42Model.Pnet; // electrical power produced [W]
1443 13932 : this->A42Model.ACEnergyGen = this->A42Model.Pnet * state.dataHVACGlobal->TimeStepSys * DataGlobalConstants::SecInHour; // energy produced (J)
1444 13932 : this->A42Model.QdotHX = this->A42Model.UAhx * (this->A42Model.Teng - this->A42Model.TcwOut); // heat recovered rate (W)
1445 :
1446 27864 : Real64 Cp = FluidProperties::GetSpecificHeatGlycol(state,
1447 13932 : state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).FluidName,
1448 : this->A42Model.TcwIn,
1449 13932 : state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).FluidIndex,
1450 13932 : RoutineName);
1451 :
1452 13932 : this->A42Model.QdotHR = this->PlantMassFlowRate * Cp * (this->A42Model.TcwOut - this->A42Model.TcwIn);
1453 13932 : this->A42Model.TotalHeatEnergyRec =
1454 13932 : this->A42Model.QdotHR * state.dataHVACGlobal->TimeStepSys * DataGlobalConstants::SecInHour; // heat recovered energy (J)
1455 :
1456 13932 : this->A42Model.HeatRecInletTemp = this->A42Model.TcwIn; // Heat Recovery Loop Inlet Temperature (C)
1457 13932 : this->A42Model.HeatRecOutletTemp = this->A42Model.TcwOut; // Heat Recovery Loop Outlet Temperature (C)
1458 :
1459 13932 : this->A42Model.FuelCompressPower = state.dataGenerator->FuelSupply(this->FuelSupplyID).PfuelCompEl;
1460 : // electrical power used by fuel supply compressor [W]
1461 13932 : this->A42Model.FuelCompressEnergy = state.dataGenerator->FuelSupply(this->FuelSupplyID).PfuelCompEl * state.dataHVACGlobal->TimeStepSys *
1462 : DataGlobalConstants::SecInHour; // elect energy
1463 13932 : this->A42Model.FuelCompressSkinLoss = state.dataGenerator->FuelSupply(this->FuelSupplyID).QskinLoss;
1464 : // heat rate of losses.by fuel supply compressor [W]
1465 41796 : this->A42Model.FuelEnergyHHV = this->A42Model.NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).HHV *
1466 27864 : state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec * state.dataHVACGlobal->TimeStepSys *
1467 : DataGlobalConstants::SecInHour;
1468 : // reporting: Fuel Energy used (W)
1469 27864 : this->A42Model.FuelEnergyUseRateHHV = this->A42Model.NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).HHV *
1470 13932 : state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
1471 : // reporting: Fuel Energy used (J)
1472 41796 : this->A42Model.FuelEnergyLHV = this->A42Model.NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000000.0 *
1473 27864 : state.dataHVACGlobal->TimeStepSys * DataGlobalConstants::SecInHour;
1474 : // reporting: Fuel Energy used (W)
1475 13932 : this->A42Model.FuelEnergyUseRateLHV = this->A42Model.NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000000.0;
1476 :
1477 13932 : this->A42Model.SkinLossPower = this->A42Model.QdotConvZone + this->A42Model.QdotRadZone;
1478 13932 : this->A42Model.SkinLossEnergy =
1479 13932 : (this->A42Model.QdotConvZone + this->A42Model.QdotRadZone) * state.dataHVACGlobal->TimeStepSys * DataGlobalConstants::SecInHour;
1480 13932 : this->A42Model.SkinLossConvect = this->A42Model.QdotConvZone;
1481 13932 : this->A42Model.SkinLossRadiat = this->A42Model.QdotRadZone;
1482 :
1483 : // update node data for air inlet (and outlet)
1484 13932 : if (this->AirInletNodeID > 0) {
1485 13932 : state.dataLoopNodes->Node(this->AirInletNodeID).MassFlowRate = this->A42Model.MdotAir;
1486 : }
1487 13932 : if (this->AirOutletNodeID > 0) {
1488 0 : state.dataLoopNodes->Node(this->AirOutletNodeID).MassFlowRate = this->A42Model.MdotAir;
1489 0 : state.dataLoopNodes->Node(this->AirOutletNodeID).Temp = this->A42Model.Teng;
1490 : }
1491 13932 : }
1492 16022 : void MicroCHPDataStruct::oneTimeInit(EnergyPlusData &state)
1493 : {
1494 :
1495 : bool errFlag;
1496 :
1497 16022 : if (this->myFlag) {
1498 2 : this->setupOutputVars(state);
1499 2 : this->myFlag = false;
1500 : }
1501 :
1502 16022 : if (this->MyPlantScanFlag) {
1503 2 : if (allocated(state.dataPlnt->PlantLoop)) {
1504 2 : errFlag = false;
1505 2 : PlantUtilities::ScanPlantLoopsForObject(
1506 : state, this->Name, DataPlant::PlantEquipmentType::Generator_MicroCHP, this->CWPlantLoc, errFlag, _, _, _, _, _);
1507 :
1508 2 : if (errFlag) {
1509 0 : ShowFatalError(state, "InitMicroCHPNoNormalizeGenerators: Program terminated for previous conditions.");
1510 : }
1511 :
1512 2 : if (!this->A42Model.InternalFlowControl) {
1513 : // IF this is on the supply side and not internal flow control then reset flow priority to lower
1514 1 : if (this->CWPlantLoc.loopSideNum == DataPlant::LoopSideLocation::Supply) {
1515 1 : DataPlant::CompData::getPlantComponent(state, this->CWPlantLoc).FlowPriority = DataPlant::LoopFlowStatus::TakesWhatGets;
1516 : }
1517 : }
1518 :
1519 2 : this->MyPlantScanFlag = false;
1520 : }
1521 : }
1522 16022 : }
1523 2313 : } // namespace EnergyPlus::MicroCHPElectricGenerator
|