LCOV - code coverage report
Current view: top level - EnergyPlus - MicroCHPElectricGenerator.cc (source / functions) Coverage Total Hit
Test: lcov.output.filtered Lines: 73.1 % 695 508
Test Date: 2025-06-02 07:23:51 Functions: 100.0 % 15 15

            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        17417 : PlantComponent *MicroCHPDataStruct::factory(EnergyPlusData &state, std::string const &objectName)
     106              : {
     107              :     // Process the input data
     108        17417 :     if (state.dataCHPElectGen->getMicroCHPInputFlag) {
     109            2 :         GetMicroCHPGeneratorInput(state);
     110            2 :         state.dataCHPElectGen->getMicroCHPInputFlag = false;
     111              :     }
     112              : 
     113              :     // Now look for this object
     114        17417 :     for (auto &thisMCHP : state.dataCHPElectGen->MicroCHP) {
     115        17417 :         if (thisMCHP.Name == objectName) {
     116        17417 :             return &thisMCHP;
     117              :         }
     118        34834 :     }
     119              :     // If we didn't find it, fatal
     120              :     ShowFatalError(state, format("LocalMicroCHPGenFactory: Error getting inputs for micro-CHP gen named: {}", objectName)); // LCOV_EXCL_LINE
     121              :     // Shut up the compiler
     122              :     return nullptr; // LCOV_EXCL_LINE
     123              : }
     124              : 
     125            2 : void GetMicroCHPGeneratorInput(EnergyPlusData &state)
     126              : {
     127              :     // SUBROUTINE INFORMATION:
     128              :     //       AUTHOR:          Brent Griffith
     129              :     //       DATE WRITTEN:    July 2005
     130              : 
     131              :     // PURPOSE OF THIS SUBROUTINE:
     132              :     // This routine will get the input
     133              :     // required by the Micro CHP Generator models.
     134              : 
     135              :     // METHODOLOGY EMPLOYED:
     136              :     // EnergyPlus input processor
     137              :     static constexpr std::string_view routineName = "GetMicroCHPGeneratorInput";
     138              : 
     139            2 :     Array1D_string AlphArray(25);  // character string data
     140            2 :     Array1D<Real64> NumArray(200); // numeric data TODO deal with allocatable for extensible
     141              : 
     142            2 :     auto &s_ipsc = state.dataIPShortCut;
     143              : 
     144            2 :     if (state.dataCHPElectGen->MyOneTimeFlag) {
     145            2 :         int NumAlphas = 0;        // Number of elements in the alpha array
     146            2 :         int NumNums = 0;          // Number of elements in the numeric array
     147            2 :         int IOStat = 0;           // IO Status when calling get input subroutine
     148            2 :         bool ErrorsFound = false; // error flag
     149              : 
     150              :         // call to Fuel supply module to set up data there.
     151            2 :         GeneratorFuelSupply::GetGeneratorFuelSupplyInput(state);
     152              : 
     153              :         // First get the Micro CHP Parameters so they can be nested in structure later
     154            2 :         s_ipsc->cCurrentModuleObject = "Generator:MicroCHP:NonNormalizedParameters";
     155            2 :         state.dataCHPElectGen->NumMicroCHPParams = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, s_ipsc->cCurrentModuleObject);
     156              : 
     157            2 :         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            2 :         state.dataCHPElectGen->MicroCHPParamInput.allocate(state.dataCHPElectGen->NumMicroCHPParams);
     163              : 
     164            4 :         for (int CHPParamNum = 1; CHPParamNum <= state.dataCHPElectGen->NumMicroCHPParams; ++CHPParamNum) {
     165            6 :             state.dataInputProcessing->inputProcessor->getObjectItem(state,
     166            2 :                                                                      s_ipsc->cCurrentModuleObject,
     167              :                                                                      CHPParamNum,
     168              :                                                                      AlphArray,
     169              :                                                                      NumAlphas,
     170              :                                                                      NumArray,
     171              :                                                                      NumNums,
     172              :                                                                      IOStat,
     173              :                                                                      _,
     174            2 :                                                                      s_ipsc->lAlphaFieldBlanks,
     175            2 :                                                                      s_ipsc->cAlphaFieldNames,
     176            2 :                                                                      s_ipsc->cNumericFieldNames);
     177              : 
     178            2 :             ErrorObjectHeader eoh{routineName, s_ipsc->cCurrentModuleObject, AlphArray(1)};
     179              : 
     180            2 :             auto &microCHPParams = state.dataCHPElectGen->MicroCHPParamInput(CHPParamNum);
     181              : 
     182            2 :             std::string ObjMSGName = s_ipsc->cCurrentModuleObject + " Named " + AlphArray(1);
     183              : 
     184            2 :             microCHPParams.Name = AlphArray(1);        // A1 name
     185            2 :             microCHPParams.MaxElecPower = NumArray(1); // N1 Maximum Electric Power [W]
     186            2 :             microCHPParams.MinElecPower = NumArray(2); // N2 Minimum Electric Power [W]
     187            2 :             microCHPParams.MinWaterMdot = NumArray(3); // N3 Minimum Cooling Water Flow Rate [kg/s]
     188            2 :             microCHPParams.MaxWaterTemp = NumArray(4); // N3 Maximum Cooling Water Inlet Temp [C]
     189              : 
     190            2 :             if (s_ipsc->lAlphaFieldBlanks(2)) {
     191            0 :                 ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(2));
     192            0 :                 ErrorsFound = true;
     193            2 :             } 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            2 :             if (s_ipsc->lAlphaFieldBlanks(3)) {
     199            0 :                 ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(3));
     200            0 :                 ErrorsFound = true;
     201            2 :             } 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            2 :             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            2 :             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            2 :             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            2 :             if (s_ipsc->lAlphaFieldBlanks(6)) {
     226            0 :                 ShowSevereEmptyField(state, eoh, s_ipsc->cAlphaFieldNames(6));
     227            0 :                 ErrorsFound = true;
     228            2 :             } 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            2 :             microCHPParams.DeltaPelMax = NumArray(5);       // N5 Maximum rate of change in net electrical power [W/s]
     235            2 :             microCHPParams.DeltaFuelMdotMax = NumArray(6);  // N6 Maximum Rate of change in fuel flow rate [kg/s2]
     236            2 :             microCHPParams.UAhx = NumArray(7);              // N7 Heat Exchanger UA_hx
     237            2 :             microCHPParams.UAskin = NumArray(8);            // N8 Skin Loss UA_loss
     238            2 :             microCHPParams.RadiativeFraction = NumArray(9); // N9 radiative fraction for skin losses
     239            2 :             microCHPParams.MCeng = NumArray(10);            // N10 Aggregated Thermal Mass of Generator MC_eng
     240            2 :             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            2 :             microCHPParams.MCcw = NumArray(11); // Aggregated Thermal Mass of Heat Recovery MC_cw
     247            2 :             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            2 :             microCHPParams.Pstandby = NumArray(12); // N12 Standby Power [W]
     254              : 
     255            2 :             if (Util::SameString(AlphArray(7), "TimeDelay")) {
     256            2 :                 microCHPParams.WarmUpByTimeDelay = true;
     257            2 :                 microCHPParams.WarmUpByEngineTemp = false;
     258              :             }
     259            2 :             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            2 :             microCHPParams.kf = NumArray(13);          // N13 Warmup Fuel Flow Rate Coefficient k_f
     265            2 :             microCHPParams.TnomEngOp = NumArray(14);   // N14 Nominal Engine Operating Temperature [C]
     266            2 :             microCHPParams.kp = NumArray(15);          // N15 Warmup Power Coefficient k_p
     267            2 :             microCHPParams.Rfuelwarmup = NumArray(16); // N16 Warm Up Fuel Flow Rate Limit Ratio
     268            2 :             microCHPParams.WarmUpDelay = NumArray(17); // N17 Warm Up Delay Time
     269              : 
     270            2 :             microCHPParams.PcoolDown = NumArray(18); // N18 Cool Down Power
     271              : 
     272            2 :             microCHPParams.CoolDownDelay = NumArray(19); // N19 Cool Down Delay Time in seconds
     273              : 
     274            2 :             if (Util::SameString(AlphArray(8), "MandatoryCoolDown")) {
     275            1 :                 microCHPParams.MandatoryFullCoolDown = true;
     276            1 :                 microCHPParams.WarmRestartOkay = false;
     277              :             }
     278            2 :             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            2 :         }
     284              : 
     285            2 :         s_ipsc->cCurrentModuleObject = "Generator:MicroCHP";
     286            2 :         state.dataCHPElectGen->NumMicroCHPs = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, s_ipsc->cCurrentModuleObject);
     287              : 
     288            2 :         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            2 :         if (!(allocated(state.dataCHPElectGen->MicroCHP))) {
     295            2 :             state.dataCHPElectGen->MicroCHP.allocate(state.dataCHPElectGen->NumMicroCHPs); // inits handled in derived type definitions
     296              :         }
     297              : 
     298              :         // load in Micro CHPs
     299            4 :         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            2 :                 microCHP.ZoneName = AlphArray(3); //  A3 Zone Name
     332            2 :                 microCHP.ZoneID = Util::FindItemInList(microCHP.ZoneName, state.dataHeatBal->Zone);
     333            2 :                 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            0 :                 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            0 :                 microCHP.availSched = Sched::GetScheduleAlwaysOn(state);
     396            2 :             } 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            2 :         if (ErrorsFound) {
     405            0 :             ShowFatalError(state, format("Errors found in processing input for {}", s_ipsc->cCurrentModuleObject));
     406              :         }
     407              : 
     408              :         // setup report variables
     409            4 :         for (int GeneratorNum = 1; GeneratorNum <= state.dataCHPElectGen->NumMicroCHPs; ++GeneratorNum) {
     410              :         }
     411              : 
     412            2 :         state.dataCHPElectGen->MyOneTimeFlag = false;
     413              :     }
     414            2 : }
     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            2 :         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        85963 : 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        85963 :     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        85963 : }
     710              : 
     711           10 : void MicroCHPDataStruct::onInitLoopEquip(EnergyPlusData &state, const EnergyPlus::PlantLocation &)
     712              : {
     713              :     static constexpr std::string_view RoutineName("MicroCHPDataStruct::onInitLoopEquip");
     714              : 
     715           10 :     Real64 rho = state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum)
     716           10 :                      .glycol->getDensity(state, state.dataLoopNodes->Node(this->PlantInletNodeID).Temp, RoutineName);
     717           10 :     if (this->A42Model.InternalFlowControl) { // got a curve
     718            5 :         this->PlantMassFlowRateMax =
     719            5 :             2.0 * this->A42Model.WaterFlowCurve->value(state, this->A42Model.MaxElecPower, state.dataLoopNodes->Node(this->PlantInletNodeID).Temp);
     720            5 :     } else if (this->CWPlantLoc.loopSideNum == DataPlant::LoopSideLocation::Supply) {
     721            5 :         if (state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).MaxMassFlowRate > 0.0) {
     722            4 :             this->PlantMassFlowRateMax = state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).MaxMassFlowRate;
     723            1 :         } 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            1 :             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           10 :     PlantUtilities::RegisterPlantCompDesignFlow(state, this->PlantInletNodeID, this->PlantMassFlowRateMax / rho);
     734              : 
     735           30 :     this->A42Model.ElecEff = this->A42Model.ElecEffCurve->value(
     736           10 :         state, this->A42Model.MaxElecPower, this->PlantMassFlowRateMax, state.dataLoopNodes->Node(this->PlantInletNodeID).Temp);
     737              : 
     738           30 :     this->A42Model.ThermEff = this->A42Model.ThermalEffCurve->value(
     739           10 :         state, this->A42Model.MaxElecPower, this->PlantMassFlowRateMax, state.dataLoopNodes->Node(this->PlantInletNodeID).Temp);
     740              : 
     741           10 :     GeneratorDynamicsManager::SetupGeneratorControlStateManager(state, this->DynamicsControlID);
     742           10 : }
     743              : 
     744        17415 : 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        17415 :     this->oneTimeInit(state);
     753              : 
     754        17415 :     if (!state.dataGlobal->SysSizingCalc && this->MySizeFlag && !this->MyPlantScanFlag && (state.dataPlnt->PlantFirstSizesOkayToFinalize)) {
     755            2 :         this->MySizeFlag = false;
     756              :     }
     757              : 
     758        17415 :     if (this->MySizeFlag) {
     759         2087 :         return;
     760              :     }
     761              : 
     762        15328 :     int DynaCntrlNum = this->DynamicsControlID;
     763              : 
     764        15328 :     if (state.dataGlobal->BeginEnvrnFlag && this->MyEnvrnFlag) {
     765              :         // reset to starting condition for different environment runperiods, design days
     766          162 :         this->A42Model.TengLast = 20.0;
     767          162 :         this->A42Model.TempCWOutLast = 20.0;
     768          162 :         this->A42Model.TimeElapsed = 0.0;
     769          162 :         this->A42Model.OpMode = DataGenerators::OperatingMode::Invalid;
     770          162 :         this->A42Model.OffModeTime = 0.0;
     771          162 :         this->A42Model.StandyByModeTime = 0.0;
     772          162 :         this->A42Model.WarmUpModeTime = 0.0;
     773          162 :         this->A42Model.NormalModeTime = 0.0;
     774          162 :         this->A42Model.CoolDownModeTime = 0.0;
     775          162 :         this->A42Model.Pnet = 0.0;
     776          162 :         this->A42Model.ElecEff = 0.0;
     777          162 :         this->A42Model.Qgross = 0.0;
     778          162 :         this->A42Model.ThermEff = 0.0;
     779          162 :         this->A42Model.Qgenss = 0.0;
     780          162 :         this->A42Model.NdotFuel = 0.0;
     781          162 :         this->A42Model.MdotFuel = 0.0;
     782          162 :         this->A42Model.Teng = 20.0;
     783          162 :         this->A42Model.TcwIn = 20.0;
     784          162 :         this->A42Model.TcwOut = 20.0;
     785          162 :         this->A42Model.MdotAir = 0.0;
     786          162 :         this->A42Model.QdotSkin = 0.0;
     787          162 :         this->A42Model.QdotConvZone = 0.0;
     788          162 :         this->A42Model.QdotRadZone = 0.0;
     789          162 :         state.dataGenerator->GeneratorDynamics(DynaCntrlNum).LastOpMode = DataGenerators::OperatingMode::Off;
     790          162 :         state.dataGenerator->GeneratorDynamics(DynaCntrlNum).CurrentOpMode = DataGenerators::OperatingMode::Off;
     791          162 :         state.dataGenerator->GeneratorDynamics(DynaCntrlNum).FractionalDayofLastShutDown = 0.0;
     792          162 :         state.dataGenerator->GeneratorDynamics(DynaCntrlNum).FractionalDayofLastStartUp = 0.0;
     793          162 :         state.dataGenerator->GeneratorDynamics(DynaCntrlNum).HasBeenOn = false;
     794          162 :         state.dataGenerator->GeneratorDynamics(DynaCntrlNum).DuringStartUp = false;
     795          162 :         state.dataGenerator->GeneratorDynamics(DynaCntrlNum).DuringShutDown = false;
     796          162 :         state.dataGenerator->GeneratorDynamics(DynaCntrlNum).FuelMdotLastTimestep = 0.0;
     797          162 :         state.dataGenerator->GeneratorDynamics(DynaCntrlNum).PelLastTimeStep = 0.0;
     798          162 :         state.dataGenerator->GeneratorDynamics(DynaCntrlNum).NumCycles = 0;
     799              : 
     800          162 :         state.dataGenerator->FuelSupply(this->FuelSupplyID).QskinLoss = 0.0;
     801              : 
     802          162 :         PlantUtilities::InitComponentNodes(state, 0.0, this->PlantMassFlowRateMax, this->PlantInletNodeID, this->PlantOutletNodeID);
     803              :     }
     804              : 
     805        15328 :     if (!state.dataGlobal->BeginEnvrnFlag) {
     806        15166 :         this->MyEnvrnFlag = true;
     807              :     }
     808              : 
     809              :     Real64 TimeElapsed =
     810        15328 :         state.dataGlobal->HourOfDay + state.dataGlobal->TimeStep * state.dataGlobal->TimeStepZone + state.dataHVACGlobal->SysTimeElapsed;
     811        15328 :     if (this->A42Model.TimeElapsed != TimeElapsed) {
     812              :         // The simulation has advanced to the next system timestep.  Save conditions from the end of the previous system
     813              :         // timestep for use as the initial conditions of each iteration that does not advance the system timestep.
     814         5081 :         this->A42Model.TengLast = this->A42Model.Teng;
     815         5081 :         this->A42Model.TempCWOutLast = this->A42Model.TcwOut;
     816         5081 :         this->A42Model.TimeElapsed = TimeElapsed;
     817         5081 :         state.dataGenerator->GeneratorDynamics(DynaCntrlNum).LastOpMode = state.dataGenerator->GeneratorDynamics(DynaCntrlNum).CurrentOpMode;
     818         5081 :         state.dataGenerator->GeneratorDynamics(DynaCntrlNum).FuelMdotLastTimestep = this->A42Model.MdotFuel;
     819         5081 :         state.dataGenerator->GeneratorDynamics(DynaCntrlNum).PelLastTimeStep = this->A42Model.Pnet;
     820              :     }
     821              : 
     822        15328 :     if (!this->A42Model.InternalFlowControl) {
     823              : 
     824         6134 :         Real64 mdot = this->PlantMassFlowRateMax;
     825         6134 :         PlantUtilities::SetComponentFlowRate(state, mdot, this->PlantInletNodeID, this->PlantOutletNodeID, this->CWPlantLoc);
     826         6134 :         this->PlantMassFlowRate = mdot;
     827              :     }
     828              : }
     829              : 
     830        15328 : void MicroCHPDataStruct::CalcMicroCHPNoNormalizeGeneratorModel(EnergyPlusData &state,
     831              :                                                                bool const RunFlagElectCenter, // TRUE when Generator operating
     832              :                                                                bool const RunFlagPlant,
     833              :                                                                Real64 const MyElectricLoad, // Generator demand
     834              :                                                                Real64 const MyThermalLoad)
     835              : {
     836              : 
     837              :     // SUBROUTINE INFORMATION:
     838              :     //       AUTHOR        B Griffith
     839              :     //       DATE WRITTEN   July 2006
     840              :     //       MODIFIED       na
     841              :     //       RE-ENGINEERED  na
     842              : 
     843              :     // PURPOSE OF THIS SUBROUTINE:
     844              :     // Main calculation subroutine for the IEA Annex 42 model
     845              : 
     846              :     // METHODOLOGY EMPLOYED:
     847              :     // curve fit, dynamic control limits,
     848              : 
     849              :     // REFERENCES:
     850              :     // IEA Annex 42 FC-COGEN-SIM "A Generic Model Specification for Combustion-based Residential CHP Devices"
     851              :     // Alex Ferguson, Nick Kelly, Version 3, June 26, 2006
     852              : 
     853              :     static constexpr std::string_view RoutineName("CalcMicroCHPNoNormalizeGeneratorModel");
     854              : 
     855        15328 :     DataGenerators::OperatingMode CurrentOpMode = DataGenerators::OperatingMode::Invalid;
     856              :     Real64 NdotFuel;
     857        15328 :     Real64 AllowedLoad = 0.0;
     858        15328 :     Real64 PLRforSubtimestepStartUp(1.0);
     859        15328 :     Real64 PLRforSubtimestepShutDown(0.0);
     860              : 
     861        15328 :     GeneratorDynamicsManager::ManageGeneratorControlState(state,
     862              :                                                           this->DynamicsControlID,
     863              :                                                           RunFlagElectCenter,
     864              :                                                           RunFlagPlant,
     865              :                                                           MyElectricLoad,
     866              :                                                           MyThermalLoad,
     867              :                                                           AllowedLoad,
     868              :                                                           CurrentOpMode,
     869              :                                                           PLRforSubtimestepStartUp,
     870              :                                                           PLRforSubtimestepShutDown);
     871              : 
     872        15328 :     Real64 Teng = this->A42Model.Teng;
     873        15328 :     Real64 TcwOut = this->A42Model.TcwOut;
     874              : 
     875              :     Real64 thisAmbientTemp;
     876        15328 :     if (this->ZoneID > 0) {
     877        15328 :         thisAmbientTemp = state.dataZoneTempPredictorCorrector->zoneHeatBalance(this->ZoneID).MAT;
     878              :     } else { // outdoor location, no zone
     879            0 :         thisAmbientTemp = state.dataEnvrn->OutDryBulbTemp;
     880              :     }
     881              : 
     882        15328 :     Real64 Pnetss = 0.0;
     883        15328 :     Real64 Pstandby = 0.0; // power draw during standby, positive here means negative production
     884        15328 :     Real64 Pcooler = 0.0;  // power draw during cool down, positive here means negative production
     885        15328 :     Real64 ElecEff = 0.0;
     886        15328 :     Real64 MdotAir = 0.0;
     887        15328 :     Real64 Qgenss = 0.0;
     888        15328 :     Real64 MdotCW = 0.0;
     889        15328 :     Real64 TcwIn = 0.0;
     890        15328 :     Real64 MdotFuel = 0.0;
     891        15328 :     Real64 Qgross = 0.0;
     892        15328 :     Real64 ThermEff = 0.0;
     893              : 
     894        15328 :     switch (CurrentOpMode) {
     895         5630 :     case DataGenerators::OperatingMode::Off: { // same as standby in model spec but no Pnet standby electricity losses.
     896              : 
     897         5630 :         Qgenss = 0.0;
     898         5630 :         TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
     899         5630 :         Pnetss = 0.0;
     900         5630 :         Pstandby = 0.0;
     901         5630 :         Pcooler = this->A42Model.PcoolDown * PLRforSubtimestepShutDown;
     902         5630 :         ElecEff = 0.0;
     903         5630 :         ThermEff = 0.0;
     904         5630 :         Qgross = 0.0;
     905         5630 :         NdotFuel = 0.0;
     906         5630 :         MdotFuel = 0.0;
     907         5630 :         MdotAir = 0.0;
     908              : 
     909         5630 :         MdotCW = 0.0;
     910         5630 :         PlantUtilities::SetComponentFlowRate(state, MdotCW, this->PlantInletNodeID, this->PlantOutletNodeID, this->CWPlantLoc);
     911         5630 :         this->PlantMassFlowRate = MdotCW;
     912         5630 :     } break;
     913         4554 :     case DataGenerators::OperatingMode::Standby: {
     914         4554 :         Qgenss = 0.0;
     915         4554 :         TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp; // C
     916         4554 :         Pnetss = 0.0;
     917         4554 :         Pstandby = this->A42Model.Pstandby * (1.0 - PLRforSubtimestepShutDown);
     918         4554 :         Pcooler = this->A42Model.PcoolDown * PLRforSubtimestepShutDown;
     919         4554 :         ElecEff = 0.0;
     920         4554 :         ThermEff = 0.0;
     921         4554 :         Qgross = 0.0;
     922         4554 :         NdotFuel = 0.0;
     923         4554 :         MdotFuel = 0.0;
     924         4554 :         MdotAir = 0.0;
     925              : 
     926         4554 :         MdotCW = 0.0;
     927         4554 :         PlantUtilities::SetComponentFlowRate(state, MdotCW, this->PlantInletNodeID, this->PlantOutletNodeID, this->CWPlantLoc);
     928         4554 :         this->PlantMassFlowRate = MdotCW;
     929         4554 :     } break;
     930            0 :     case DataGenerators::OperatingMode::WarmUp: {
     931            0 :         if (this->A42Model.WarmUpByTimeDelay) {
     932              :             // Internal combustion engine.  This is just like normal  operation but no net power yet.
     933            0 :             Pnetss = MyElectricLoad; // W
     934            0 :             Pstandby = 0.0;
     935            0 :             Pcooler = this->A42Model.PcoolDown * PLRforSubtimestepShutDown;
     936            0 :             TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp;          // C
     937            0 :             MdotCW = state.dataLoopNodes->Node(this->PlantInletNodeID).MassFlowRate; // kg/s
     938            0 :             if (this->A42Model.InternalFlowControl) {
     939            0 :                 MdotCW = GeneratorDynamicsManager::FuncDetermineCWMdotForInternalFlowControl(state, this->DynamicsControlID, Pnetss, TcwIn);
     940              :             }
     941            0 :             ElecEff = this->A42Model.ElecEffCurve->value(state, Pnetss, MdotCW, TcwIn);
     942            0 :             ElecEff = max(0.0, ElecEff); // protect against bad curve result
     943              : 
     944            0 :             if (ElecEff > 0.0) {           // trap divide by bad thing
     945            0 :                 Qgross = Pnetss / ElecEff; // W
     946              :             } else {
     947            0 :                 Qgross = 0.0;
     948              :             }
     949            0 :             ThermEff = this->A42Model.ThermalEffCurve->value(state, Pnetss, MdotCW, TcwIn);
     950            0 :             ThermEff = max(0.0, ThermEff); // protect against bad curve result
     951              : 
     952            0 :             Qgenss = ThermEff * Qgross; // W
     953              : 
     954            0 :             MdotFuel = Qgross / (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0) *
     955            0 :                        state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
     956              :             //  kMol/s = (J/s) /(KJ/mol * 1000 J/KJ * 1000 mol/kmol)
     957              : 
     958            0 :             bool ConstrainedIncreasingNdot(false);
     959            0 :             bool ConstrainedDecreasingNdot(false);
     960            0 :             Real64 MdotFuelAllowed = 0.0;
     961              : 
     962            0 :             GeneratorDynamicsManager::ManageGeneratorFuelFlow(
     963              :                 state, this->DynamicsControlID, MdotFuel, MdotFuelAllowed, ConstrainedIncreasingNdot, ConstrainedDecreasingNdot);
     964              : 
     965            0 :             if (ConstrainedIncreasingNdot || ConstrainedDecreasingNdot) { // recalculate Pnetss with new NdotFuel with iteration
     966            0 :                 MdotFuel = MdotFuelAllowed;
     967            0 :                 NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
     968            0 :                 Qgross = NdotFuel * (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
     969              : 
     970            0 :                 for (int i = 1; i <= 20; ++i) { // iterating here  could add use of search method
     971            0 :                     Pnetss = Qgross * ElecEff;
     972            0 :                     if (this->A42Model.InternalFlowControl) {
     973            0 :                         MdotCW = GeneratorDynamicsManager::FuncDetermineCWMdotForInternalFlowControl(state, this->DynamicsControlID, Pnetss, TcwIn);
     974              :                     }
     975            0 :                     ElecEff = this->A42Model.ElecEffCurve->value(state, Pnetss, MdotCW, TcwIn);
     976            0 :                     ElecEff = max(0.0, ElecEff); // protect against bad curve result
     977              :                 }
     978              : 
     979            0 :                 ThermEff = this->A42Model.ThermalEffCurve->value(state, Pnetss, MdotCW, TcwIn);
     980            0 :                 ThermEff = max(0.0, ThermEff); // protect against bad curve result
     981            0 :                 Qgenss = ThermEff * Qgross;    // W
     982              :             }
     983            0 :             Pnetss = 0.0; // no actually power produced here.
     984            0 :             NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
     985            0 :             MdotAir = this->A42Model.AirFlowCurve->value(state, MdotFuel);
     986            0 :             MdotAir = max(0.0, MdotAir); // protect against bad curve result
     987              : 
     988            0 :         } else if (this->A42Model.WarmUpByEngineTemp) {
     989              :             // Stirling engine mode warm up
     990              :             //   find MdotFuelMax
     991            0 :             Real64 Pmax = this->A42Model.MaxElecPower;
     992            0 :             Pstandby = 0.0;
     993            0 :             Pcooler = this->A42Model.PcoolDown * PLRforSubtimestepShutDown;          // could be here with part load in cool down
     994            0 :             TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp;          // C
     995            0 :             MdotCW = state.dataLoopNodes->Node(this->PlantInletNodeID).MassFlowRate; // kg/s
     996            0 :             ElecEff = this->A42Model.ElecEffCurve->value(state, Pmax, MdotCW, TcwIn);
     997            0 :             ElecEff = max(0.0, ElecEff); // protect against bad curve result
     998            0 :             if (ElecEff > 0.0) {         // trap divide by bad thing
     999            0 :                 Qgross = Pmax / ElecEff; // W
    1000              :             } else {
    1001            0 :                 Qgross = 0.0;
    1002              :             }
    1003            0 :             NdotFuel = Qgross / (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
    1004              :             //  kMol/s = (J/s) /(KJ/mol * 1000 J/KJ * 1000 mol/kmol)
    1005            0 :             Real64 MdotFuelMax = NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
    1006              : 
    1007              :             Real64 MdotFuelWarmup;
    1008            0 :             if (Teng > thisAmbientTemp) {
    1009            0 :                 MdotFuelWarmup =
    1010            0 :                     MdotFuelMax + this->A42Model.kf * MdotFuelMax * ((this->A42Model.TnomEngOp - thisAmbientTemp) / (Teng - thisAmbientTemp));
    1011              :                 // check that numerical answer didn't blow up beyond limit, and reset if it did
    1012            0 :                 if (MdotFuelWarmup > this->A42Model.Rfuelwarmup * MdotFuelMax) {
    1013            0 :                     MdotFuelWarmup = this->A42Model.Rfuelwarmup * MdotFuelMax;
    1014              :                 }
    1015              :             } else { // equal would divide by zero
    1016            0 :                 MdotFuelWarmup = this->A42Model.Rfuelwarmup * MdotFuelMax;
    1017              :             }
    1018              : 
    1019            0 :             if (this->A42Model.TnomEngOp > thisAmbientTemp) {
    1020            0 :                 Pnetss = Pmax * this->A42Model.kp * ((Teng - thisAmbientTemp) / (this->A42Model.TnomEngOp - thisAmbientTemp));
    1021              :             } else { // equal would divide by zero
    1022            0 :                 Pnetss = Pmax;
    1023              :             }
    1024              : 
    1025            0 :             MdotFuel = MdotFuelWarmup;
    1026            0 :             NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
    1027            0 :             MdotAir = this->A42Model.AirFlowCurve->value(state, MdotFuelWarmup);
    1028            0 :             MdotAir = max(0.0, MdotAir); // protect against bad curve result
    1029            0 :             Qgross = NdotFuel * (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
    1030            0 :             ThermEff = this->A42Model.ThermalEffCurve->value(state, Pmax, MdotCW, TcwIn);
    1031            0 :             Qgenss = ThermEff * Qgross; // W
    1032              :         }
    1033            0 :     } break;
    1034         5144 :     case DataGenerators::OperatingMode::Normal: {
    1035         5144 :         if (PLRforSubtimestepStartUp < 1.0) {
    1036         1496 :             if (RunFlagElectCenter) {
    1037         1496 :                 Pnetss = MyElectricLoad; // W
    1038              :             }
    1039         1496 :             if (RunFlagPlant) {
    1040            0 :                 Pnetss = AllowedLoad;
    1041              :             }
    1042              :         } else {
    1043         3648 :             Pnetss = AllowedLoad;
    1044              :         }
    1045         5144 :         Pstandby = 0.0;
    1046         5144 :         Pcooler = 0.0;
    1047         5144 :         TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp;          // C
    1048         5144 :         MdotCW = state.dataLoopNodes->Node(this->PlantInletNodeID).MassFlowRate; // kg/s
    1049         5144 :         if (this->A42Model.InternalFlowControl) {
    1050         4640 :             MdotCW = GeneratorDynamicsManager::FuncDetermineCWMdotForInternalFlowControl(state, this->DynamicsControlID, Pnetss, TcwIn);
    1051              :         }
    1052              : 
    1053         5144 :         ElecEff = this->A42Model.ElecEffCurve->value(state, Pnetss, MdotCW, TcwIn);
    1054         5144 :         ElecEff = max(0.0, ElecEff); // protect against bad curve result
    1055              : 
    1056         5144 :         if (ElecEff > 0.0) {           // trap divide by bad thing
    1057         5144 :             Qgross = Pnetss / ElecEff; // W
    1058              :         } else {
    1059            0 :             Qgross = 0.0;
    1060              :         }
    1061              : 
    1062         5144 :         ThermEff = this->A42Model.ThermalEffCurve->value(state, Pnetss, MdotCW, TcwIn);
    1063         5144 :         ThermEff = max(0.0, ThermEff); // protect against bad curve result
    1064         5144 :         Qgenss = ThermEff * Qgross;    // W
    1065         5144 :         MdotFuel = Qgross / (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0) *
    1066         5144 :                    state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
    1067              :         //  kMol/s = (J/s) /(KJ/mol * 1000 J/KJ * 1000 mol/kmol)
    1068              : 
    1069         5144 :         bool ConstrainedIncreasingNdot(false);
    1070         5144 :         bool ConstrainedDecreasingNdot(false);
    1071         5144 :         Real64 MdotFuelAllowed = 0.0;
    1072              : 
    1073         5144 :         GeneratorDynamicsManager::ManageGeneratorFuelFlow(
    1074              :             state, this->DynamicsControlID, MdotFuel, MdotFuelAllowed, ConstrainedIncreasingNdot, ConstrainedDecreasingNdot);
    1075              : 
    1076         5144 :         if (ConstrainedIncreasingNdot || ConstrainedDecreasingNdot) { // recalculate Pnetss with new NdotFuel with iteration
    1077            0 :             MdotFuel = MdotFuelAllowed;
    1078            0 :             NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
    1079            0 :             Qgross = NdotFuel * (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
    1080              : 
    1081            0 :             for (int i = 1; i <= 20; ++i) { // iterating here,  could add use of search method error signal
    1082            0 :                 Pnetss = Qgross * ElecEff;
    1083            0 :                 if (this->A42Model.InternalFlowControl) {
    1084            0 :                     MdotCW = GeneratorDynamicsManager::FuncDetermineCWMdotForInternalFlowControl(state, this->DynamicsControlID, Pnetss, TcwIn);
    1085              :                 }
    1086            0 :                 ElecEff = this->A42Model.ElecEffCurve->value(state, Pnetss, MdotCW, TcwIn);
    1087            0 :                 ElecEff = max(0.0, ElecEff); // protect against bad curve result
    1088              :             }
    1089              : 
    1090            0 :             ThermEff = this->A42Model.ThermalEffCurve->value(state, Pnetss, MdotCW, TcwIn);
    1091            0 :             ThermEff = max(0.0, ThermEff); // protect against bad curve result
    1092            0 :             Qgenss = ThermEff * Qgross;    // W
    1093              :         }
    1094              : 
    1095         5144 :         NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
    1096         5144 :         MdotAir = this->A42Model.AirFlowCurve->value(state, MdotFuel);
    1097         5144 :         MdotAir = max(0.0, MdotAir); // protect against bad curve result
    1098         5144 :         if (PLRforSubtimestepStartUp < 1.0) {
    1099         1496 :             Pnetss = AllowedLoad;
    1100              :         }
    1101         5144 :     } break;
    1102            0 :     case DataGenerators::OperatingMode::CoolDown: {
    1103            0 :         Pnetss = 0.0;
    1104            0 :         Pstandby = 0.0;
    1105            0 :         Pcooler = this->A42Model.PcoolDown;
    1106            0 :         TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp;          // C
    1107            0 :         MdotCW = state.dataLoopNodes->Node(this->PlantInletNodeID).MassFlowRate; // kg/s
    1108            0 :         if (this->A42Model.InternalFlowControl) {
    1109            0 :             MdotCW = GeneratorDynamicsManager::FuncDetermineCWMdotForInternalFlowControl(state, this->DynamicsControlID, Pnetss, TcwIn);
    1110              :         }
    1111            0 :         NdotFuel = 0.0;
    1112            0 :         MdotFuel = 0.0;
    1113            0 :         MdotAir = 0.0;
    1114            0 :         ElecEff = 0.0;
    1115            0 :         ThermEff = 0.0;
    1116            0 :         Qgross = 0.0;
    1117            0 :         Qgenss = 0.0;
    1118            0 :     } break;
    1119            0 :     default:
    1120            0 :         break;
    1121              :     }
    1122              : 
    1123        76640 :     for (int i = 1; i <= 20; ++i) { // sequential search with exit criteria
    1124              :         // calculate new value for engine temperature
    1125              :         // for Stirling in warmup, need to include dependency of Qgness on Teng
    1126        76640 :         if ((this->A42Model.WarmUpByEngineTemp) && (CurrentOpMode == DataGenerators::OperatingMode::WarmUp)) {
    1127              : 
    1128            0 :             Real64 Pmax = this->A42Model.MaxElecPower;
    1129            0 :             TcwIn = state.dataLoopNodes->Node(this->PlantInletNodeID).Temp;          // C
    1130            0 :             MdotCW = state.dataLoopNodes->Node(this->PlantInletNodeID).MassFlowRate; // kg/s
    1131            0 :             ElecEff = this->A42Model.ElecEffCurve->value(state, Pmax, MdotCW, TcwIn);
    1132            0 :             ElecEff = max(0.0, ElecEff); // protect against bad curve result
    1133            0 :             if (ElecEff > 0.0) {         // trap divide by bad thing
    1134            0 :                 Qgross = Pmax / ElecEff; // W
    1135              :             } else {
    1136            0 :                 Qgross = 0.0;
    1137              :             }
    1138            0 :             NdotFuel = Qgross / (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
    1139              :             //  kMol/s = (J/s) /(KJ/mol * 1000 J/KJ * 1000 mol/kmol)
    1140            0 :             Real64 MdotFuelMax = NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
    1141              : 
    1142              :             Real64 MdotFuelWarmup;
    1143            0 :             if (Teng > thisAmbientTemp) {
    1144            0 :                 MdotFuelWarmup =
    1145            0 :                     MdotFuelMax + this->A42Model.kf * MdotFuelMax * ((this->A42Model.TnomEngOp - thisAmbientTemp) / (Teng - thisAmbientTemp));
    1146              : 
    1147              :                 // check that numerical answer didn't blow up beyond limit, and reset if it did
    1148            0 :                 if (MdotFuelWarmup > this->A42Model.Rfuelwarmup * MdotFuelMax) {
    1149            0 :                     MdotFuelWarmup = this->A42Model.Rfuelwarmup * MdotFuelMax;
    1150              :                 }
    1151            0 :                 if (this->A42Model.TnomEngOp > thisAmbientTemp) {
    1152            0 :                     Pnetss = Pmax * this->A42Model.kp * ((Teng - thisAmbientTemp) / (this->A42Model.TnomEngOp - thisAmbientTemp));
    1153              :                 } else { // equal would divide by zero
    1154            0 :                     Pnetss = Pmax;
    1155              :                 }
    1156              :             } else { // equal would divide by zero
    1157            0 :                 MdotFuelWarmup = this->A42Model.Rfuelwarmup * MdotFuelMax;
    1158              :             }
    1159            0 :             MdotFuel = MdotFuelWarmup;
    1160            0 :             NdotFuel = MdotFuel / state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
    1161            0 :             MdotAir = this->A42Model.AirFlowCurve->value(state, MdotFuelWarmup);
    1162            0 :             MdotAir = max(0.0, MdotAir); // protect against bad curve result
    1163            0 :             Qgross = NdotFuel * (state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000.0 * 1000.0);
    1164            0 :             ThermEff = this->A42Model.ThermalEffCurve->value(state, Pmax, MdotCW, TcwIn);
    1165            0 :             ThermEff = max(0.0, ThermEff); // protect against bad curve result
    1166            0 :             Qgenss = ThermEff * Qgross;    // W
    1167              :         }
    1168              : 
    1169        76640 :         Real64 dt = state.dataHVACGlobal->TimeStepSysSec;
    1170              : 
    1171        76640 :         Teng = FuncDetermineEngineTemp(
    1172              :             TcwOut, this->A42Model.MCeng, this->A42Model.UAhx, this->A42Model.UAskin, thisAmbientTemp, Qgenss, this->A42Model.TengLast, dt);
    1173              : 
    1174        76640 :         Real64 Cp = state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).glycol->getSpecificHeat(state, TcwIn, RoutineName);
    1175              : 
    1176              :         TcwOut =
    1177        76640 :             FuncDetermineCoolantWaterExitTemp(TcwIn, this->A42Model.MCcw, this->A42Model.UAhx, MdotCW * Cp, Teng, this->A42Model.TempCWOutLast, dt);
    1178              : 
    1179              :         // form balance and exit once met.
    1180        76640 :         bool EnergyBalOK = CheckMicroCHPThermalBalance(this->A42Model.MaxElecPower,
    1181              :                                                        TcwIn,
    1182              :                                                        TcwOut,
    1183              :                                                        Teng,
    1184              :                                                        thisAmbientTemp,
    1185              :                                                        this->A42Model.UAhx,
    1186              :                                                        this->A42Model.UAskin,
    1187              :                                                        Qgenss,
    1188              :                                                        this->A42Model.MCeng,
    1189              :                                                        this->A42Model.MCcw,
    1190              :                                                        MdotCW * Cp);
    1191              : 
    1192        76640 :         if (EnergyBalOK && (i > 4)) {
    1193        15328 :             break;
    1194              :         }
    1195              :     }
    1196              : 
    1197        15328 :     this->PlantMassFlowRate = MdotCW;
    1198        15328 :     this->A42Model.Pnet = Pnetss - Pcooler - Pstandby;
    1199        15328 :     this->A42Model.ElecEff = ElecEff;
    1200        15328 :     this->A42Model.Qgross = Qgross;
    1201        15328 :     this->A42Model.ThermEff = ThermEff;
    1202        15328 :     this->A42Model.Qgenss = Qgenss;
    1203        15328 :     this->A42Model.NdotFuel = NdotFuel;
    1204        15328 :     this->A42Model.MdotFuel = MdotFuel;
    1205        15328 :     this->A42Model.Teng = Teng;
    1206        15328 :     this->A42Model.TcwOut = TcwOut;
    1207        15328 :     this->A42Model.TcwIn = TcwIn;
    1208        15328 :     this->A42Model.MdotAir = MdotAir;
    1209        15328 :     this->A42Model.QdotSkin = this->A42Model.UAskin * (Teng - thisAmbientTemp);
    1210              : 
    1211        15328 :     this->A42Model.OpMode = CurrentOpMode;
    1212        15328 : }
    1213              : 
    1214        76640 : Real64 FuncDetermineEngineTemp(Real64 const TcwOut,   // hot water leaving temp
    1215              :                                Real64 const MCeng,    // Fictitious mass and heat capacity of engine
    1216              :                                Real64 const UAHX,     // Heat exchanger UA
    1217              :                                Real64 const UAskin,   // Skin losses UA
    1218              :                                Real64 const Troom,    // surrounding zone temperature C
    1219              :                                Real64 const Qgenss,   // steady state generator heat generation
    1220              :                                Real64 const TengLast, // engine temp at previous time step
    1221              :                                Real64 const time      // elapsed time since previous evaluation
    1222              : )
    1223              : {
    1224              : 
    1225              :     // FUNCTION INFORMATION:
    1226              :     //       AUTHOR         B. Griffith
    1227              :     //       DATE WRITTEN   Feb. 2007
    1228              :     //       MODIFIED       na
    1229              :     //       RE-ENGINEERED  na
    1230              : 
    1231              :     // PURPOSE OF THIS FUNCTION:
    1232              :     // Calculate engine temperature,
    1233              : 
    1234              :     // METHODOLOGY EMPLOYED:
    1235              :     // model is dynamic in that previous condition affects current timestep
    1236              :     //  solve ode for engine temp using analytical solution
    1237              : 
    1238        76640 :     Real64 a = ((UAHX * TcwOut / MCeng) + (UAskin * Troom / MCeng) + (Qgenss / MCeng));
    1239        76640 :     Real64 b = ((-1.0 * UAHX / MCeng) + (-1.0 * UAskin / MCeng));
    1240              : 
    1241        76640 :     return (TengLast + a / b) * std::exp(b * time) - a / b;
    1242              : }
    1243              : 
    1244        76640 : Real64 FuncDetermineCoolantWaterExitTemp(Real64 const TcwIn,      // hot water inlet temp
    1245              :                                          Real64 const MCcw,       // Fictitious mass and heat capacity of coolant hx
    1246              :                                          Real64 const UAHX,       // Heat exchanger UA
    1247              :                                          Real64 const MdotCpcw,   // mass flow and specific heat of coolant water
    1248              :                                          Real64 const Teng,       // engine mass temperature C
    1249              :                                          Real64 const TcwoutLast, // coolant water leaving temp at previous time step
    1250              :                                          Real64 const time        // elapsed time since previous evaluation
    1251              : )
    1252              : {
    1253              : 
    1254              :     // FUNCTION INFORMATION:
    1255              :     //       AUTHOR         B. Griffith
    1256              :     //       DATE WRITTEN   Feb. 2007
    1257              :     //       MODIFIED       na
    1258              :     //       RE-ENGINEERED  na
    1259              : 
    1260              :     // PURPOSE OF THIS FUNCTION:
    1261              :     // Calculate coolant water leaving temperature,
    1262              : 
    1263              :     // METHODOLOGY EMPLOYED:
    1264              :     // model is dynamic in that previous condition affects current timestep
    1265              :     //  solve ode for coolant water outlet temp using analytical solution
    1266              : 
    1267        76640 :     Real64 a = (MdotCpcw * TcwIn / MCcw) + (UAHX * Teng / MCcw);
    1268        76640 :     Real64 b = ((-1.0 * MdotCpcw / MCcw) + (-1.0 * UAHX / MCcw));
    1269              : 
    1270        76640 :     if (b * time < (-1.0 * Constant::MaxEXPArg)) {
    1271        23220 :         return -a / b;
    1272              :     } else {
    1273        53420 :         return (TcwoutLast + a / b) * std::exp(b * time) - a / b;
    1274              :     }
    1275              : }
    1276              : 
    1277        76640 : bool CheckMicroCHPThermalBalance(Real64 const NomHeatGen, // nominal heat generation rate for scaling
    1278              :                                  Real64 const TcwIn,      // hot water inlet temp
    1279              :                                  Real64 const TcwOut,     // hot water leaving temp
    1280              :                                  Real64 const Teng,       // engine mass temperature C
    1281              :                                  Real64 const Troom,      // surrounding zone temperature C
    1282              :                                  Real64 const UAHX,       // Heat exchanger UA
    1283              :                                  Real64 const UAskin,     // Skin losses UA
    1284              :                                  Real64 const Qgenss,     // steady state generator heat generation
    1285              :                                  Real64 const MCeng,      // Fictitious mass and heat capacity of engine
    1286              :                                  Real64 const MCcw,       // Fictitious mass and heat capacity of coolant hx
    1287              :                                  Real64 const MdotCpcw    // mass flow and specific heat of coolant water
    1288              : )
    1289              : {
    1290              : 
    1291              :     // FUNCTION INFORMATION:
    1292              :     //       AUTHOR         B. Griffith
    1293              :     //       DATE WRITTEN   Feb. 2007
    1294              :     //       MODIFIED       na
    1295              :     //       RE-ENGINEERED  na
    1296              : 
    1297              :     // PURPOSE OF THIS FUNCTION:
    1298              :     // Check for energy balance to test if can exit iteration loop
    1299              : 
    1300              :     // METHODOLOGY EMPLOYED:
    1301              :     // put all terms of dynamic energy balances on RHS and compute magnitude of imbalance
    1302              :     //  compare imbalance to scalable thresholds and make a boolean conclusion.
    1303              : 
    1304              :     // first compute derivatives using a + bT
    1305              :     // derivative of engine temp wrt time
    1306        76640 :     Real64 a = ((UAHX * TcwOut / MCeng) + (UAskin * Troom / MCeng) + (Qgenss / MCeng));
    1307        76640 :     Real64 b = ((-1.0 * UAHX / MCeng) + (-1.0 * UAskin / MCeng));
    1308        76640 :     Real64 DTengDTime = a + b * Teng;
    1309              : 
    1310              :     // derivative of coolant exit temp wrt time
    1311        76640 :     Real64 c = (MdotCpcw * TcwIn / MCcw) + (UAHX * Teng / MCcw);
    1312        76640 :     Real64 d = ((-1.0 * MdotCpcw / MCcw) + (-1.0 * UAHX / MCcw));
    1313        76640 :     Real64 DCoolOutTDtime = c + d * TcwOut;
    1314              : 
    1315              :     // energy imbalance for engine control volume
    1316        76640 :     Real64 magImbalEng = UAHX * (TcwOut - Teng) + UAskin * (Troom - Teng) + Qgenss - MCeng * DTengDTime;
    1317              : 
    1318              :     // energy imbalance for coolant control volume
    1319        76640 :     Real64 magImbalCooling = MdotCpcw * (TcwIn - TcwOut) + UAHX * (Teng - TcwOut) - MCcw * DCoolOutTDtime;
    1320              : 
    1321              :     // criteria for when to call energy balance okay
    1322        76640 :     Real64 threshold = NomHeatGen / 10000000.0;
    1323              : 
    1324        76640 :     return (threshold > magImbalEng) && (threshold > magImbalCooling);
    1325              : }
    1326              : 
    1327      2828408 : void FigureMicroCHPZoneGains(EnergyPlusData &state)
    1328              : {
    1329              : 
    1330              :     // SUBROUTINE INFORMATION:
    1331              :     //       AUTHOR         B. Griffith
    1332              :     //       DATE WRITTEN   July 2006
    1333              :     //       MODIFIED       na
    1334              :     //       RE-ENGINEERED  na
    1335              : 
    1336              :     // PURPOSE OF THIS SUBROUTINE:
    1337              :     // Couple equipment skin losses to the Zone Heat Balance
    1338              : 
    1339              :     // METHODOLOGY EMPLOYED:
    1340              :     // This routine adds up the various skin losses and then
    1341              :     //  sets the values in the ZoneIntGain structure
    1342              : 
    1343      2828408 :     if (state.dataCHPElectGen->NumMicroCHPs == 0) {
    1344      2821474 :         return;
    1345              :     }
    1346              : 
    1347         6934 :     if (state.dataGlobal->BeginEnvrnFlag && state.dataCHPElectGen->MyEnvrnFlag) {
    1348           32 :         for (auto &e : state.dataGenerator->FuelSupply) {
    1349           20 :             e.QskinLoss = 0.0;
    1350              :         }
    1351           24 :         for (auto &e : state.dataCHPElectGen->MicroCHP) {
    1352           12 :             e.A42Model.QdotSkin = 0.0;
    1353           12 :             e.A42Model.SkinLossConvect = 0.0;
    1354           12 :             e.A42Model.SkinLossRadiat = 0.0;
    1355           12 :         }
    1356           12 :         state.dataCHPElectGen->MyEnvrnFlag = false;
    1357              :     }
    1358              : 
    1359         6934 :     if (!state.dataGlobal->BeginEnvrnFlag) {
    1360         6922 :         state.dataCHPElectGen->MyEnvrnFlag = true;
    1361              :     }
    1362              : 
    1363        13868 :     for (int CHPnum = 1; CHPnum <= state.dataCHPElectGen->NumMicroCHPs; ++CHPnum) {
    1364         6934 :         Real64 TotalZoneHeatGain = state.dataGenerator->FuelSupply(state.dataCHPElectGen->MicroCHP(CHPnum).FuelSupplyID).QskinLoss +
    1365         6934 :                                    state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.QdotSkin;
    1366              : 
    1367         6934 :         state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.QdotConvZone =
    1368         6934 :             TotalZoneHeatGain * (1 - state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.RadiativeFraction);
    1369         6934 :         state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.SkinLossConvect = state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.QdotConvZone;
    1370         6934 :         state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.QdotRadZone =
    1371         6934 :             TotalZoneHeatGain * state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.RadiativeFraction;
    1372         6934 :         state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.SkinLossRadiat = state.dataCHPElectGen->MicroCHP(CHPnum).A42Model.QdotRadZone;
    1373              :     }
    1374              : }
    1375              : 
    1376        15328 : void MicroCHPDataStruct::CalcUpdateHeatRecovery(EnergyPlusData &state) const
    1377              : {
    1378              : 
    1379              :     // SUBROUTINE INFORMATION:
    1380              :     //       AUTHOR         B Griffith
    1381              :     //       DATE WRITTEN   Aug 2006
    1382              :     //       MODIFIED       na
    1383              :     //       RE-ENGINEERED  na
    1384              : 
    1385              :     // PURPOSE OF THIS SUBROUTINE:
    1386              :     // update plant loop interactions, do any calcs needed
    1387              : 
    1388              :     static constexpr std::string_view RoutineName("CalcUpdateHeatRecovery");
    1389              : 
    1390        15328 :     PlantUtilities::SafeCopyPlantNode(state, this->PlantInletNodeID, this->PlantOutletNodeID);
    1391              : 
    1392        15328 :     state.dataLoopNodes->Node(this->PlantOutletNodeID).Temp = this->A42Model.TcwOut;
    1393              : 
    1394        15328 :     Real64 Cp = state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).glycol->getSpecificHeat(state, this->A42Model.TcwIn, RoutineName);
    1395              : 
    1396        15328 :     state.dataLoopNodes->Node(this->PlantOutletNodeID).Enthalpy = this->A42Model.TcwOut * Cp;
    1397        15328 : }
    1398              : 
    1399           10 : void MicroCHPDataStruct::getDesignCapacities(
    1400              :     [[maybe_unused]] EnergyPlusData &state, const EnergyPlus::PlantLocation &, Real64 &MaxLoad, Real64 &MinLoad, Real64 &OptLoad)
    1401              : {
    1402           10 :     MaxLoad = state.dataGenerator->GeneratorDynamics(this->DynamicsControlID).QdotHXMax;
    1403           10 :     MinLoad = state.dataGenerator->GeneratorDynamics(this->DynamicsControlID).QdotHXMin;
    1404           10 :     OptLoad = state.dataGenerator->GeneratorDynamics(this->DynamicsControlID).QdotHXOpt;
    1405           10 : }
    1406              : 
    1407        15328 : void MicroCHPDataStruct::UpdateMicroCHPGeneratorRecords(EnergyPlusData &state) // Generator number
    1408              : {
    1409              : 
    1410              :     // SUBROUTINE INFORMATION:
    1411              :     //       AUTHOR         B. Griffith
    1412              :     //       DATE WRITTEN   July 2006
    1413              :     //       MODIFIED       na
    1414              :     //       RE-ENGINEERED  na
    1415              : 
    1416              :     // PURPOSE OF THIS SUBROUTINE:
    1417              :     // update variables in structures linked to output reports
    1418              : 
    1419              :     static constexpr std::string_view RoutineName("UpdateMicroCHPGeneratorRecords");
    1420              : 
    1421        15328 :     this->A42Model.ACPowerGen = this->A42Model.Pnet;                                             // electrical power produced [W]
    1422        15328 :     this->A42Model.ACEnergyGen = this->A42Model.Pnet * state.dataHVACGlobal->TimeStepSysSec;     // energy produced (J)
    1423        15328 :     this->A42Model.QdotHX = this->A42Model.UAhx * (this->A42Model.Teng - this->A42Model.TcwOut); //  heat recovered rate (W)
    1424              : 
    1425        15328 :     Real64 Cp = state.dataPlnt->PlantLoop(this->CWPlantLoc.loopNum).glycol->getSpecificHeat(state, this->A42Model.TcwIn, RoutineName);
    1426              : 
    1427        15328 :     this->A42Model.QdotHR = this->PlantMassFlowRate * Cp * (this->A42Model.TcwOut - this->A42Model.TcwIn);
    1428        15328 :     this->A42Model.TotalHeatEnergyRec = this->A42Model.QdotHR * state.dataHVACGlobal->TimeStepSysSec; // heat recovered energy (J)
    1429              : 
    1430        15328 :     this->A42Model.HeatRecInletTemp = this->A42Model.TcwIn;   // Heat Recovery Loop Inlet Temperature (C)
    1431        15328 :     this->A42Model.HeatRecOutletTemp = this->A42Model.TcwOut; // Heat Recovery Loop Outlet Temperature (C)
    1432              : 
    1433        15328 :     this->A42Model.FuelCompressPower = state.dataGenerator->FuelSupply(this->FuelSupplyID).PfuelCompEl;
    1434              :     // electrical power used by fuel supply compressor [W]
    1435        15328 :     this->A42Model.FuelCompressEnergy =
    1436        15328 :         state.dataGenerator->FuelSupply(this->FuelSupplyID).PfuelCompEl * state.dataHVACGlobal->TimeStepSys * Constant::rSecsInHour; // elect energy
    1437        15328 :     this->A42Model.FuelCompressSkinLoss = state.dataGenerator->FuelSupply(this->FuelSupplyID).QskinLoss;
    1438              :     // heat rate of losses.by fuel supply compressor [W]
    1439        15328 :     this->A42Model.FuelEnergyHHV = this->A42Model.NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).HHV *
    1440        15328 :                                    state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec * state.dataHVACGlobal->TimeStepSys *
    1441              :                                    Constant::rSecsInHour;
    1442              :     // reporting: Fuel Energy used (W)
    1443        15328 :     this->A42Model.FuelEnergyUseRateHHV = this->A42Model.NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).HHV *
    1444        15328 :                                           state.dataGenerator->FuelSupply(this->FuelSupplyID).KmolPerSecToKgPerSec;
    1445              :     // reporting: Fuel Energy used (J)
    1446        15328 :     this->A42Model.FuelEnergyLHV =
    1447        15328 :         this->A42Model.NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000000.0 * state.dataHVACGlobal->TimeStepSysSec;
    1448              :     // reporting: Fuel Energy used (W)
    1449        15328 :     this->A42Model.FuelEnergyUseRateLHV = this->A42Model.NdotFuel * state.dataGenerator->FuelSupply(this->FuelSupplyID).LHV * 1000000.0;
    1450              : 
    1451        15328 :     this->A42Model.SkinLossPower = this->A42Model.QdotConvZone + this->A42Model.QdotRadZone;
    1452        15328 :     this->A42Model.SkinLossEnergy = (this->A42Model.QdotConvZone + this->A42Model.QdotRadZone) * state.dataHVACGlobal->TimeStepSysSec;
    1453        15328 :     this->A42Model.SkinLossConvect = this->A42Model.QdotConvZone;
    1454        15328 :     this->A42Model.SkinLossRadiat = this->A42Model.QdotRadZone;
    1455              : 
    1456              :     // update node data for air inlet (and outlet)
    1457        15328 :     if (this->AirInletNodeID > 0) {
    1458        15328 :         state.dataLoopNodes->Node(this->AirInletNodeID).MassFlowRate = this->A42Model.MdotAir;
    1459              :     }
    1460        15328 :     if (this->AirOutletNodeID > 0) {
    1461            0 :         state.dataLoopNodes->Node(this->AirOutletNodeID).MassFlowRate = this->A42Model.MdotAir;
    1462            0 :         state.dataLoopNodes->Node(this->AirOutletNodeID).Temp = this->A42Model.Teng;
    1463              :     }
    1464        15328 : }
    1465        17415 : void MicroCHPDataStruct::oneTimeInit(EnergyPlusData &state)
    1466              : {
    1467        17415 :     if (this->myFlag) {
    1468            2 :         this->setupOutputVars(state);
    1469            2 :         this->myFlag = false;
    1470              :     }
    1471              : 
    1472        17415 :     if (this->MyPlantScanFlag) {
    1473            2 :         if (allocated(state.dataPlnt->PlantLoop)) {
    1474            2 :             bool errFlag = false;
    1475            4 :             PlantUtilities::ScanPlantLoopsForObject(
    1476            2 :                 state, this->Name, DataPlant::PlantEquipmentType::Generator_MicroCHP, this->CWPlantLoc, errFlag, _, _, _, _, _);
    1477              : 
    1478            2 :             if (errFlag) {
    1479            0 :                 ShowFatalError(state, "InitMicroCHPNoNormalizeGenerators: Program terminated for previous conditions.");
    1480              :             }
    1481              : 
    1482            2 :             if (!this->A42Model.InternalFlowControl) {
    1483              :                 // IF this is on the supply side and not internal flow control then reset flow priority to lower
    1484            1 :                 if (this->CWPlantLoc.loopSideNum == DataPlant::LoopSideLocation::Supply) {
    1485            1 :                     DataPlant::CompData::getPlantComponent(state, this->CWPlantLoc).FlowPriority = DataPlant::LoopFlowStatus::TakesWhatGets;
    1486              :                 }
    1487              :             }
    1488              : 
    1489            2 :             this->MyPlantScanFlag = false;
    1490              :         }
    1491              :     }
    1492        17415 : }
    1493              : } // namespace EnergyPlus::MicroCHPElectricGenerator
        

Generated by: LCOV version 2.0-1