LCOV - code coverage report
Current view: top level - EnergyPlus - MicroCHPElectricGenerator.cc (source / functions) Coverage Total Hit
Test: lcov.output.filtered Lines: 37.3 % 687 256
Test Date: 2025-05-22 16:09:37 Functions: 60.0 % 15 9

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

Generated by: LCOV version 2.0-1