LCOV - code coverage report
Current view: top level - EnergyPlus - PhotovoltaicThermalCollectors.cc (source / functions) Coverage Total Hit
Test: lcov.output.filtered Lines: 46.0 % 1088 500
Test Date: 2025-05-22 16:09:37 Functions: 50.0 % 28 14

            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              : #include <iostream>
      51              : 
      52              : // ObjexxFCL Headers
      53              : #include <ObjexxFCL/Array.functions.hh>
      54              : #include <ObjexxFCL/Fmath.hh>
      55              : 
      56              : // EnergyPlus Headers
      57              : #include <EnergyPlus/Autosizing/Base.hh>
      58              : #include <EnergyPlus/BranchNodeConnections.hh>
      59              : #include <EnergyPlus/Construction.hh>
      60              : #include <EnergyPlus/ConvectionCoefficients.hh>
      61              : #include <EnergyPlus/Data/EnergyPlusData.hh>
      62              : #include <EnergyPlus/DataEnvironment.hh>
      63              : #include <EnergyPlus/DataHVACGlobals.hh>
      64              : #include <EnergyPlus/DataHeatBalSurface.hh>
      65              : #include <EnergyPlus/DataHeatBalance.hh>
      66              : #include <EnergyPlus/DataIPShortCuts.hh>
      67              : #include <EnergyPlus/DataLoopNode.hh>
      68              : #include <EnergyPlus/DataPhotovoltaics.hh>
      69              : #include <EnergyPlus/DataSizing.hh>
      70              : #include <EnergyPlus/DataSurfaces.hh>
      71              : #include <EnergyPlus/EMSManager.hh>
      72              : #include <EnergyPlus/FluidProperties.hh>
      73              : #include <EnergyPlus/General.hh>
      74              : #include <EnergyPlus/GeneralRoutines.hh>
      75              : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
      76              : #include <EnergyPlus/NodeInputManager.hh>
      77              : #include <EnergyPlus/OutputProcessor.hh>
      78              : #include <EnergyPlus/PhotovoltaicThermalCollectors.hh>
      79              : #include <EnergyPlus/Plant/DataPlant.hh>
      80              : #include <EnergyPlus/Plant/PlantLocation.hh>
      81              : #include <EnergyPlus/PlantUtilities.hh>
      82              : #include <EnergyPlus/Psychrometrics.hh>
      83              : #include <EnergyPlus/ScheduleManager.hh>
      84              : #include <EnergyPlus/SurfaceGeometry.hh>
      85              : #include <EnergyPlus/UtilityRoutines.hh>
      86              : 
      87              : namespace EnergyPlus {
      88              : 
      89              : namespace PhotovoltaicThermalCollectors {
      90              : 
      91              :     // Module containing the routines dealing with the photovoltaic thermal collectors
      92              : 
      93              :     // MODULE INFORMATION:
      94              :     //       AUTHOR         Brent. Griffith
      95              :     //       DATE WRITTEN   June-August 2008
      96              :     //       MODIFIED       na
      97              :     //       RE-ENGINEERED  na
      98              : 
      99              :     // PURPOSE OF THIS MODULE:
     100              :     // collect models related to PVT or hybrid, photovoltaic - thermal solar collectors
     101              : 
     102              :     // METHODOLOGY EMPLOYED:
     103              :     // The approach is to have one PVT structure that works with different models.
     104              :     //  the PVT model reuses photovoltaic modeling in Photovoltaics.cc for electricity generation.
     105              :     //  the electric load center and "generator" is all accessed thru PV objects and models.
     106              :     //  this module is for the thermal portion of PVT.
     107              :     //  the first model is a "simple" or "ideal" model useful for sizing, early design, or policy analyses
     108              :     //  Simple PV/T model just converts incoming solar to electricity and temperature rise of a working fluid.
     109              : 
     110            0 :     PlantComponent *PVTCollectorStruct::factory(EnergyPlusData &state, std::string_view objectName)
     111              : 
     112              :     {
     113            0 :         if (state.dataPhotovoltaicThermalCollector->GetInputFlag) {
     114            0 :             GetPVTcollectorsInput(state);
     115            0 :             state.dataPhotovoltaicThermalCollector->GetInputFlag = false;
     116              :         }
     117              : 
     118            0 :         for (auto &thisComp : state.dataPhotovoltaicThermalCollector->PVT) {
     119            0 :             if (thisComp.Name == objectName) {
     120            0 :                 return &thisComp;
     121              :             }
     122              :         }
     123              : 
     124              :         // If we didn't find it, fatal
     125            0 :         ShowFatalError(state, format("Solar Thermal Collector Factory: Error getting inputs for object named: {}", objectName));
     126              :         // Shut up the compiler
     127            0 :         return nullptr;
     128              :     }
     129              : 
     130            1 :     void PVTCollectorStruct::onInitLoopEquip(EnergyPlusData &state, [[maybe_unused]] const PlantLocation &calledFromLocation)
     131              :     {
     132            1 :         this->initialize(state, true);
     133            1 :         this->size(state);
     134            1 :     }
     135              : 
     136            0 :     void PVTCollectorStruct::simulate(EnergyPlusData &state,
     137              :                                       [[maybe_unused]] const PlantLocation &calledFromLocation,
     138              :                                       bool const FirstHVACIteration,
     139              :                                       [[maybe_unused]] Real64 &CurLoad,
     140              :                                       [[maybe_unused]] bool const RunFlag)
     141              :     {
     142              : 
     143            0 :         this->initialize(state, FirstHVACIteration);
     144            0 :         this->control(state);
     145            0 :         this->calculate(state);
     146            0 :         this->update(state);
     147            0 :     }
     148              : 
     149            4 :     void GetPVTcollectorsInput(EnergyPlusData &state)
     150              :     {
     151              :         // SUBROUTINE INFORMATION:
     152              :         //       AUTHOR         B. Griffith
     153              :         //       DATE WRITTEN   June 2008
     154              :         //       RE-ENGINEERED  na
     155              : 
     156              :         // PURPOSE OF THIS SUBROUTINE:
     157              :         // Get input for PVT and BIPVT objects
     158              : 
     159              :         // Object Data
     160            4 :         Array1D<SimplePVTModelStruct> tmpSimplePVTperf;
     161            4 :         Array1D<BIPVTModelStruct> tmpBIPVTperf;
     162              : 
     163              :         // first load the 'Simple' performance object info into temporary structure
     164            4 :         state.dataIPShortCut->cCurrentModuleObject = "SolarCollectorPerformance:PhotovoltaicThermal:Simple";
     165            4 :         int NumSimplePVTPerform = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, state.dataIPShortCut->cCurrentModuleObject);
     166            4 :         if (NumSimplePVTPerform > 0) GetPVTSimpleCollectorsInput(state, NumSimplePVTPerform, tmpSimplePVTperf);
     167              : 
     168              :         // load the 'BIPVT' performance object info into temporary structure
     169            4 :         state.dataIPShortCut->cCurrentModuleObject = "SolarCollectorPerformance:PhotovoltaicThermal:BIPVT";
     170            4 :         int NumBIPVTPerform = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, state.dataIPShortCut->cCurrentModuleObject);
     171            4 :         if (NumBIPVTPerform > 0) GetBIPVTCollectorsInput(state, NumBIPVTPerform, tmpBIPVTperf);
     172              : 
     173              :         // now get main PVT objects
     174            4 :         state.dataIPShortCut->cCurrentModuleObject = "SolarCollector:FlatPlate:PhotovoltaicThermal";
     175            8 :         state.dataPhotovoltaicThermalCollector->NumPVT =
     176            4 :             state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, state.dataIPShortCut->cCurrentModuleObject);
     177            4 :         if (state.dataPhotovoltaicThermalCollector->NumPVT > 0)
     178            4 :             GetMainPVTInput(
     179            4 :                 state, state.dataPhotovoltaicThermalCollector->NumPVT, state.dataPhotovoltaicThermalCollector->PVT, tmpSimplePVTperf, tmpBIPVTperf);
     180            4 :         if (allocated(tmpSimplePVTperf)) tmpSimplePVTperf.deallocate();
     181            4 :         if (allocated(tmpBIPVTperf)) tmpBIPVTperf.deallocate();
     182            4 :     }
     183              : 
     184            2 :     void GetPVTSimpleCollectorsInput(EnergyPlusData &state, int NumSimplePVTPerform, Array1D<SimplePVTModelStruct> &tmpSimplePVTperf)
     185              :     {
     186              :         // PURPOSE OF THIS SUBROUTINE:
     187              :         // Get input for PVT Simple objects
     188              : 
     189              :         static constexpr std::string_view routineName = "GetPVTSimpleCollectorsInput";
     190              : 
     191              :         int Item;                // Item to be "gotten"
     192              :         int NumAlphas;           // Number of Alphas for each GetObjectItem call
     193              :         int NumNumbers;          // Number of Numbers for each GetObjectItem call
     194              :         int IOStatus;            // Used in GetObjectItem
     195            2 :         bool ErrorsFound(false); // Set to true if errors in input, fatal at end of routine
     196              : 
     197            2 :         tmpSimplePVTperf.allocate(NumSimplePVTPerform);
     198            4 :         for (Item = 1; Item <= NumSimplePVTPerform; ++Item) {
     199            6 :             state.dataInputProcessing->inputProcessor->getObjectItem(state,
     200            2 :                                                                      state.dataIPShortCut->cCurrentModuleObject,
     201              :                                                                      Item,
     202            2 :                                                                      state.dataIPShortCut->cAlphaArgs,
     203              :                                                                      NumAlphas,
     204            2 :                                                                      state.dataIPShortCut->rNumericArgs,
     205              :                                                                      NumNumbers,
     206              :                                                                      IOStatus,
     207              :                                                                      _,
     208            2 :                                                                      state.dataIPShortCut->lAlphaFieldBlanks,
     209            2 :                                                                      state.dataIPShortCut->cAlphaFieldNames,
     210            2 :                                                                      state.dataIPShortCut->cNumericFieldNames);
     211              : 
     212            2 :             ErrorObjectHeader eoh{routineName, state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)};
     213              : 
     214            2 :             auto &thisTmpSimplePVTperf = tmpSimplePVTperf(Item);
     215            2 :             thisTmpSimplePVTperf.Name = state.dataIPShortCut->cAlphaArgs(1);
     216            2 :             thisTmpSimplePVTperf.ThermEfficMode =
     217            2 :                 static_cast<ThermEfficEnum>(getEnumValue(ThermEfficTypeNamesUC, Util::makeUPPER(state.dataIPShortCut->cAlphaArgs(2))));
     218            2 :             thisTmpSimplePVTperf.ThermalActiveFract = state.dataIPShortCut->rNumericArgs(1);
     219            2 :             thisTmpSimplePVTperf.ThermEffic = state.dataIPShortCut->rNumericArgs(2);
     220              : 
     221            2 :             if (thisTmpSimplePVTperf.ThermEfficMode == ThermEfficEnum::SCHEDULED) {
     222            0 :                 if (state.dataIPShortCut->lAlphaFieldBlanks(3)) {
     223            0 :                     ShowSevereEmptyField(state, eoh, state.dataIPShortCut->cAlphaFieldNames(3));
     224            0 :                     ErrorsFound = true;
     225            0 :                 } else if ((thisTmpSimplePVTperf.thermEffSched = Sched::GetSchedule(state, state.dataIPShortCut->cAlphaArgs(3))) == nullptr) {
     226            0 :                     ShowSevereItemNotFound(state, eoh, state.dataIPShortCut->cAlphaFieldNames(3), state.dataIPShortCut->cAlphaArgs(3));
     227            0 :                     ErrorsFound = true;
     228              :                 }
     229              :             }
     230            2 :             thisTmpSimplePVTperf.SurfEmissivity = state.dataIPShortCut->rNumericArgs(3);
     231              :         }
     232            2 :     }
     233              : 
     234            2 :     void GetBIPVTCollectorsInput(EnergyPlusData &state, int NumBIPVTPerform, Array1D<BIPVTModelStruct> &tmpBIPVTperf)
     235              :     {
     236              :         // PURPOSE OF THIS SUBROUTINE:
     237              :         // Get input for BIPVT objects
     238              :         static constexpr std::string_view routineName = "GetBIPVTCollectorsInput";
     239              : 
     240              :         int Item;       // Item to be "gotten"
     241              :         int NumAlphas;  // Number of Alphas for each GetObjectItem call
     242              :         int NumNumbers; // Number of Numbers for each GetObjectItem call
     243              :         int IOStatus;   // Used in GetObjectItem
     244              :         using DataSurfaces::OSCMData;
     245              : 
     246            2 :         tmpBIPVTperf.allocate(NumBIPVTPerform);
     247            4 :         for (Item = 1; Item <= NumBIPVTPerform; ++Item) {
     248            6 :             state.dataInputProcessing->inputProcessor->getObjectItem(state,
     249            2 :                                                                      state.dataIPShortCut->cCurrentModuleObject,
     250              :                                                                      Item,
     251            2 :                                                                      state.dataIPShortCut->cAlphaArgs,
     252              :                                                                      NumAlphas,
     253            2 :                                                                      state.dataIPShortCut->rNumericArgs,
     254              :                                                                      NumNumbers,
     255              :                                                                      IOStatus,
     256              :                                                                      _,
     257            2 :                                                                      state.dataIPShortCut->lAlphaFieldBlanks,
     258            2 :                                                                      state.dataIPShortCut->cAlphaFieldNames,
     259            2 :                                                                      state.dataIPShortCut->cNumericFieldNames);
     260              : 
     261            2 :             ErrorObjectHeader eoh{routineName, state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)};
     262              : 
     263            2 :             auto &thisTmpBIPVTperf = tmpBIPVTperf(Item);
     264            2 :             thisTmpBIPVTperf.Name = state.dataIPShortCut->cAlphaArgs(1);
     265            2 :             thisTmpBIPVTperf.OSCMName = state.dataIPShortCut->cAlphaArgs(2);
     266            2 :             int Found = Util::FindItemInList(thisTmpBIPVTperf.OSCMName, state.dataSurface->OSCM);
     267            2 :             if (Found == 0) {
     268            0 :                 ShowSevereError(state,
     269            0 :                                 format("GetBIPVTCollectorsInput: Invalid outside model name={}, object type={}, object name={}",
     270            0 :                                        thisTmpBIPVTperf.OSCMName,
     271            0 :                                        state.dataIPShortCut->cCurrentModuleObject,
     272            0 :                                        thisTmpBIPVTperf.Name));
     273              :             }
     274            2 :             thisTmpBIPVTperf.OSCMPtr = Found;
     275            2 :             thisTmpBIPVTperf.PVEffGapWidth = state.dataIPShortCut->rNumericArgs(1);
     276            2 :             thisTmpBIPVTperf.PVCellTransAbsProduct = state.dataIPShortCut->rNumericArgs(2);
     277            2 :             thisTmpBIPVTperf.BackMatTranAbsProduct = state.dataIPShortCut->rNumericArgs(3);
     278            2 :             thisTmpBIPVTperf.CladTranAbsProduct = state.dataIPShortCut->rNumericArgs(4);
     279            2 :             thisTmpBIPVTperf.PVAreaFract = state.dataIPShortCut->rNumericArgs(5);
     280            2 :             thisTmpBIPVTperf.PVCellAreaFract = state.dataIPShortCut->rNumericArgs(6);
     281            2 :             thisTmpBIPVTperf.PVRTop = state.dataIPShortCut->rNumericArgs(7);
     282            2 :             thisTmpBIPVTperf.PVRBot = state.dataIPShortCut->rNumericArgs(8);
     283            2 :             thisTmpBIPVTperf.PVGEmiss = state.dataIPShortCut->rNumericArgs(9);
     284            2 :             thisTmpBIPVTperf.BackMatEmiss = state.dataIPShortCut->rNumericArgs(10);
     285            2 :             thisTmpBIPVTperf.ThGlass = state.dataIPShortCut->rNumericArgs(11);
     286            2 :             thisTmpBIPVTperf.RIndGlass = state.dataIPShortCut->rNumericArgs(12);
     287            2 :             thisTmpBIPVTperf.ECoffGlass = state.dataIPShortCut->rNumericArgs(13);
     288              : 
     289            2 :             if (state.dataIPShortCut->lAlphaFieldBlanks(3)) {
     290            2 :                 thisTmpBIPVTperf.availSched = Sched::GetScheduleAlwaysOn(state);
     291            0 :             } else if ((thisTmpBIPVTperf.availSched = Sched::GetSchedule(state, state.dataIPShortCut->cAlphaArgs(3))) == nullptr) {
     292            0 :                 ShowSevereItemNotFound(state, eoh, state.dataIPShortCut->cAlphaFieldNames(3), state.dataIPShortCut->cAlphaArgs(3));
     293            0 :                 continue;
     294              :             }
     295              :         }
     296            2 :     }
     297              : 
     298            4 :     void GetMainPVTInput(EnergyPlusData &state,
     299              :                          int NumPVT,
     300              :                          Array1D<PVTCollectorStruct> &PVT,
     301              :                          Array1D<SimplePVTModelStruct> const &tmpSimplePVTperf,
     302              :                          Array1D<BIPVTModelStruct> const &tmpBIPVTperf)
     303              :     {
     304              :         // SUBROUTINE INFORMATION:
     305              :         //       AUTHOR         B. Griffith
     306              :         //       DATE WRITTEN   June 2008
     307              :         //       RE-ENGINEERED  na
     308              : 
     309              :         // PURPOSE OF THIS SUBROUTINE:
     310              :         // Get input for main PVT objects
     311              : 
     312              :         int Item;                // Item to be "gotten"
     313              :         int NumAlphas;           // Number of Alphas for each GetObjectItem call
     314              :         int NumNumbers;          // Number of Numbers for each GetObjectItem call
     315              :         int IOStatus;            // Used in GetObjectItem
     316            4 :         bool ErrorsFound(false); // Set to true if errors in input, fatal at end of routine
     317              : 
     318            4 :         PVT.allocate(NumPVT);
     319            8 :         for (Item = 1; Item <= NumPVT; ++Item) {
     320           12 :             state.dataInputProcessing->inputProcessor->getObjectItem(state,
     321            4 :                                                                      state.dataIPShortCut->cCurrentModuleObject,
     322              :                                                                      Item,
     323            4 :                                                                      state.dataIPShortCut->cAlphaArgs,
     324              :                                                                      NumAlphas,
     325            4 :                                                                      state.dataIPShortCut->rNumericArgs,
     326              :                                                                      NumNumbers,
     327              :                                                                      IOStatus,
     328              :                                                                      _,
     329            4 :                                                                      state.dataIPShortCut->lAlphaFieldBlanks,
     330            4 :                                                                      state.dataIPShortCut->cAlphaFieldNames,
     331            4 :                                                                      state.dataIPShortCut->cNumericFieldNames);
     332            4 :             auto &thisPVT = state.dataPhotovoltaicThermalCollector->PVT(Item);
     333            4 :             thisPVT.Name = state.dataIPShortCut->cAlphaArgs(1);
     334            4 :             thisPVT.Type = DataPlant::PlantEquipmentType::PVTSolarCollectorFlatPlate;
     335              : 
     336            4 :             thisPVT.SurfNum = Util::FindItemInList(state.dataIPShortCut->cAlphaArgs(2), state.dataSurface->Surface);
     337              :             // check surface
     338            4 :             if (thisPVT.SurfNum == 0) {
     339            0 :                 if (state.dataIPShortCut->lAlphaFieldBlanks(2)) {
     340            0 :                     ShowSevereError(state, format("Invalid {} = {}", state.dataIPShortCut->cAlphaFieldNames(2), state.dataIPShortCut->cAlphaArgs(2)));
     341            0 :                     ShowContinueError(state,
     342            0 :                                       format("Entered in {} = {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
     343              : 
     344            0 :                     ShowContinueError(state, "Surface name cannot be blank.");
     345              :                 } else {
     346            0 :                     ShowSevereError(state, format("Invalid {} = {}", state.dataIPShortCut->cAlphaFieldNames(2), state.dataIPShortCut->cAlphaArgs(2)));
     347            0 :                     ShowContinueError(state,
     348            0 :                                       format("Entered in {} = {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
     349            0 :                     ShowContinueError(state, "Surface was not found.");
     350              :                 }
     351            0 :                 ErrorsFound = true;
     352              :             } else {
     353              : 
     354            4 :                 if (!state.dataSurface->Surface(thisPVT.SurfNum).ExtSolar) {
     355            0 :                     ShowSevereError(state, format("Invalid {} = {}", state.dataIPShortCut->cAlphaFieldNames(2), state.dataIPShortCut->cAlphaArgs(2)));
     356            0 :                     ShowContinueError(state,
     357            0 :                                       format("Entered in {} = {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
     358            0 :                     ShowContinueError(state, "Surface must be exposed to solar.");
     359            0 :                     ErrorsFound = true;
     360              :                 }
     361              :                 // check surface orientation, warn if upside down
     362            4 :                 if ((state.dataSurface->Surface(thisPVT.SurfNum).Tilt < -95.0) || (state.dataSurface->Surface(thisPVT.SurfNum).Tilt > 95.0)) {
     363            0 :                     ShowWarningError(state,
     364            0 :                                      format("Suspected input problem with {} = {}",
     365            0 :                                             state.dataIPShortCut->cAlphaFieldNames(2),
     366            0 :                                             state.dataIPShortCut->cAlphaArgs(2)));
     367            0 :                     ShowContinueError(state,
     368            0 :                                       format("Entered in {} = {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
     369            0 :                     ShowContinueError(state, "Surface used for solar collector faces down");
     370            0 :                     ShowContinueError(
     371              :                         state,
     372            0 :                         format("Surface tilt angle (degrees from ground outward normal) = {:.2R}", state.dataSurface->Surface(thisPVT.SurfNum).Tilt));
     373              :                 }
     374              :             } // check surface
     375              : 
     376            4 :             if (state.dataIPShortCut->lAlphaFieldBlanks(3)) {
     377            0 :                 ShowSevereError(state, format("Invalid {} = {}", state.dataIPShortCut->cAlphaFieldNames(3), state.dataIPShortCut->cAlphaArgs(3)));
     378            0 :                 ShowContinueError(state,
     379            0 :                                   format("Entered in {} = {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
     380            0 :                 ShowContinueError(state, format("{}, name cannot be blank.", state.dataIPShortCut->cAlphaFieldNames(3)));
     381            0 :                 ErrorsFound = true;
     382              :             } else {
     383            4 :                 thisPVT.PVTModelName = state.dataIPShortCut->cAlphaArgs(3);
     384            4 :                 int ThisParamObj = Util::FindItemInList(thisPVT.PVTModelName, tmpSimplePVTperf);
     385            4 :                 if (ThisParamObj > 0) {
     386            2 :                     thisPVT.Simple = tmpSimplePVTperf(ThisParamObj); // entire structure assigned
     387              :                     // do one-time setups on input data
     388            2 :                     thisPVT.AreaCol = state.dataSurface->Surface(thisPVT.SurfNum).Area * thisPVT.Simple.ThermalActiveFract;
     389            2 :                     thisPVT.ModelType = PVTModelType::Simple;
     390              :                 } else {
     391            2 :                     ThisParamObj = Util::FindItemInList(PVT(Item).PVTModelName, tmpBIPVTperf);
     392            2 :                     if (ThisParamObj > 0) {
     393            2 :                         thisPVT.BIPVT = tmpBIPVTperf(ThisParamObj); // entire structure assigned
     394              :                         // do one-time setups on input data
     395            2 :                         thisPVT.AreaCol = state.dataSurface->Surface(thisPVT.SurfNum).Area;
     396            2 :                         thisPVT.ModelType = PVTModelType::BIPVT;
     397              :                     } else {
     398            0 :                         ShowSevereError(state,
     399            0 :                                         format("Invalid {} = {}", state.dataIPShortCut->cAlphaFieldNames(3), state.dataIPShortCut->cAlphaArgs(3)));
     400            0 :                         ShowContinueError(
     401            0 :                             state, format("Entered in {} = {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
     402            0 :                         ShowContinueError(state, format("{}, was not found.", state.dataIPShortCut->cAlphaFieldNames(3)));
     403            0 :                         ErrorsFound = true;
     404              :                     }
     405              :                 }
     406              : 
     407            4 :                 if (allocated(state.dataPhotovoltaic->PVarray)) { // then PV input gotten... but don't expect this to be true.
     408            4 :                     thisPVT.PVnum = Util::FindItemInList(state.dataIPShortCut->cAlphaArgs(4), state.dataPhotovoltaic->PVarray);
     409              :                     // check PV
     410            4 :                     if (thisPVT.PVnum == 0) {
     411            0 :                         ShowSevereError(state,
     412            0 :                                         format("Invalid {} = {}", state.dataIPShortCut->cAlphaFieldNames(4), state.dataIPShortCut->cAlphaArgs(4)));
     413            0 :                         ShowContinueError(
     414            0 :                             state, format("Entered in {} = {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
     415            0 :                         ErrorsFound = true;
     416              :                     } else {
     417            4 :                         thisPVT.PVname = state.dataIPShortCut->cAlphaArgs(4);
     418            4 :                         thisPVT.PVfound = true;
     419              :                     }
     420              :                 } else { // no PV or not yet gotten.
     421            0 :                     thisPVT.PVname = state.dataIPShortCut->cAlphaArgs(4);
     422            0 :                     thisPVT.PVfound = false;
     423              :                 }
     424              : 
     425            4 :                 if (Util::SameString(state.dataIPShortCut->cAlphaArgs(5), "Water")) {
     426            2 :                     thisPVT.WorkingFluidType = WorkingFluidEnum::LIQUID;
     427            2 :                 } else if (Util::SameString(state.dataIPShortCut->cAlphaArgs(5), "Air")) {
     428            2 :                     thisPVT.WorkingFluidType = WorkingFluidEnum::AIR;
     429              :                 } else {
     430            0 :                     if (state.dataIPShortCut->lAlphaFieldBlanks(5)) {
     431            0 :                         ShowSevereError(state,
     432            0 :                                         format("Invalid {} = {}", state.dataIPShortCut->cAlphaFieldNames(5), state.dataIPShortCut->cAlphaArgs(5)));
     433            0 :                         ShowContinueError(
     434            0 :                             state, format("Entered in {} = {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
     435            0 :                         ShowContinueError(state, format("{} field cannot be blank.", state.dataIPShortCut->cAlphaFieldNames(5)));
     436              :                     } else {
     437            0 :                         ShowSevereError(state,
     438            0 :                                         format("Invalid {} = {}", state.dataIPShortCut->cAlphaFieldNames(5), state.dataIPShortCut->cAlphaArgs(5)));
     439            0 :                         ShowContinueError(
     440            0 :                             state, format("Entered in {} = {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
     441              :                     }
     442            0 :                     ErrorsFound = true;
     443              :                 }
     444              : 
     445            4 :                 if (thisPVT.WorkingFluidType == WorkingFluidEnum::LIQUID) {
     446            2 :                     thisPVT.PlantInletNodeNum =
     447            2 :                         NodeInputManager::GetOnlySingleNode(state,
     448            2 :                                                             state.dataIPShortCut->cAlphaArgs(6),
     449              :                                                             ErrorsFound,
     450              :                                                             DataLoopNode::ConnectionObjectType::SolarCollectorFlatPlatePhotovoltaicThermal,
     451            2 :                                                             state.dataIPShortCut->cAlphaArgs(1),
     452              :                                                             DataLoopNode::NodeFluidType::Water,
     453              :                                                             DataLoopNode::ConnectionType::Inlet,
     454              :                                                             NodeInputManager::CompFluidStream::Primary,
     455              :                                                             DataLoopNode::ObjectIsNotParent);
     456            2 :                     thisPVT.PlantOutletNodeNum =
     457            4 :                         NodeInputManager::GetOnlySingleNode(state,
     458            2 :                                                             state.dataIPShortCut->cAlphaArgs(7),
     459              :                                                             ErrorsFound,
     460              :                                                             DataLoopNode::ConnectionObjectType::SolarCollectorFlatPlatePhotovoltaicThermal,
     461            2 :                                                             state.dataIPShortCut->cAlphaArgs(1),
     462              :                                                             DataLoopNode::NodeFluidType::Water,
     463              :                                                             DataLoopNode::ConnectionType::Outlet,
     464              :                                                             NodeInputManager::CompFluidStream::Primary,
     465              :                                                             DataLoopNode::ObjectIsNotParent);
     466            4 :                     BranchNodeConnections::TestCompSet(state,
     467            2 :                                                        state.dataIPShortCut->cCurrentModuleObject,
     468            2 :                                                        state.dataIPShortCut->cAlphaArgs(1),
     469            2 :                                                        state.dataIPShortCut->cAlphaArgs(6),
     470            2 :                                                        state.dataIPShortCut->cAlphaArgs(7),
     471              :                                                        "Water Nodes");
     472              : 
     473            2 :                     thisPVT.WPlantLoc.loopSideNum = DataPlant::LoopSideLocation::Invalid;
     474              :                 }
     475            4 :                 if (thisPVT.WorkingFluidType == WorkingFluidEnum::AIR) {
     476            2 :                     thisPVT.HVACInletNodeNum =
     477            2 :                         NodeInputManager::GetOnlySingleNode(state,
     478            2 :                                                             state.dataIPShortCut->cAlphaArgs(8),
     479              :                                                             ErrorsFound,
     480              :                                                             DataLoopNode::ConnectionObjectType::SolarCollectorFlatPlatePhotovoltaicThermal,
     481            2 :                                                             state.dataIPShortCut->cAlphaArgs(1),
     482              :                                                             DataLoopNode::NodeFluidType::Air,
     483              :                                                             DataLoopNode::ConnectionType::Inlet,
     484              :                                                             NodeInputManager::CompFluidStream::Primary,
     485              :                                                             DataLoopNode::ObjectIsNotParent);
     486            2 :                     thisPVT.HVACOutletNodeNum =
     487            4 :                         NodeInputManager::GetOnlySingleNode(state,
     488            2 :                                                             state.dataIPShortCut->cAlphaArgs(9),
     489              :                                                             ErrorsFound,
     490              :                                                             DataLoopNode::ConnectionObjectType::SolarCollectorFlatPlatePhotovoltaicThermal,
     491            2 :                                                             state.dataIPShortCut->cAlphaArgs(1),
     492              :                                                             DataLoopNode::NodeFluidType::Air,
     493              :                                                             DataLoopNode::ConnectionType::Outlet,
     494              :                                                             NodeInputManager::CompFluidStream::Primary,
     495              :                                                             DataLoopNode::ObjectIsNotParent);
     496              : 
     497            6 :                     BranchNodeConnections::TestCompSet(state,
     498            2 :                                                        state.dataIPShortCut->cCurrentModuleObject,
     499            2 :                                                        state.dataIPShortCut->cAlphaArgs(1),
     500            2 :                                                        state.dataIPShortCut->cAlphaArgs(8),
     501            2 :                                                        state.dataIPShortCut->cAlphaArgs(9),
     502              :                                                        "Air Nodes");
     503              :                 }
     504              : 
     505            4 :                 thisPVT.DesignVolFlowRate = state.dataIPShortCut->rNumericArgs(1);
     506            4 :                 thisPVT.SizingInit = true;
     507            4 :                 if (thisPVT.DesignVolFlowRate == DataSizing::AutoSize) {
     508            4 :                     thisPVT.DesignVolFlowRateWasAutoSized = true;
     509              :                 }
     510            4 :                 if (thisPVT.DesignVolFlowRate != DataSizing::AutoSize) {
     511              : 
     512            0 :                     if (thisPVT.WorkingFluidType == WorkingFluidEnum::LIQUID) {
     513            0 :                         PlantUtilities::RegisterPlantCompDesignFlow(state, thisPVT.PlantInletNodeNum, thisPVT.DesignVolFlowRate);
     514            0 :                     } else if (thisPVT.WorkingFluidType == WorkingFluidEnum::AIR) {
     515            0 :                         thisPVT.MaxMassFlowRate = thisPVT.DesignVolFlowRate * state.dataEnvrn->StdRhoAir;
     516              :                     }
     517            0 :                     thisPVT.SizingInit = false;
     518              :                 }
     519              :             }
     520              : 
     521            4 :             if (ErrorsFound) {
     522            0 :                 ShowFatalError(state, "Errors found in processing input for photovoltaic thermal collectors");
     523              :             }
     524              :         }
     525            4 :     }
     526              : 
     527            1 :     void PVTCollectorStruct::setupReportVars(EnergyPlusData &state)
     528              :     {
     529            2 :         SetupOutputVariable(state,
     530              :                             "Generator Produced Thermal Rate",
     531              :                             Constant::Units::W,
     532            1 :                             this->Report.ThermPower,
     533              :                             OutputProcessor::TimeStepType::System,
     534              :                             OutputProcessor::StoreType::Average,
     535            1 :                             this->Name);
     536              : 
     537            1 :         if (this->WorkingFluidType == WorkingFluidEnum::LIQUID) {
     538            2 :             SetupOutputVariable(state,
     539              :                                 "Generator Produced Thermal Energy",
     540              :                                 Constant::Units::J,
     541            1 :                                 this->Report.ThermEnergy,
     542              :                                 OutputProcessor::TimeStepType::System,
     543              :                                 OutputProcessor::StoreType::Sum,
     544            1 :                                 this->Name,
     545              :                                 Constant::eResource::SolarWater,
     546              :                                 OutputProcessor::Group::Plant,
     547              :                                 OutputProcessor::EndUseCat::HeatProduced);
     548              : 
     549            0 :         } else if (this->WorkingFluidType == WorkingFluidEnum::AIR) {
     550            0 :             SetupOutputVariable(state,
     551              :                                 "Generator Produced Thermal Energy",
     552              :                                 Constant::Units::J,
     553            0 :                                 this->Report.ThermEnergy,
     554              :                                 OutputProcessor::TimeStepType::System,
     555              :                                 OutputProcessor::StoreType::Sum,
     556            0 :                                 this->Name,
     557              :                                 Constant::eResource::SolarAir,
     558              :                                 OutputProcessor::Group::HVAC,
     559              :                                 OutputProcessor::EndUseCat::HeatProduced);
     560              : 
     561            0 :             SetupOutputVariable(state,
     562              :                                 "Generator PVT Fluid Bypass Status",
     563              :                                 Constant::Units::None,
     564            0 :                                 this->Report.BypassStatus,
     565              :                                 OutputProcessor::TimeStepType::System,
     566              :                                 OutputProcessor::StoreType::Average,
     567            0 :                                 this->Name);
     568              :         }
     569              : 
     570            2 :         SetupOutputVariable(state,
     571              :                             "Generator PVT Fluid Inlet Temperature",
     572              :                             Constant::Units::C,
     573            1 :                             this->Report.TinletWorkFluid,
     574              :                             OutputProcessor::TimeStepType::System,
     575              :                             OutputProcessor::StoreType::Average,
     576            1 :                             this->Name);
     577              : 
     578            2 :         SetupOutputVariable(state,
     579              :                             "Generator PVT Fluid Outlet Temperature",
     580              :                             Constant::Units::C,
     581            1 :                             this->Report.ToutletWorkFluid,
     582              :                             OutputProcessor::TimeStepType::System,
     583              :                             OutputProcessor::StoreType::Average,
     584            1 :                             this->Name);
     585              : 
     586            2 :         SetupOutputVariable(state,
     587              :                             "Generator PVT Fluid Mass Flow Rate",
     588              :                             Constant::Units::kg_s,
     589            1 :                             this->Report.MdotWorkFluid,
     590              :                             OutputProcessor::TimeStepType::System,
     591              :                             OutputProcessor::StoreType::Average,
     592            1 :                             this->Name);
     593            1 :     }
     594              : 
     595            1 :     void PVTCollectorStruct::initialize(EnergyPlusData &state, bool const FirstHVACIteration)
     596              :     {
     597              : 
     598              :         // SUBROUTINE INFORMATION:
     599              :         //       AUTHOR         B. Griffith
     600              :         //       DATE WRITTEN   June 2008
     601              :         //       MODIFIED       B. Griffith, May 2009, EMS setpoint check
     602              :         //       RE-ENGINEERED  na
     603              : 
     604              :         // PURPOSE OF THIS SUBROUTINE:
     605              :         // init for PVT
     606              : 
     607              :         static constexpr std::string_view RoutineName("InitPVTcollectors");
     608              : 
     609              :         // Do the one time initializations
     610            1 :         this->oneTimeInit(state);
     611              : 
     612              :         // finish set up of PV, because PV get-input follows PVT's get input.
     613            1 :         if (!this->PVfound) {
     614            0 :             if (allocated(state.dataPhotovoltaic->PVarray)) {
     615            0 :                 this->PVnum = Util::FindItemInList(this->PVname, state.dataPhotovoltaic->PVarray);
     616            0 :                 if (this->PVnum == 0) {
     617            0 :                     ShowSevereError(state, format("Invalid name for photovoltaic generator = {}", this->PVname));
     618            0 :                     ShowContinueError(state, format("Entered in flat plate photovoltaic-thermal collector = {}", this->Name));
     619              :                 } else {
     620            0 :                     this->PVfound = true;
     621              :                 }
     622              :             } else {
     623            0 :                 if ((!state.dataGlobal->BeginEnvrnFlag) && (!FirstHVACIteration)) {
     624            0 :                     ShowSevereError(state, "Photovoltaic generators are missing for Photovoltaic Thermal modeling");
     625            0 :                     ShowContinueError(state, format("Needed for flat plate photovoltaic-thermal collector = {}", this->Name));
     626              :                 }
     627              :             }
     628              :         }
     629              : 
     630            1 :         if (!state.dataGlobal->SysSizingCalc && this->MySetPointCheckFlag && state.dataHVACGlobal->DoSetPointTest) {
     631            0 :             for (int PVTindex = 1; PVTindex <= state.dataPhotovoltaicThermalCollector->NumPVT; ++PVTindex) {
     632            0 :                 if (state.dataPhotovoltaicThermalCollector->PVT(PVTindex).WorkingFluidType == WorkingFluidEnum::AIR) {
     633            0 :                     if (state.dataLoopNodes->Node(state.dataPhotovoltaicThermalCollector->PVT(PVTindex).HVACOutletNodeNum).TempSetPoint ==
     634              :                         DataLoopNode::SensedNodeFlagValue) {
     635            0 :                         if (!state.dataGlobal->AnyEnergyManagementSystemInModel) {
     636            0 :                             ShowSevereError(state, "Missing temperature setpoint for PVT outlet node  ");
     637            0 :                             ShowContinueError(state,
     638            0 :                                               format("Add a setpoint manager to outlet node of PVT named {}",
     639            0 :                                                      state.dataPhotovoltaicThermalCollector->PVT(PVTindex).Name));
     640            0 :                             state.dataHVACGlobal->SetPointErrorFlag = true;
     641              :                         } else {
     642              :                             // need call to EMS to check node
     643            0 :                             EMSManager::CheckIfNodeSetPointManagedByEMS(state,
     644            0 :                                                                         state.dataPhotovoltaicThermalCollector->PVT(PVTindex).HVACOutletNodeNum,
     645              :                                                                         HVAC::CtrlVarType::Temp,
     646            0 :                                                                         state.dataHVACGlobal->SetPointErrorFlag);
     647            0 :                             if (state.dataHVACGlobal->SetPointErrorFlag) {
     648            0 :                                 ShowSevereError(state, "Missing temperature setpoint for PVT outlet node  ");
     649            0 :                                 ShowContinueError(state,
     650            0 :                                                   format("Add a setpoint manager to outlet node of PVT named {}",
     651            0 :                                                          state.dataPhotovoltaicThermalCollector->PVT(PVTindex).Name));
     652            0 :                                 ShowContinueError(state, "  or use an EMS actuator to establish a setpoint at the outlet node of PVT");
     653              :                             }
     654              :                         }
     655              :                     }
     656              :                 }
     657              :             }
     658            0 :             this->MySetPointCheckFlag = false;
     659              :         }
     660              : 
     661            1 :         if (!state.dataGlobal->SysSizingCalc && this->SizingInit && (this->WorkingFluidType == WorkingFluidEnum::AIR)) {
     662            0 :             this->size(state);
     663              :         }
     664              : 
     665            1 :         int InletNode = 0;
     666            1 :         int OutletNode = 0;
     667              : 
     668            1 :         switch (this->WorkingFluidType) {
     669            1 :         case WorkingFluidEnum::LIQUID: {
     670            1 :             InletNode = this->PlantInletNodeNum;
     671            1 :             OutletNode = this->PlantOutletNodeNum;
     672            1 :         } break;
     673            0 :         case WorkingFluidEnum::AIR: {
     674            0 :             InletNode = this->HVACInletNodeNum;
     675            0 :             OutletNode = this->HVACOutletNodeNum;
     676            0 :         } break;
     677            0 :         default: {
     678            0 :             assert(false);
     679              :         } break;
     680              :         }
     681              : 
     682            1 :         if (state.dataGlobal->BeginEnvrnFlag && this->EnvrnInit) {
     683              : 
     684            0 :             this->MassFlowRate = 0.0;
     685            0 :             this->BypassDamperOff = true;
     686            0 :             this->CoolingUseful = false;
     687            0 :             this->HeatingUseful = false;
     688            0 :             this->Simple.LastCollectorTemp = 0.0;
     689            0 :             this->BIPVT.LastCollectorTemp = 0.0;
     690            0 :             this->Report.ThermPower = 0.0;
     691            0 :             this->Report.ThermHeatGain = 0.0;
     692            0 :             this->Report.ThermHeatLoss = 0.0;
     693            0 :             this->Report.ThermEnergy = 0.0;
     694            0 :             this->Report.MdotWorkFluid = 0.0;
     695            0 :             this->Report.TinletWorkFluid = 0.0;
     696            0 :             this->Report.ToutletWorkFluid = 0.0;
     697            0 :             this->Report.BypassStatus = 0.0;
     698              : 
     699            0 :             switch (this->WorkingFluidType) {
     700            0 :             case WorkingFluidEnum::LIQUID: {
     701              : 
     702            0 :                 Real64 rho = state.dataPlnt->PlantLoop(this->WPlantLoc.loopNum).glycol->getDensity(state, Constant::HWInitConvTemp, RoutineName);
     703              : 
     704            0 :                 this->MaxMassFlowRate = this->DesignVolFlowRate * rho;
     705              : 
     706            0 :                 PlantUtilities::InitComponentNodes(state, 0.0, this->MaxMassFlowRate, InletNode, OutletNode);
     707              : 
     708            0 :                 this->Simple.LastCollectorTemp = 23.0;
     709            0 :             } break;
     710            0 :             case WorkingFluidEnum::AIR: {
     711            0 :                 this->Simple.LastCollectorTemp = 23.0;
     712            0 :                 this->BIPVT.LastCollectorTemp = 23.0;
     713            0 :             } break;
     714            0 :             default:
     715            0 :                 break;
     716              :             }
     717              : 
     718            0 :             this->EnvrnInit = false;
     719              :         }
     720            1 :         if (!state.dataGlobal->BeginEnvrnFlag) this->EnvrnInit = true;
     721              : 
     722            1 :         switch (this->WorkingFluidType) {
     723            1 :         case WorkingFluidEnum::LIQUID: {
     724              :             // heating only right now, so control flow requests based on incident solar;
     725            1 :             if (state.dataHeatBal->SurfQRadSWOutIncident(this->SurfNum) > DataPhotovoltaics::MinIrradiance) {
     726            0 :                 this->MassFlowRate = this->MaxMassFlowRate;
     727              :             } else {
     728            1 :                 this->MassFlowRate = 0.0;
     729              :             }
     730              : 
     731            1 :             PlantUtilities::SetComponentFlowRate(state, this->MassFlowRate, InletNode, OutletNode, this->WPlantLoc);
     732            1 :         } break;
     733            0 :         case WorkingFluidEnum::AIR: {
     734            0 :             this->MassFlowRate = state.dataLoopNodes->Node(InletNode).MassFlowRate;
     735            0 :         } break;
     736            0 :         default:
     737            0 :             break;
     738              :         }
     739            1 :     }
     740              : 
     741            1 :     void PVTCollectorStruct::size(EnergyPlusData &state)
     742              :     {
     743              : 
     744              :         // SUBROUTINE INFORMATION:
     745              :         //       AUTHOR         Brent Griffith
     746              :         //       DATE WRITTEN   August 2008
     747              :         //       MODIFIED       November 2013 Daeho Kang, add component sizing table entries
     748              :         //       RE-ENGINEERED  na
     749              : 
     750              :         // PURPOSE OF THIS SUBROUTINE:
     751              :         // This subroutine is for sizing PVT flow rates that
     752              :         // have not been specified in the input.
     753              : 
     754              :         // METHODOLOGY EMPLOYED:
     755              :         // Obtains hot water flow rate from the plant sizing array.
     756              : 
     757              :         bool SizingDesRunThisAirSys; // true if a particular air system had a Sizing:System object and system sizing done
     758              : 
     759              :         // Indicator to hardsize and no sizing run
     760            1 :         bool HardSizeNoDesRun = !(state.dataSize->SysSizingRunDone || state.dataSize->ZoneSizingRunDone);
     761              : 
     762            1 :         if (state.dataSize->CurSysNum > 0) {
     763            0 :             CheckThisAirSystemForSizing(state, state.dataSize->CurSysNum, SizingDesRunThisAirSys);
     764              :         } else {
     765            1 :             SizingDesRunThisAirSys = false;
     766              :         }
     767              : 
     768            1 :         Real64 DesignVolFlowRateDes = 0.0; // Autosize design volume flow for reporting
     769            1 :         bool ErrorsFound = false;
     770              : 
     771            1 :         if (this->WorkingFluidType == WorkingFluidEnum::LIQUID) {
     772              : 
     773            1 :             if (!allocated(state.dataSize->PlantSizData)) return;
     774            0 :             if (!allocated(state.dataPlnt->PlantLoop)) return;
     775            0 :             int PltSizNum = 0; // Plant Sizing index corresponding to CurLoopNum
     776              : 
     777            0 :             if (this->WPlantLoc.loopNum > 0) {
     778            0 :                 PltSizNum = state.dataPlnt->PlantLoop(this->WPlantLoc.loopNum).PlantSizNum;
     779              :             }
     780            0 :             if (this->WPlantLoc.loopSideNum == DataPlant::LoopSideLocation::Supply) {
     781            0 :                 if (PltSizNum > 0) {
     782            0 :                     if (state.dataSize->PlantSizData(PltSizNum).DesVolFlowRate >= HVAC::SmallWaterVolFlow) {
     783            0 :                         DesignVolFlowRateDes = state.dataSize->PlantSizData(PltSizNum).DesVolFlowRate;
     784              :                     } else {
     785            0 :                         DesignVolFlowRateDes = 0.0;
     786              :                     }
     787              :                 } else {
     788            0 :                     if (this->DesignVolFlowRateWasAutoSized) {
     789            0 :                         if (state.dataPlnt->PlantFirstSizesOkayToFinalize) {
     790            0 :                             ShowSevereError(state, "Autosizing of PVT solar collector design flow rate requires a Sizing:Plant object");
     791            0 :                             ShowContinueError(state, format("Occurs in PVT object={}", this->Name));
     792            0 :                             ErrorsFound = true;
     793              :                         }
     794              :                     } else { // Hardsized
     795            0 :                         if (state.dataPlnt->PlantFinalSizesOkayToReport && this->DesignVolFlowRate > 0.0) {
     796            0 :                             BaseSizer::reportSizerOutput(state,
     797              :                                                          "SolarCollector:FlatPlate:PhotovoltaicThermal",
     798              :                                                          this->Name,
     799              :                                                          "User-Specified Design Flow Rate [m3/s]",
     800              :                                                          this->DesignVolFlowRate);
     801              :                         }
     802              :                     }
     803              :                 }
     804            0 :             } else if (this->WPlantLoc.loopSideNum == DataPlant::LoopSideLocation::Demand) {
     805            0 :                 Real64 constexpr SimplePVTWaterSizeFactor(1.905e-5); // [ m3/s/m2 ] average of collectors in SolarCollectors.idf
     806            0 :                 DesignVolFlowRateDes = this->AreaCol * SimplePVTWaterSizeFactor;
     807              :             }
     808            0 :             if (this->DesignVolFlowRateWasAutoSized) {
     809            0 :                 this->DesignVolFlowRate = DesignVolFlowRateDes;
     810            0 :                 if (state.dataPlnt->PlantFinalSizesOkayToReport) {
     811            0 :                     BaseSizer::reportSizerOutput(state,
     812              :                                                  "SolarCollector:FlatPlate:PhotovoltaicThermal",
     813              :                                                  this->Name,
     814              :                                                  "Design Size Design Flow Rate [m3/s]",
     815              :                                                  DesignVolFlowRateDes);
     816              :                 }
     817            0 :                 if (state.dataPlnt->PlantFirstSizesOkayToReport) {
     818            0 :                     BaseSizer::reportSizerOutput(state,
     819              :                                                  "SolarCollector:FlatPlate:PhotovoltaicThermal",
     820              :                                                  this->Name,
     821              :                                                  "Initial Design Size Design Flow Rate [m3/s]",
     822              :                                                  DesignVolFlowRateDes);
     823              :                 }
     824            0 :                 PlantUtilities::RegisterPlantCompDesignFlow(state, this->PlantInletNodeNum, this->DesignVolFlowRate);
     825              : 
     826              :             } else { // Hardsized with sizing data
     827            0 :                 if (this->DesignVolFlowRate > 0.0 && DesignVolFlowRateDes > 0.0 && state.dataPlnt->PlantFinalSizesOkayToReport) {
     828            0 :                     Real64 DesignVolFlowRateUser = this->DesignVolFlowRate;
     829            0 :                     BaseSizer::reportSizerOutput(state,
     830              :                                                  "SolarCollector:FlatPlate:PhotovoltaicThermal",
     831              :                                                  this->Name,
     832              :                                                  "Design Size Design Flow Rate [m3/s]",
     833              :                                                  DesignVolFlowRateDes,
     834              :                                                  "User-Specified Design Flow Rate [m3/s]",
     835              :                                                  DesignVolFlowRateUser);
     836            0 :                     if (state.dataGlobal->DisplayExtraWarnings) {
     837            0 :                         if ((std::abs(DesignVolFlowRateDes - DesignVolFlowRateUser) / DesignVolFlowRateUser) >
     838            0 :                             state.dataSize->AutoVsHardSizingThreshold) {
     839            0 :                             ShowMessage(state, format("SizeSolarCollector: Potential issue with equipment sizing for {}", this->Name));
     840            0 :                             ShowContinueError(state, format("User-Specified Design Flow Rate of {:.5R} [W]", DesignVolFlowRateUser));
     841            0 :                             ShowContinueError(state, format("differs from Design Size Design Flow Rate of {:.5R} [W]", DesignVolFlowRateDes));
     842            0 :                             ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
     843            0 :                             ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
     844              :                         }
     845              :                     }
     846              :                 }
     847              :             }
     848              :         } // plant component
     849              : 
     850            0 :         if (this->WorkingFluidType == WorkingFluidEnum::AIR) {
     851              : 
     852            0 :             if (state.dataSize->CurSysNum > 0) {
     853            0 :                 if (!this->DesignVolFlowRateWasAutoSized && !SizingDesRunThisAirSys) { // Simulation continue
     854            0 :                     HardSizeNoDesRun = true;
     855            0 :                     if (this->DesignVolFlowRate > 0.0) {
     856            0 :                         BaseSizer::reportSizerOutput(state,
     857              :                                                      "SolarCollector:FlatPlate:PhotovoltaicThermal",
     858              :                                                      this->Name,
     859              :                                                      "User-Specified Design Flow Rate [m3/s]",
     860              :                                                      this->DesignVolFlowRate);
     861              :                     }
     862              :                 } else {
     863            0 :                     CheckSysSizing(state, "SolarCollector:FlatPlate:PhotovoltaicThermal", this->Name);
     864            0 :                     auto const &thisFinalSysSizing = state.dataSize->FinalSysSizing(state.dataSize->CurSysNum);
     865            0 :                     if (state.dataSize->CurOASysNum > 0) {
     866            0 :                         DesignVolFlowRateDes = thisFinalSysSizing.DesOutAirVolFlow;
     867              :                     } else {
     868            0 :                         switch (state.dataSize->CurDuctType) {
     869            0 :                         case HVAC::AirDuctType::Main: {
     870            0 :                             DesignVolFlowRateDes = thisFinalSysSizing.SysAirMinFlowRat * thisFinalSysSizing.DesMainVolFlow;
     871            0 :                         } break;
     872            0 :                         case HVAC::AirDuctType::Cooling: {
     873            0 :                             DesignVolFlowRateDes = thisFinalSysSizing.SysAirMinFlowRat * thisFinalSysSizing.DesCoolVolFlow;
     874            0 :                         } break;
     875            0 :                         case HVAC::AirDuctType::Heating: {
     876            0 :                             DesignVolFlowRateDes = thisFinalSysSizing.DesHeatVolFlow;
     877            0 :                         } break;
     878            0 :                         default: {
     879            0 :                             DesignVolFlowRateDes = thisFinalSysSizing.DesMainVolFlow;
     880            0 :                         } break;
     881              :                         }
     882              :                     }
     883            0 :                     Real64 DesMassFlow = state.dataEnvrn->StdRhoAir * DesignVolFlowRateDes;
     884            0 :                     this->MaxMassFlowRate = DesMassFlow;
     885              :                 }
     886            0 :                 if (!HardSizeNoDesRun) {
     887            0 :                     if (this->DesignVolFlowRateWasAutoSized) {
     888            0 :                         this->DesignVolFlowRate = DesignVolFlowRateDes;
     889            0 :                         BaseSizer::reportSizerOutput(state,
     890              :                                                      "SolarCollector:FlatPlate:PhotovoltaicThermal",
     891              :                                                      this->Name,
     892              :                                                      "Design Size Design Flow Rate [m3/s]",
     893              :                                                      DesignVolFlowRateDes);
     894            0 :                         this->SizingInit = false;
     895              :                     } else {
     896            0 :                         if (this->DesignVolFlowRate > 0.0 && DesignVolFlowRateDes > 0.0) {
     897            0 :                             Real64 DesignVolFlowRateUser = this->DesignVolFlowRate;
     898            0 :                             BaseSizer::reportSizerOutput(state,
     899              :                                                          "SolarCollector:FlatPlate:PhotovoltaicThermal",
     900              :                                                          this->Name,
     901              :                                                          "Design Size Design Flow Rate [m3/s]",
     902              :                                                          DesignVolFlowRateDes,
     903              :                                                          "User-Specified Design Flow Rate [m3/s]",
     904              :                                                          DesignVolFlowRateUser);
     905            0 :                             if (state.dataGlobal->DisplayExtraWarnings) {
     906            0 :                                 if ((std::abs(DesignVolFlowRateDes - DesignVolFlowRateUser) / DesignVolFlowRateUser) >
     907            0 :                                     state.dataSize->AutoVsHardSizingThreshold) {
     908            0 :                                     ShowMessage(state, format("SizeSolarCollector: Potential issue with equipment sizing for {}", this->Name));
     909            0 :                                     ShowContinueError(state, format("User-Specified Design Flow Rate of {:.5R} [W]", DesignVolFlowRateUser));
     910            0 :                                     ShowContinueError(state, format("differs from Design Size Design Flow Rate of {:.5R} [W]", DesignVolFlowRateDes));
     911            0 :                                     ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
     912            0 :                                     ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
     913              :                                 }
     914              :                             }
     915              :                         }
     916              :                     }
     917              :                 }
     918            0 :             } else if (state.dataSize->CurZoneEqNum > 0) {
     919              :                 // PVT is not currently for zone equipment, should not come here.
     920              :             }
     921              :         }
     922              : 
     923            0 :         if (ErrorsFound) {
     924            0 :             ShowFatalError(state, "Preceding sizing errors cause program termination");
     925              :         }
     926              :     }
     927              : 
     928            0 :     void PVTCollectorStruct::control(EnergyPlusData &state)
     929              :     {
     930              : 
     931              :         // SUBROUTINE INFORMATION:
     932              :         //       AUTHOR         Brent Griffith
     933              :         //       DATE WRITTEN   August 2008
     934              :         //       RE-ENGINEERED  na
     935              : 
     936              :         // PURPOSE OF THIS SUBROUTINE:
     937              :         // make control decisions for PVT collector
     938              : 
     939              :         // METHODOLOGY EMPLOYED:
     940              :         // decide if PVT should be in cooling or heat mode and if it should be bypassed or not
     941              : 
     942            0 :         if (this->WorkingFluidType == WorkingFluidEnum::AIR) {
     943            0 :             if ((this->ModelType == PVTModelType::Simple) || (this->ModelType == PVTModelType::BIPVT)) {
     944            0 :                 if (state.dataHeatBal->SurfQRadSWOutIncident(this->SurfNum) > DataPhotovoltaics::MinIrradiance) {
     945              :                     // is heating wanted?
     946              :                     //  Outlet node is required to have a setpoint.
     947            0 :                     if (state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint > state.dataLoopNodes->Node(this->HVACInletNodeNum).Temp) {
     948            0 :                         this->HeatingUseful = true;
     949            0 :                         this->CoolingUseful = false;
     950            0 :                         this->BypassDamperOff = true;
     951              :                     } else {
     952            0 :                         this->HeatingUseful = false;
     953            0 :                         this->CoolingUseful = true;
     954            0 :                         this->BypassDamperOff = false;
     955              :                     }
     956              :                 } else {
     957              :                     // is cooling wanted?
     958            0 :                     if (state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint < state.dataLoopNodes->Node(this->HVACInletNodeNum).Temp) {
     959            0 :                         this->CoolingUseful = true;
     960            0 :                         this->HeatingUseful = false;
     961            0 :                         this->BypassDamperOff = true;
     962              :                     } else {
     963            0 :                         this->CoolingUseful = false;
     964            0 :                         this->HeatingUseful = true;
     965            0 :                         this->BypassDamperOff = false;
     966              :                     }
     967              :                 }
     968              :             }
     969              : 
     970            0 :         } else if (this->WorkingFluidType == WorkingFluidEnum::LIQUID) {
     971            0 :             if (this->ModelType == PVTModelType::Simple) {
     972            0 :                 if (state.dataHeatBal->SurfQRadSWOutIncident(this->SurfNum) > DataPhotovoltaics::MinIrradiance) {
     973              :                     // is heating wanted?
     974            0 :                     this->HeatingUseful = true;
     975            0 :                     this->BypassDamperOff = true;
     976              :                 } else {
     977              :                     // is cooling wanted?
     978            0 :                     this->CoolingUseful = false;
     979            0 :                     this->BypassDamperOff = false;
     980              :                 }
     981              :             }
     982              :         }
     983            0 :     }
     984              : 
     985            0 :     void PVTCollectorStruct::calculate(EnergyPlusData &state)
     986              :     {
     987              : 
     988              :         // SUBROUTINE INFORMATION:
     989              :         //       AUTHOR         Brent Griffith
     990              :         //       DATE WRITTEN   August 2008
     991              :         //       RE-ENGINEERED  na
     992              : 
     993              :         // PURPOSE OF THIS SUBROUTINE:
     994              :         // Calculate PVT collector thermal performance
     995              : 
     996              :         // METHODOLOGY EMPLOYED:
     997              : 
     998            0 :         if (this->ModelType == PVTModelType::Simple) {
     999            0 :             calculateSimplePVT(state);
    1000            0 :         } else if (this->ModelType == PVTModelType::BIPVT) {
    1001            0 :             calculateBIPVT(state);
    1002              :         }
    1003            0 :     }
    1004              : 
    1005            0 :     void PVTCollectorStruct::calculateSimplePVT(EnergyPlusData &state)
    1006              :     {
    1007              : 
    1008              :         // SUBROUTINE INFORMATION:
    1009              :         //       AUTHOR         Brent Griffith
    1010              :         //       DATE WRITTEN   August 2008
    1011              :         //       RE-ENGINEERED  na
    1012              : 
    1013              :         // PURPOSE OF THIS SUBROUTINE:
    1014              :         // Calculate PVT Simple collector thermal
    1015              : 
    1016              :         // METHODOLOGY EMPLOYED:
    1017              :         // Current model is "simple" fixed efficiency and simple night sky balance for cooling
    1018              : 
    1019              :         static constexpr std::string_view RoutineName("CalcSimplePVTcollectors");
    1020              : 
    1021            0 :         int InletNode(0);
    1022              : 
    1023            0 :         switch (this->WorkingFluidType) {
    1024            0 :         case WorkingFluidEnum::LIQUID: {
    1025            0 :             InletNode = this->PlantInletNodeNum;
    1026            0 :         } break;
    1027            0 :         case WorkingFluidEnum::AIR: {
    1028            0 :             InletNode = this->HVACInletNodeNum;
    1029            0 :         } break;
    1030            0 :         default:
    1031            0 :             break;
    1032              :         }
    1033              : 
    1034            0 :         Real64 mdot = this->MassFlowRate;
    1035            0 :         Real64 Tinlet = state.dataLoopNodes->Node(InletNode).Temp;
    1036              : 
    1037            0 :         Real64 BypassFraction(0.0);
    1038            0 :         Real64 PotentialOutletTemp(0.0);
    1039              : 
    1040            0 :         if (this->HeatingUseful && this->BypassDamperOff && (mdot > 0.0)) {
    1041              : 
    1042            0 :             Real64 Eff(0.0);
    1043              : 
    1044            0 :             switch (this->Simple.ThermEfficMode) {
    1045            0 :             case ThermEfficEnum::FIXED: {
    1046            0 :                 Eff = this->Simple.ThermEffic;
    1047            0 :             } break;
    1048            0 :             case ThermEfficEnum::SCHEDULED: {
    1049            0 :                 Eff = this->Simple.thermEffSched->getCurrentVal();
    1050            0 :                 this->Simple.ThermEffic = Eff;
    1051            0 :             } break;
    1052            0 :             default:
    1053            0 :                 break;
    1054              :             }
    1055              : 
    1056            0 :             Real64 PotentialHeatGain = state.dataHeatBal->SurfQRadSWOutIncident(this->SurfNum) * Eff * this->AreaCol;
    1057              : 
    1058            0 :             if (this->WorkingFluidType == WorkingFluidEnum::AIR) {
    1059            0 :                 Real64 Winlet = state.dataLoopNodes->Node(InletNode).HumRat;
    1060            0 :                 Real64 CpInlet = Psychrometrics::PsyCpAirFnW(Winlet);
    1061            0 :                 if (mdot * CpInlet > 0.0) {
    1062            0 :                     PotentialOutletTemp = Tinlet + PotentialHeatGain / (mdot * CpInlet);
    1063              :                 } else {
    1064            0 :                     PotentialOutletTemp = Tinlet;
    1065              :                 }
    1066              :                 // now compare heating potential to setpoint and figure bypass fraction
    1067            0 :                 if (PotentialOutletTemp > state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint) { // need to modulate
    1068            0 :                     if (Tinlet != PotentialOutletTemp) {
    1069            0 :                         BypassFraction =
    1070            0 :                             (state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint - PotentialOutletTemp) / (Tinlet - PotentialOutletTemp);
    1071              :                     } else {
    1072            0 :                         BypassFraction = 0.0;
    1073              :                     }
    1074            0 :                     BypassFraction = max(0.0, BypassFraction);
    1075            0 :                     PotentialOutletTemp = state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint;
    1076            0 :                     PotentialHeatGain = mdot * Psychrometrics::PsyCpAirFnW(Winlet) * (PotentialOutletTemp - Tinlet);
    1077              : 
    1078              :                 } else {
    1079            0 :                     BypassFraction = 0.0;
    1080              :                 }
    1081            0 :             } else if (this->WorkingFluidType == WorkingFluidEnum::LIQUID) {
    1082            0 :                 Real64 CpInlet = Psychrometrics::CPHW(Tinlet);
    1083            0 :                 if (mdot * CpInlet != 0.0) { // protect divide by zero
    1084            0 :                     PotentialOutletTemp = Tinlet + PotentialHeatGain / (mdot * CpInlet);
    1085              :                 } else {
    1086            0 :                     PotentialOutletTemp = Tinlet;
    1087              :                 }
    1088            0 :                 BypassFraction = 0.0;
    1089              :             }
    1090              : 
    1091            0 :             this->Report.ThermHeatGain = PotentialHeatGain;
    1092            0 :             this->Report.ThermPower = this->Report.ThermHeatGain;
    1093            0 :             this->Report.ThermEnergy = this->Report.ThermPower * state.dataHVACGlobal->TimeStepSysSec;
    1094            0 :             this->Report.ThermHeatLoss = 0.0;
    1095            0 :             this->Report.TinletWorkFluid = Tinlet;
    1096            0 :             this->Report.MdotWorkFluid = mdot;
    1097            0 :             this->Report.ToutletWorkFluid = PotentialOutletTemp;
    1098            0 :             this->Report.BypassStatus = BypassFraction;
    1099              : 
    1100            0 :         } else if (this->CoolingUseful && this->BypassDamperOff && (mdot > 0.0)) {
    1101              :             // calculate cooling using energy balance
    1102              : 
    1103            0 :             Real64 HrGround(0.0);
    1104            0 :             Real64 HrAir(0.0);
    1105            0 :             Real64 HcExt(0.0);
    1106            0 :             Real64 HrSky(0.0);
    1107            0 :             Real64 HrSrdSurf(0.0);
    1108              : 
    1109            0 :             Convect::InitExtConvCoeff(state,
    1110              :                                       this->SurfNum,
    1111              :                                       0.0,
    1112              :                                       Material::SurfaceRoughness::VerySmooth,
    1113              :                                       this->Simple.SurfEmissivity,
    1114              :                                       this->Simple.LastCollectorTemp,
    1115              :                                       HcExt,
    1116              :                                       HrSky,
    1117              :                                       HrGround,
    1118              :                                       HrAir,
    1119              :                                       HrSrdSurf);
    1120              : 
    1121            0 :             Real64 WetBulbInlet(0.0);
    1122            0 :             Real64 DewPointInlet(0.0);
    1123            0 :             Real64 CpInlet(0.0);
    1124              : 
    1125            0 :             if (this->WorkingFluidType == WorkingFluidEnum::AIR) {
    1126            0 :                 Real64 Winlet = state.dataLoopNodes->Node(InletNode).HumRat;
    1127            0 :                 CpInlet = Psychrometrics::PsyCpAirFnW(Winlet);
    1128            0 :                 WetBulbInlet = Psychrometrics::PsyTwbFnTdbWPb(state, Tinlet, Winlet, state.dataEnvrn->OutBaroPress, RoutineName);
    1129            0 :                 DewPointInlet = Psychrometrics::PsyTdpFnTdbTwbPb(state, Tinlet, WetBulbInlet, state.dataEnvrn->OutBaroPress, RoutineName);
    1130            0 :             } else if (this->WorkingFluidType == WorkingFluidEnum::LIQUID) {
    1131            0 :                 CpInlet = Psychrometrics::CPHW(Tinlet);
    1132              :             }
    1133              :             Real64 Tcollector =
    1134            0 :                 (2.0 * mdot * CpInlet * Tinlet + this->AreaCol * (HrGround * state.dataEnvrn->OutDryBulbTemp + HrSky * state.dataEnvrn->SkyTemp +
    1135            0 :                                                                   HrAir * state.dataSurface->SurfOutDryBulbTemp(this->SurfNum) +
    1136            0 :                                                                   HcExt * state.dataSurface->SurfOutDryBulbTemp(this->SurfNum))) /
    1137            0 :                 (2.0 * mdot * CpInlet + this->AreaCol * (HrGround + HrSky + HrAir + HcExt));
    1138            0 :             PotentialOutletTemp = 2.0 * Tcollector - Tinlet;
    1139            0 :             this->Report.ToutletWorkFluid = PotentialOutletTemp;
    1140              :             // trap for air not being cooled below its wetbulb.
    1141            0 :             if (this->WorkingFluidType == WorkingFluidEnum::AIR) {
    1142            0 :                 if (PotentialOutletTemp < DewPointInlet) {
    1143              :                     //  water removal would be needed.. not going to allow that for now.  limit cooling to dew point and model bypass
    1144            0 :                     if (Tinlet != PotentialOutletTemp) {
    1145            0 :                         BypassFraction = (DewPointInlet - PotentialOutletTemp) / (Tinlet - PotentialOutletTemp);
    1146              : 
    1147              :                     } else {
    1148            0 :                         BypassFraction = 0.0;
    1149              :                     }
    1150            0 :                     BypassFraction = max(0.0, BypassFraction);
    1151            0 :                     PotentialOutletTemp = DewPointInlet;
    1152              :                 }
    1153              :             }
    1154              : 
    1155            0 :             this->Report.MdotWorkFluid = mdot;
    1156            0 :             this->Report.TinletWorkFluid = Tinlet;
    1157            0 :             this->Report.ToutletWorkFluid = PotentialOutletTemp;
    1158            0 :             this->Report.ThermHeatLoss = mdot * CpInlet * (Tinlet - this->Report.ToutletWorkFluid);
    1159            0 :             this->Report.ThermHeatGain = 0.0;
    1160            0 :             this->Report.ThermPower = -1.0 * this->Report.ThermHeatLoss;
    1161            0 :             this->Report.ThermEnergy = this->Report.ThermPower * state.dataHVACGlobal->TimeStepSysSec;
    1162            0 :             this->Simple.LastCollectorTemp = Tcollector;
    1163            0 :             this->Report.BypassStatus = BypassFraction;
    1164              : 
    1165            0 :         } else {
    1166            0 :             this->Report.TinletWorkFluid = Tinlet;
    1167            0 :             this->Report.ToutletWorkFluid = Tinlet;
    1168            0 :             this->Report.ThermHeatLoss = 0.0;
    1169            0 :             this->Report.ThermHeatGain = 0.0;
    1170            0 :             this->Report.ThermPower = 0.0;
    1171            0 :             this->Report.ThermEnergy = 0.0;
    1172            0 :             this->Report.BypassStatus = 1.0;
    1173            0 :             this->Report.MdotWorkFluid = mdot;
    1174              :         }
    1175            0 :     }
    1176              : 
    1177            0 :     void PVTCollectorStruct::calculateBIPVT(EnergyPlusData &state)
    1178              :     {
    1179              : 
    1180              :         // PURPOSE OF THIS SUBROUTINE:
    1181              :         // Calculate BIPVT collector thermal peformancce
    1182              : 
    1183              :         // METHODOLOGY EMPLOYED:
    1184              :         // ???
    1185              : 
    1186            0 :         static std::string const RoutineName("CalcBIPVTcollectors");
    1187              : 
    1188            0 :         int InletNode = this->HVACInletNodeNum;
    1189            0 :         Real64 mdot = this->MassFlowRate;
    1190            0 :         Real64 Tinlet = state.dataLoopNodes->Node(InletNode).Temp;
    1191            0 :         Real64 BypassFraction(0.0);
    1192            0 :         Real64 PotentialOutletTemp(Tinlet);
    1193            0 :         Real64 PotentialHeatGain(0.0);
    1194            0 :         Real64 Eff(0.0);
    1195            0 :         Real64 Tcollector(Tinlet);
    1196            0 :         this->OperatingMode = PVTMode::Heating;
    1197              : 
    1198            0 :         if (this->HeatingUseful && this->BypassDamperOff && (this->BIPVT.availSched->getCurrentVal() > 0.0)) {
    1199              : 
    1200            0 :             if ((state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint - Tinlet) > 0.1) {
    1201            0 :                 calculateBIPVTMaxHeatGain(state,
    1202            0 :                                           state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint,
    1203              :                                           BypassFraction,
    1204              :                                           PotentialHeatGain,
    1205              :                                           PotentialOutletTemp,
    1206              :                                           Eff,
    1207              :                                           Tcollector);
    1208            0 :                 if (PotentialHeatGain < 0.0) {
    1209            0 :                     BypassFraction = 1.0;
    1210            0 :                     PotentialHeatGain = 0.0;
    1211            0 :                     PotentialOutletTemp = Tinlet;
    1212              :                 }
    1213              :             }
    1214              : 
    1215            0 :             this->Report.ThermHeatGain = PotentialHeatGain;
    1216            0 :             this->Report.ThermPower = this->Report.ThermHeatGain;
    1217            0 :             this->Report.ThermEnergy = this->Report.ThermPower * state.dataHVACGlobal->TimeStepSysSec;
    1218            0 :             this->Report.ThermHeatLoss = 0.0;
    1219            0 :             this->Report.TinletWorkFluid = Tinlet;
    1220            0 :             this->Report.MdotWorkFluid = mdot;
    1221            0 :             this->Report.ToutletWorkFluid = PotentialOutletTemp;
    1222            0 :             this->Report.BypassStatus = BypassFraction;
    1223            0 :             if (PotentialHeatGain > 0.0) this->BIPVT.LastCollectorTemp = Tcollector;
    1224              : 
    1225            0 :         } else if (this->CoolingUseful && this->BypassDamperOff && (this->BIPVT.availSched->getCurrentVal() > 0.0)) {
    1226              : 
    1227            0 :             this->OperatingMode = PVTMode::Cooling;
    1228            0 :             if ((Tinlet - state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint) > 0.1) {
    1229            0 :                 calculateBIPVTMaxHeatGain(state,
    1230            0 :                                           state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint,
    1231              :                                           BypassFraction,
    1232              :                                           PotentialHeatGain,
    1233              :                                           PotentialOutletTemp,
    1234              :                                           Eff,
    1235              :                                           Tcollector);
    1236            0 :                 if (PotentialHeatGain > 0.0) {
    1237            0 :                     PotentialHeatGain = 0.0;
    1238            0 :                     BypassFraction = 1.0;
    1239            0 :                     PotentialOutletTemp = Tinlet;
    1240              :                 } else {
    1241            0 :                     Real64 WetBulbInlet(0.0);
    1242            0 :                     Real64 DewPointInlet(0.0);
    1243            0 :                     Real64 CpInlet(0.0);
    1244            0 :                     Real64 Winlet = state.dataLoopNodes->Node(InletNode).HumRat;
    1245            0 :                     CpInlet = Psychrometrics::PsyCpAirFnW(Winlet);
    1246            0 :                     WetBulbInlet = Psychrometrics::PsyTwbFnTdbWPb(state, Tinlet, Winlet, state.dataEnvrn->OutBaroPress, RoutineName);
    1247            0 :                     DewPointInlet = Psychrometrics::PsyTdpFnTdbTwbPb(state, Tinlet, WetBulbInlet, state.dataEnvrn->OutBaroPress, RoutineName);
    1248              :                     // trap for air not being cooled below its dewpoint.
    1249            0 :                     if ((PotentialOutletTemp < DewPointInlet) && ((Tinlet - DewPointInlet) > 0.1)) {
    1250              :                         //  water removal would be needed.. not going to allow that for now.  limit cooling to dew point and model bypass
    1251            0 :                         calculateBIPVTMaxHeatGain(state, DewPointInlet, BypassFraction, PotentialHeatGain, PotentialOutletTemp, Eff, Tcollector);
    1252            0 :                         PotentialOutletTemp = DewPointInlet;
    1253              :                     }
    1254              :                 }
    1255              :             } else {
    1256            0 :                 PotentialHeatGain = 0.0;
    1257            0 :                 BypassFraction = 1.0;
    1258            0 :                 PotentialOutletTemp = Tinlet;
    1259              :             }
    1260            0 :             this->Report.MdotWorkFluid = mdot;
    1261            0 :             this->Report.TinletWorkFluid = Tinlet;
    1262            0 :             this->Report.ToutletWorkFluid = PotentialOutletTemp;
    1263            0 :             this->Report.ThermHeatLoss = -PotentialHeatGain;
    1264            0 :             this->Report.ThermHeatGain = 0.0;
    1265            0 :             this->Report.ThermPower = -1.0 * this->Report.ThermHeatLoss;
    1266            0 :             this->Report.ThermEnergy = this->Report.ThermPower * state.dataHVACGlobal->TimeStepSysSec;
    1267            0 :             if (PotentialHeatGain < 0.0) this->BIPVT.LastCollectorTemp = Tcollector;
    1268            0 :             this->Report.BypassStatus = BypassFraction;
    1269              :         } else {
    1270            0 :             this->Report.TinletWorkFluid = Tinlet;
    1271            0 :             this->Report.ToutletWorkFluid = Tinlet;
    1272            0 :             this->Report.ThermHeatLoss = 0.0;
    1273            0 :             this->Report.ThermHeatGain = 0.0;
    1274            0 :             this->Report.ThermPower = 0.0;
    1275            0 :             this->Report.ThermEnergy = 0.0;
    1276            0 :             this->Report.BypassStatus = 1.0;
    1277            0 :             this->Report.MdotWorkFluid = mdot;
    1278              :         }
    1279            0 :     } // namespace PhotovoltaicThermalCollectors
    1280              : 
    1281           11 :     void PVTCollectorStruct::calculateBIPVTMaxHeatGain(
    1282              :         EnergyPlusData &state, Real64 tsp, Real64 &bfr, Real64 &q, Real64 &tmixed, Real64 &ThEff, Real64 &tpv)
    1283              :     {
    1284              :         // PURPOSE OF THIS SUBROUTINE:
    1285              :         // Calculate the maximum heat transfer from the BIPVT system to the air stream in the channel behind the PV module
    1286              : 
    1287              :         // METHODOLOGY EMPLOYED:
    1288              :         // Numerical & Analytical
    1289              : 
    1290           11 :         Real64 pi(Constant::Pi);
    1291              :         // BIPVT system geometry
    1292           11 :         Real64 l = state.dataSurface->Surface(this->SurfNum).Height;
    1293           11 :         Real64 w = state.dataSurface->Surface(this->SurfNum).Width;
    1294           11 :         Real64 depth_channel = this->BIPVT.PVEffGapWidth;                             // depth of air channel (m)
    1295           11 :         Real64 slope = (pi / 180.0) * state.dataSurface->Surface(this->SurfNum).Tilt; // surface tilt in rad
    1296           11 :         Real64 beta(0); // surface tilt for calculating internal convective coefficient for stagnation condition
    1297           11 :         Real64 surf_azimuth = state.dataSurface->Surface(this->SurfNum).Azimuth; // surface azimuth (deg)
    1298           11 :         Real64 fcell = this->BIPVT.PVCellAreaFract;                              // area fraction of cells on pv module
    1299           11 :         Real64 glass_thickness = this->BIPVT.ThGlass;                            // glass thickness
    1300           11 :         Real64 area_pv = w * l * this->BIPVT.PVAreaFract;                        // total area of pv modules
    1301           11 :         Real64 area_wall_total = w * l;                                          // total area of wall
    1302           11 :         Real64 length_conv = l;                                                  // length for wind convection coefficient calc
    1303           11 :         DataSurfaces::SurfaceShape shape_bld_surf = state.dataSurface->Surface(this->SurfNum).Shape;
    1304              : 
    1305           11 :         if (shape_bld_surf != EnergyPlus::DataSurfaces::SurfaceShape::Rectangle) {
    1306            0 :             ShowFatalError(state,
    1307            0 :                            "BIPVT is located on non-rectangular surface. Surface name = " + state.dataSurface->Surface(this->SurfNum).Name +
    1308              :                                ". BIPVT model requires rectangular surface.");
    1309              :         }
    1310              : 
    1311              :         // BIPVT materials thermal properties
    1312           11 :         Real64 emiss_b = this->BIPVT.BackMatEmiss;                 // emissivity of backing surface
    1313           11 :         Real64 emiss_2(0.85);                                      // emissivity of bldg surface
    1314           11 :         Real64 emiss_pvg = this->BIPVT.PVGEmiss;                   // emissivity of glass surface
    1315           11 :         Real64 rpvg_pv = this->BIPVT.PVRTop;                       // thermal resistance of glass (m2-K/W)
    1316           11 :         Real64 rpv_1 = this->BIPVT.PVRBot;                         // thermal resistance of backing layer (m2-K/W)
    1317           11 :         Real64 taoalpha_back = this->BIPVT.BackMatTranAbsProduct;  // tao-alpha product normal back of PV panel
    1318           11 :         Real64 taoalpha_pv = this->BIPVT.PVCellTransAbsProduct;    // tao-aplha product normal PV cells
    1319           11 :         Real64 taoaplha_cladding = this->BIPVT.CladTranAbsProduct; // tao-alpha product normal cladding
    1320           11 :         Real64 refrac_index_glass = this->BIPVT.RIndGlass;         // glass refractive index
    1321           11 :         Real64 k_glass = this->BIPVT.ECoffGlass;                   // extinction coefficient pv glass
    1322              : 
    1323              :         // PV panel efficiency
    1324           11 :         Real64 eff_pv(0.0); // efficiency pv panel
    1325              : 
    1326              :         // Weather/thermodynamic state/air properties/heat transfer
    1327           11 :         Real64 g(0.0);                                // Solar incident on surface of BIPVT collector (W/m^2)
    1328              :         Real64 tsurr, tsurrK;                         // surrouding temperature (DegC, DegK)
    1329              :         Real64 t1, t1K, t1_new;                       // temperature of pv backing surface (DegC, DegK, DegC)
    1330              :         Real64 tpv_new;                               // temperature of pv surface (DegC, DegC)
    1331              :         Real64 tpvg, tpvgK, tpvg_new;                 // temperature of pv glass cover (DegC, DegK,DegC)
    1332           11 :         Real64 tfavg(18.0);                           // average fluid temperature (DegC)
    1333              :         Real64 tfout;                                 // outlet fluid temperature from BIPVT channel (DegC)
    1334           11 :         Real64 hconvf1(100.0);                        // heat transfer coefficient between fluid and backing surface (W/m2-K)
    1335           11 :         Real64 hconvf2(100.0);                        // heat transfer coefficient between fluid and bldg surface (W/m2-K)
    1336           11 :         Real64 hconvt_nat(0.0);                       // htc external natural
    1337           11 :         Real64 hconvt_forced(0.0);                    // htc external forced
    1338           11 :         Real64 hconvt(0.0);                           // htc external total
    1339              :         Real64 hpvg_pv;                               // conductance of pv glass cover (W/m2-K)
    1340              :         Real64 hpv_1;                                 // conductance of pv backing (W/m2-K)
    1341              :         Real64 hrad12;                                // radiative heat transfer coefficient between bldg surface and pv backing surface (W/m2-K)
    1342              :         Real64 hrad_surr;                             // radiative heat transfer coefficient between pv glass cover and surrounding (W/m2-K)
    1343           11 :         constexpr Real64 sigma(5.67e-8);              // stephan bolzmann constant
    1344           11 :         Real64 reynolds(0.0);                         // Reynolds inside collector
    1345           11 :         Real64 nusselt(0.0);                          // Nusselt inside collector
    1346           11 :         Real64 vel(0.0);                              // flow velocity (m/s)
    1347           11 :         Real64 raleigh(0.0);                          // Raleigh number for stagnation calculations
    1348           11 :         Real64 dhyd(0.0);                             // Hydraulic diameter of channel (m)
    1349           11 :         constexpr Real64 gravity(9.81);               // gravity (m/s^2)
    1350           11 :         Real64 mu_air(22.7e-6);                       // dynamic viscosity (kg/m-s)
    1351           11 :         Real64 k_air(0.026);                          // thermal conductivity (W/m-K)
    1352           11 :         Real64 prandtl_air(0.7);                      // Prandtl number
    1353           11 :         Real64 density_air(1.2);                      // density (kg/m^3)
    1354           11 :         Real64 diffusivity_air(0.0);                  // thermal diffusivity (m^2/s)
    1355           11 :         Real64 kin_viscosity_air(0.0);                // kinematic viscosity (m^2/s)
    1356           11 :         Real64 extHTCcoeff(0.0);                      // coefficient for calculating external forced HTC
    1357           11 :         Real64 extHTCexp(0.0);                        // exponent for calculating external forced HTC
    1358           11 :         int const InletNode = this->HVACInletNodeNum; // HVAC node associated with inlet of BIPVT
    1359           11 :         Real64 tfin = state.dataLoopNodes->Node(InletNode).Temp;            // inlet fluid temperature (DegC)
    1360           11 :         Real64 w_in = state.dataLoopNodes->Node(InletNode).HumRat;          // inlet air humidity ratio (kgda/kg)
    1361           11 :         Real64 cp_in = Psychrometrics::PsyCpAirFnW(w_in);                   // inlet air specific heat (J/kg-K)
    1362           11 :         Real64 tamb = state.dataEnvrn->OutDryBulbTemp;                      // ambient temperature (DegC)
    1363           11 :         Real64 wamb = state.dataEnvrn->OutHumRat;                           // ambient humidity ratio (kg/kg)
    1364           11 :         Real64 cp_amb = Psychrometrics::PsyCpAirFnW(wamb);                  // ambient air specific heat (J/kg-K)
    1365           11 :         Real64 t_film(20.0);                                                // film temperature to calculate htc at ambient/pv interface (DegC)
    1366           11 :         Real64 tsky = state.dataEnvrn->SkyTemp;                             // sky temperature (DegC)
    1367           11 :         Real64 v_wind = state.dataEnvrn->WindSpeed;                         // wind speed (m/s)
    1368           11 :         Real64 wind_dir = state.dataEnvrn->WindDir;                         // wind direction (deg)
    1369           11 :         Real64 t2 = state.dataHeatBalSurf->SurfTempOut(this->SurfNum), t2K; // temperature of bldg surface (DegC)
    1370           11 :         Real64 mdot = this->MassFlowRate;                                   // fluid mass flow rate (kg/s)
    1371           11 :         Real64 mdot_bipvt(mdot), mdot_bipvt_new(mdot);                      // mass flow rate through the bipvt duct (kg/s)
    1372           11 :         Real64 s(0.0);                                                      // solar radiation gain at pv surface (W/m2)
    1373           11 :         Real64 s1(0.0);                                                     // solar radiation gain at pv backing surface (W/m2)
    1374           11 :         Real64 k_taoalpha_beam(0.0);
    1375           11 :         Real64 k_taoalpha_sky(0.0);
    1376           11 :         Real64 k_taoalpha_ground(0.0); // solar radiation gain at pv backing surface (W/m2)
    1377           11 :         Real64 iam_pv_beam(1.0);       // incident angle modifier pv cells
    1378           11 :         Real64 iam_back_beam(1.0);     // incident angle modifier back
    1379           11 :         Real64 iam_pv_sky(1.0);
    1380           11 :         Real64 iam_back_sky(1.0);
    1381           11 :         Real64 iam_pv_ground(1.0);
    1382           11 :         Real64 iam_back_ground(1.0);
    1383           11 :         Real64 theta_sky(0.0 * pi / 180.0);                                                     // incident angle sky
    1384           11 :         Real64 theta_ground(0.0 * pi / 180.0);                                                  // incident angle ground
    1385           11 :         Real64 theta_beam = std::acos(state.dataHeatBal->SurfCosIncidenceAngle(this->SurfNum)); // incident angle beam in rad
    1386           11 :         Real64 wind_incidence(0);                                                               // wind incidence angle on surface
    1387              : 
    1388              :         // other parameters
    1389           11 :         constexpr Real64 small_num(1.0e-10);                                  // small real number
    1390           11 :         Real64 a(0), b(0), c(0), d(0), e(0);                                  // variables used for solving average fluid temperature
    1391           11 :         Real64 err_tpvg(1.0), err_tpv(1.0), err_t1(1.0), err_mdot_bipvt(1.0); // convergence errors for temperatures
    1392           11 :         constexpr Real64 tol(1.0e-3);                                         // temperature convergence tolerance
    1393           11 :         constexpr Real64 rf(0.75);                                            // relaxation factor
    1394           11 :         constexpr Real64 degc_to_kelvin(273.15);                              // conversion constant degC to Kelvin
    1395              :         Real64 ebal1, ebal2, ebal3;                                           // energy balances on 3 surfaces
    1396           11 :         std::array<Real64, 9> jj = {0.0};                                     // 3x3 array for coefficient matrix
    1397           11 :         std::array<Real64, 3> f = {0.0};                                      // 3 element array for constant term
    1398           11 :         std::array<Real64, 3> y = {0.0};                                      // solution array for tpvg,tpv, and t1
    1399           11 :         int m(3);                                                             // parameter for number of unknwons
    1400              :         int i;                                                                // index
    1401           11 :         int iter(0);                                                          // iteration counter
    1402              : 
    1403           11 :         emiss_2 = state.dataConstruction->Construct(state.dataSurface->Surface(this->SurfNum).Construction)
    1404              :                       .OutsideAbsorpThermal; // get emissivity of interior surface of channel from building properties
    1405           11 :         theta_ground = (pi / 180) * (90 - 0.5788 * (slope * 180 / pi) + 0.002693 * std::pow((slope * 180 / pi), 2)); // incidence angle ground rad
    1406           11 :         theta_sky = (pi / 180) * (59.7 - 0.1388 * (slope * 180 / pi) + 0.001497 * std::pow((slope * 180 / pi), 2));  // incidence angle sky rad
    1407           11 :         t1 = (tamb + t2) / 2.0;
    1408           11 :         tpv = (tamb + t2) / 2.0;
    1409           11 :         tpvg = (tamb + t2) / 2.0;
    1410           11 :         hpvg_pv = 1.0 / rpvg_pv;
    1411           11 :         hpv_1 = 1.0 / rpv_1;
    1412              : 
    1413           11 :         k_taoalpha_beam = calc_k_taoalpha(theta_beam, glass_thickness, refrac_index_glass, k_glass);
    1414           11 :         iam_back_beam = k_taoalpha_beam;
    1415           11 :         iam_pv_beam = k_taoalpha_beam;
    1416              : 
    1417           11 :         k_taoalpha_sky = calc_k_taoalpha(theta_sky, glass_thickness, refrac_index_glass, k_glass);
    1418           11 :         iam_back_sky = k_taoalpha_sky;
    1419           11 :         iam_pv_sky = k_taoalpha_sky;
    1420              : 
    1421           11 :         k_taoalpha_ground = calc_k_taoalpha(theta_ground, glass_thickness, refrac_index_glass, k_glass);
    1422           11 :         iam_back_ground = k_taoalpha_sky;
    1423           11 :         iam_pv_ground = k_taoalpha_sky;
    1424              : 
    1425              :         tsurrK =
    1426           11 :             std::pow((std::pow((tamb + 273.15), 4) * 0.5 * (1 - std::cos(slope)) + std::pow((tsky + 273.15), 4) * 0.5 * (1 + std::cos(slope))), 0.25);
    1427           11 :         tsurr = tsurrK - degc_to_kelvin;
    1428           11 :         tpvgK = tpvg + degc_to_kelvin;
    1429           11 :         hrad_surr = sigma * emiss_pvg * (pow(tsurrK, 2) + pow(tpvgK, 2)) * (tsurrK + tpvgK);
    1430              : 
    1431           11 :         dhyd = 4 * w * l / (2 * (w + l));
    1432              : 
    1433           11 :         tmixed = tfin;
    1434           11 :         bfr = 0.0;
    1435           11 :         q = 0.0;
    1436          120 :         while ((err_t1 > tol) || (err_tpv > tol) || (err_tpvg > tol) || (err_mdot_bipvt > tol)) {
    1437              :             // Properties of air required for external convective heat transfer coefficient calculations - function of exterior film temperature
    1438          109 :             t_film = (tamb + tpvg) * 0.5;
    1439          109 :             mu_air =
    1440          109 :                 0.0000171 * (std::pow(((t_film + 273.15) / 273.0), 1.5)) *
    1441          109 :                 ((273.0 + 110.4) /
    1442          109 :                  ((t_film + 273.15) +
    1443              :                   110.4)); // Sutherland's formula https://www.grc.nasa.gov/www/k-12/airplane/viscosity.html Sutherland's constant = 198.72 R
    1444              :                            // converted to K =>110.4. At 273.15, Viscosity is 1.71E-5 as per Incropera, et al 6th ed. Temp range approx 273K - 373K
    1445          109 :             k_air = 0.000000000015207 * std::pow(t_film + 273.15, 3.0) - 0.000000048574 * std::pow(t_film + 273.15, 2.0) +
    1446          109 :                     0.00010184 * (t_film + 273.15) - 0.00039333; // Dumas, A., and Trancossi, M., SAE Technical Papers, 2009
    1447          109 :             density_air = 101.3 / (0.287 * (t_film + 273.15));   // Ideal gas law
    1448          109 :             diffusivity_air = k_air / (cp_amb * density_air);    // definition
    1449          109 :             kin_viscosity_air = mu_air / density_air;            // definition
    1450              : 
    1451              :             // duffie and beckman correlation for nat convection - This is for exterior
    1452          109 :             raleigh = (gravity * (1.0 / (0.5 * (tamb + tpvg) + 273.15)) * (std::max((Real64)(0.000001), std::abs(tpvg - tamb))) * std::pow(dhyd, 3)) /
    1453          109 :                       (diffusivity_air * kin_viscosity_air);             // definition
    1454          109 :             hconvt_nat = 0.15 * std::pow(raleigh, 0.333) * k_air / dhyd; // Incropera et al. 6th ed.
    1455              : 
    1456          109 :             wind_incidence = std::abs(wind_dir - surf_azimuth);
    1457          109 :             if ((wind_incidence - 180.0) > 0.001) wind_incidence -= 360.0;
    1458              : 
    1459          109 :             if (slope > 75.0 * pi / 180.0) { // If slope of surface if greater than 75deg, assume it's a wall and use wall external htc
    1460            0 :                 if (wind_incidence <= 45) {
    1461            0 :                     extHTCcoeff = 10.9247; // Windward Vert
    1462            0 :                     extHTCexp = 0.6434;    // Windward Vert
    1463            0 :                     length_conv = dhyd;
    1464            0 :                 } else if (wind_incidence > 45.0 && wind_incidence <= 135.0) {
    1465            0 :                     extHTCcoeff = 8.8505; // Sides
    1466            0 :                     extHTCexp = 0.6765;   // Sides
    1467            0 :                     length_conv = w;
    1468              :                 } else {
    1469            0 :                     extHTCcoeff = 7.5141; // Leeward Vertical
    1470            0 :                     extHTCexp = 0.6235;   // Leeward Vertical
    1471            0 :                     length_conv = dhyd;
    1472              :                 }
    1473              : 
    1474              :             } else { // if not, it's a roof
    1475          109 :                 if (wind_incidence <= 90.0) {
    1476              : 
    1477          109 :                     extHTCcoeff = 7.7283; // Windward Roof
    1478          109 :                     extHTCexp = 0.7586;   // Windward Roof
    1479          109 :                     length_conv = l;
    1480              :                 } else {
    1481              : 
    1482            0 :                     extHTCcoeff = 5.6217; // Leeward Roof
    1483            0 :                     extHTCexp = 0.6569;   // Leeward Roof;
    1484            0 :                     length_conv = l;
    1485              :                 }
    1486              :             }
    1487              : 
    1488              :             // forced conv htc derived from results from Gorman et al 2019 - Charact. lenght is: Roof - length along flow direction, windward and
    1489              :             // leeawrd vert - hydraulic perimeter of surface, vert sides - length of surface along flow direction
    1490          109 :             hconvt_forced = extHTCcoeff * std::pow((v_wind), extHTCexp) / (std::pow(l, 1.0 - extHTCexp)); // derived correlation for forced convection
    1491              : 
    1492              :             //"total" exterior htc is a combination of natural and forced htc - As described in Churchill and Usagi 1972, n=3 is a good value in most
    1493              :             // cases.
    1494          109 :             hconvt = std::pow((std::pow(hconvt_forced, 3.0) + std::pow(hconvt_nat, 3.0)), 1.0 / 3.0);
    1495              : 
    1496          109 :             if (state.dataPhotovoltaic->PVarray(this->PVnum).PVModelType == DataPhotovoltaics::PVModel::Simple) {
    1497            0 :                 eff_pv = state.dataPhotovoltaic->PVarray(this->PVnum).SimplePVModule.PVEfficiency;
    1498          109 :             } else if (state.dataPhotovoltaic->PVarray(this->PVnum).PVModelType == DataPhotovoltaics::PVModel::Sandia) {
    1499            0 :                 eff_pv = state.dataPhotovoltaic->PVarray(this->PVnum).SNLPVCalc.EffMax;
    1500          109 :             } else if (state.dataPhotovoltaic->PVarray(this->PVnum).PVModelType == DataPhotovoltaics::PVModel::TRNSYS) {
    1501          109 :                 eff_pv = state.dataPhotovoltaic->PVarray(this->PVnum).TRNSYSPVcalc.ArrayEfficiency;
    1502              :             }
    1503              : 
    1504          109 :             g = state.dataHeatBal->SurfQRadSWOutIncidentBeam(SurfNum) * iam_pv_beam +
    1505          109 :                 state.dataHeatBal->SurfQRadSWOutIncidentSkyDiffuse(SurfNum) * iam_pv_sky +
    1506          109 :                 state.dataHeatBal->SurfQRadSWOutIncidentGndDiffuse(SurfNum) * iam_pv_ground;
    1507              :             // s1 = DataHeatBalance::QRadSWOutIncident(this->SurfNum) * (1.0 - this->BIPVT.PVAreaFract) * IAM_bs;
    1508          109 :             s = g * taoalpha_pv * fcell * area_pv / area_wall_total - g * eff_pv * area_pv / area_wall_total;
    1509          109 :             s1 = taoalpha_back * g * (1.0 - fcell) * (area_pv / area_wall_total) + taoaplha_cladding * g * (1 - area_pv / area_wall_total);
    1510              : 
    1511              :             // Below are properties of air required for convective heat transfer coefficient calculations inside channel - function of avg channel
    1512              :             // temperature
    1513              : 
    1514          109 :             mu_air =
    1515          109 :                 0.0000171 * (std::pow(((tfavg + 273.15) / 273.0), 1.5)) *
    1516          109 :                 ((273.0 + 110.4) /
    1517          109 :                  ((tfavg + 273.15) +
    1518              :                   110.4)); // Sutherland's formula https://www.grc.nasa.gov/www/k-12/airplane/viscosity.html Sutherland's constant = 198.72 R
    1519              :                            // converted to K =>110.4. At 273.15, Viscosity is 1.71E-5 as per Incropera, et al 6th ed. Temp range approx 273K - 373K
    1520          109 :             k_air = 0.000000000015207 * std::pow(tfavg + 273.15, 3.0) - 0.000000048574 * std::pow(tfavg + 273.15, 2.0) +
    1521          109 :                     0.00010184 * (tfavg + 273.15) - 0.00039333;                        // Dumas, A., and Trancossi, M., SAE Technical Papers, 2009
    1522          109 :             prandtl_air = 0.680 + 0.000000469 * std::pow(tfavg + 273.15 - 540.0, 2.0); // The Schock Absorber Handbook, 2nd Ed. John C. Dixon 2007
    1523          109 :             density_air = 101.3 / (0.287 * (tfavg + 273.15));                          // Ideal gas law
    1524          109 :             diffusivity_air = k_air / (cp_in * density_air);                           // definition
    1525          109 :             kin_viscosity_air = mu_air / density_air;                                  // definition
    1526          109 :             t1K = t1 + degc_to_kelvin;
    1527          109 :             t2K = t2 + degc_to_kelvin;
    1528          109 :             tpvgK = tpvg + degc_to_kelvin;
    1529          109 :             hrad12 = sigma * (pow(t1K, 2) + pow(t2K, 2)) * (t1K + t2K) / (1 / emiss_b + 1 / emiss_2 - 1);
    1530          109 :             hrad_surr = sigma * emiss_pvg * (pow(tsurrK, 2) + pow(tpvgK, 2)) * (tsurrK + tpvgK);
    1531          109 :             if (mdot_bipvt > 0.0) // If there is a positive flow rate
    1532              :             {
    1533          109 :                 vel = mdot_bipvt / (density_air * w * depth_channel);
    1534          109 :                 reynolds = density_air * (vel) * (4 * w * depth_channel / (2 * (w + depth_channel))) / mu_air;
    1535          109 :                 nusselt = 0.052 * (std::pow(reynolds, 0.78)) * (std::pow(prandtl_air, 0.4)); // Candanedo et al. 2011
    1536          109 :                 hconvf1 = k_air * nusselt / (4 * w * depth_channel / (2 * (w + depth_channel)));
    1537          109 :                 nusselt = 1.017 * (std::pow(reynolds, 0.471)) * (std::pow(prandtl_air, 0.4));
    1538          109 :                 hconvf2 = k_air * nusselt / (4 * w * depth_channel / (2 * (w + depth_channel))); // Candanedo et al. 2011
    1539          109 :                 a = -(w / (mdot_bipvt * cp_in)) * (hconvf1 + hconvf2);
    1540          109 :                 b = (w / (mdot_bipvt * cp_in)) * (hconvf1 * t1 + hconvf2 * t2);
    1541          109 :                 tfavg = (1.0 / (a * l)) * (tfin + b / a) * (std::exp(a * l) - 1.0) - b / a;
    1542              :             } else // if there is no flow rate (stagnation)
    1543              :             {
    1544            0 :                 raleigh = (gravity * (1.0 / (tfavg + 273.15)) * (std::max((Real64)(0.000001), std::abs(t1 - t2))) * std::pow(depth_channel, 3)) /
    1545            0 :                           (diffusivity_air * kin_viscosity_air);
    1546            0 :                 if (slope > 75.0 * pi / 180.0) {
    1547            0 :                     beta = 75.0 * pi / 180.0;
    1548              :                 } else {
    1549            0 :                     beta = slope;
    1550              :                 }
    1551            0 :                 nusselt =
    1552            0 :                     1.0 +
    1553            0 :                     1.44 * (1.0 - 1708.0 * (std::pow((std::sin(1.8 * beta)), 1.6)) / raleigh / std::cos(beta)) *
    1554            0 :                         std::max(0.0, (1.0 - 1708.0 / raleigh / std::cos(beta))) +
    1555            0 :                     std::max(0.0, ((std::pow((raleigh * std::cos(beta) / 5830.0), (1.0 / 3.0))) - 1.0)); // Hollands et al J. Heat transfer v.98, 1976
    1556            0 :                 hconvf1 = 2.0 * k_air * nusselt / depth_channel; // cavity is split in two; hconvf1 and hconvf2. hconvf1 is assumed equal to hconvf2.
    1557              :                                                                  // Therefore, hconvf1 = 2*hcavity = hconvf2
    1558            0 :                 hconvf2 = hconvf1;
    1559            0 :                 c = s + s1 + hconvt * (tamb - tpvg) + hrad_surr * (tsurr - tpvg) + hrad12 * (t2 - t1);
    1560            0 :                 d = c + hconvf2 * t2;
    1561            0 :                 e = -hconvf2;
    1562            0 :                 tfavg = -d / e;
    1563              :             }
    1564          109 :             tfavg = std::max(tfavg, -50.0);
    1565              : 
    1566          436 :             for (i = 0; i <= (m - 1); i++) {
    1567          327 :                 f[i] = 0.0;
    1568          327 :                 y[i] = 0.0;
    1569              :             }
    1570          218 :             for (i = 0; i <= ((m ^ 2) - 1); i++) {
    1571          109 :                 jj[i] = 0.0;
    1572              :             }
    1573          109 :             jj[0] = hconvt + hrad_surr + hpvg_pv;
    1574          109 :             jj[1] = -hpvg_pv;
    1575          109 :             jj[2] = 0.0;
    1576          109 :             jj[3] = hpvg_pv;
    1577          109 :             jj[4] = -hpv_1 - hpvg_pv;
    1578          109 :             jj[5] = hpv_1;
    1579          109 :             jj[6] = 0.0;
    1580          109 :             jj[7] = hpv_1;
    1581          109 :             jj[8] = -hpv_1 - hconvf1 - hrad12;
    1582          109 :             f[0] = hconvt * tamb + hrad_surr * tsurr;
    1583          109 :             f[1] = -s;
    1584          109 :             f[2] = -s1 - hconvf1 * tfavg - hrad12 * t2;
    1585          109 :             solveLinSysBackSub(jj, f, y);
    1586          109 :             tpvg_new = y[0];
    1587          109 :             tpv_new = y[1];
    1588          109 :             t1_new = y[2];
    1589          109 :             if (mdot > 0.0) {
    1590          109 :                 tfout = (tfin + b / a) * std::exp(a * l) - b / a; // air outlet temperature (DegC)
    1591          109 :                 if (((this->OperatingMode == PVTMode::Heating) && (q > 0.0) && (tmixed > tsp) && (tfin < tsp)) ||
    1592          108 :                     ((this->OperatingMode == PVTMode::Cooling) && (q < 0.0) && (tmixed < tsp) && (tfin > tsp))) {
    1593           10 :                     bfr = (tsp - tfout) / (tfin - tfout); // bypass fraction
    1594           10 :                     bfr = std::max(0.0, bfr);
    1595           10 :                     bfr = std::min(1.0, bfr);
    1596           99 :                 } else if (((this->OperatingMode == PVTMode::Heating) && (q > 0.0) && (tmixed > tsp) && (tfin >= tsp)) ||
    1597           97 :                            ((this->OperatingMode == PVTMode::Cooling) && (q < 0.0) && (tmixed < tsp) && (tfin <= tsp))) {
    1598           12 :                     bfr = 1.0;
    1599           12 :                     tfout = tfin;
    1600              :                 }
    1601              :             } else {
    1602            0 :                 tfout = tfin;
    1603              :             }
    1604          109 :             tmixed = bfr * tfin + (1.0 - bfr) * tfout;
    1605          109 :             mdot_bipvt_new = (1.0 - bfr) * mdot;
    1606          109 :             err_tpvg = std::abs((tpvg_new - tpvg) / (tpvg + small_num));
    1607          109 :             err_tpv = std::abs((tpv_new - tpv) / (tpv + small_num));
    1608          109 :             err_t1 = std::abs((t1_new - t1) / (t1 + small_num));
    1609          109 :             err_mdot_bipvt = std::abs((mdot_bipvt_new - mdot_bipvt) / (mdot_bipvt + small_num));
    1610          109 :             tpvg = tpvg + rf * (tpvg_new - tpvg);
    1611          109 :             tpv = tpv + rf * (tpv_new - tpv);
    1612          109 :             t1 = t1 + rf * (t1_new - t1);
    1613          109 :             mdot_bipvt = mdot_bipvt + rf * (mdot_bipvt_new - mdot_bipvt);
    1614          109 :             q = mdot_bipvt * cp_in * (tfout - tfin); // heat transfer to the air
    1615          109 :             ebal1 = s1 + hpv_1 * (tpv - t1) + hconvf1 * (tfavg - t1) + hrad12 * (t2 - t1);
    1616          109 :             ebal2 = s + hpvg_pv * (tpvg - tpv) + hpv_1 * (t1 - tpv);
    1617          109 :             ebal3 = hconvt * (tpvg - tamb) + hrad_surr * (tpvg - tsurr) + hpvg_pv * (tpvg - tpv);
    1618          109 :             iter += 1;
    1619          109 :             if (iter == 50) {
    1620            0 :                 ShowSevereError(state, "Function PVTCollectorStruct::calculateBIPVTMaxHeatGain: Maximum number of iterations 50 reached");
    1621            0 :                 break;
    1622              :             }
    1623              :         }
    1624           11 :         ThEff = 0.0;
    1625           11 :         if ((q > small_num) && (state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) > small_num))
    1626            6 :             ThEff = q / (area_wall_total * state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) + small_num); // Thermal efficiency of BIPVT
    1627           11 :         this->BIPVT.Tcoll = t1;
    1628           11 :         this->BIPVT.HrPlen = hrad12;
    1629           11 :         this->BIPVT.Tplen = tfavg;
    1630           11 :         this->BIPVT.HcPlen = hconvf2;
    1631           11 :     }
    1632              : 
    1633          109 :     void PVTCollectorStruct::solveLinSysBackSub(std::array<Real64, 9> &jj, std::array<Real64, 3> &f, std::array<Real64, 3> &y)
    1634              :     {
    1635              :         // PURPOSE OF THIS SUBROUTINE:
    1636              :         // Solve a system of linear equations using Gaussian elimination and back substitution method.
    1637              : 
    1638              :         int p;
    1639          109 :         int constexpr m = 3;
    1640          109 :         Real64 constexpr small = 1.0e-10;
    1641              : 
    1642          436 :         for (int i = 0; i < m; i++) {
    1643          327 :             y[i] = 0.0;
    1644              :         }
    1645              : 
    1646          327 :         for (int i = 0; i <= (m - 2); i++) {
    1647          218 :             bool coeff_not_zero = false;
    1648          218 :             for (int j = i; j <= (m - 1); j++) {
    1649          218 :                 if (std::abs(jj[j * m + i]) > small) {
    1650          218 :                     coeff_not_zero = true;
    1651          218 :                     p = j;
    1652          218 :                     break;
    1653              :                 }
    1654              :             }
    1655              : 
    1656          218 :             if (coeff_not_zero) {
    1657          218 :                 if (p != i) {
    1658            0 :                     Real64 dummy2 = f[i];
    1659            0 :                     f[i] = f[p];
    1660            0 :                     f[p] = dummy2;
    1661            0 :                     for (int j = 0; j <= (m - 1); j++) {
    1662            0 :                         Real64 dummy1 = jj[i * m + j];
    1663            0 :                         jj[i * m + j] = jj[p * m + j];
    1664            0 :                         jj[p * m + j] = dummy1;
    1665              :                     }
    1666              :                 }
    1667          545 :                 for (int j = (i + 1); j <= (m - 1); j++) {
    1668          327 :                     if (std::abs(jj[i * m + i]) < small) jj[i * m + i] = small;
    1669          327 :                     Real64 mm = jj[j * m + i] / jj[i * m + i];
    1670          327 :                     f[j] = f[j] - mm * f[i];
    1671         1308 :                     for (int k = 0; k <= (m - 1); k++) {
    1672          981 :                         jj[j * m + k] = jj[j * m + k] - mm * jj[i * m + k];
    1673              :                     }
    1674              :                 }
    1675              :             }
    1676              :         }
    1677          109 :         if (std::abs(jj[(m - 1) * m + m - 1]) < small) jj[(m - 1) * m + m - 1] = small;
    1678          109 :         y[m - 1] = f[m - 1] / jj[(m - 1) * m + m - 1];
    1679          109 :         Real64 sum = 0.0;
    1680          327 :         for (int i = 0; i <= (m - 2); i++) {
    1681          218 :             int ii = m - 2 - i;
    1682          763 :             for (int j = ii; j <= (m - 1); j++) {
    1683          545 :                 sum = sum + jj[ii * m + j] * y[j];
    1684              :             }
    1685          218 :             if (std::abs(jj[ii * m + ii]) < small) jj[ii * m + ii] = small;
    1686          218 :             y[ii] = (f[ii] - sum) / jj[ii * m + ii];
    1687          218 :             sum = 0.0;
    1688              :         }
    1689          109 :     }
    1690              : 
    1691           80 :     Real64 PVTCollectorStruct::calc_taoalpha(Real64 theta,
    1692              :                                              Real64 glass_thickness,
    1693              :                                              Real64 refrac_index_glass,
    1694              :                                              Real64 k_glass) // typ refrac_index_glass is 1.526, k_glass typ 4 m^-1, glass_thickness typ 0.002 m
    1695              :     {
    1696              :         // PURPOSE OF THIS SUBROUTINE:
    1697              :         // calculates the transmissivity absorptance of a glass/air interface, assuming all transmitted is absorbed
    1698              : 
    1699           80 :         Real64 theta_r(0.0);
    1700           80 :         Real64 taoalpha(0.0);
    1701              : 
    1702           80 :         if (theta == 0.0) // if theta is zero, set to very small positive, otehrwise, taoalpha calculation causes division by zero
    1703              :         {
    1704           42 :             theta = 0.000000001;
    1705              :         }
    1706              : 
    1707           80 :         theta_r = std::asin(std::sin(theta) / refrac_index_glass);
    1708              : 
    1709          160 :         taoalpha = std::exp(-k_glass * glass_thickness / (std::cos(theta_r))) *
    1710           80 :                    (1 - 0.5 * ((std::pow(std::sin(theta_r - theta), 2) / std::pow(std::sin(theta_r + theta), 2)) +
    1711           80 :                                (std::pow(std::tan(theta_r - theta), 2) / std::pow(std::tan(theta_r + theta), 2))));
    1712              : 
    1713           80 :         return taoalpha;
    1714              :     }
    1715              : 
    1716           40 :     Real64 PVTCollectorStruct::calc_k_taoalpha(Real64 theta,
    1717              :                                                Real64 glass_thickness,
    1718              :                                                Real64 refrac_index_glass,
    1719              :                                                Real64 k_glass) // typ refrac_index_glass is 1.526, k_glass typ 4 m^-1, glass_thickness typ 0.002 m
    1720              :     {
    1721              :         // PURPOSE OF THIS SUBROUTINE:
    1722              :         // calculates the off-normal angle factor K for the tao-alpha product
    1723           40 :         Real64 taoalpha(0.0);
    1724           40 :         Real64 taoalpha_zero(0.0);
    1725           40 :         Real64 k_taoalpha(0.0);
    1726              : 
    1727           40 :         taoalpha = calc_taoalpha(theta, glass_thickness, refrac_index_glass, k_glass);
    1728           40 :         taoalpha_zero = calc_taoalpha(0.0, glass_thickness, refrac_index_glass, k_glass);
    1729           40 :         k_taoalpha = taoalpha / taoalpha_zero;
    1730              : 
    1731           40 :         return k_taoalpha;
    1732              :     }
    1733              : 
    1734            0 :     void PVTCollectorStruct::update(EnergyPlusData &state)
    1735              :     {
    1736              : 
    1737              :         // SUBROUTINE INFORMATION:
    1738              :         //       AUTHOR         Brent Griffith
    1739              :         //       DATE WRITTEN   August 2008
    1740              : 
    1741              :         {
    1742            0 :             switch (this->WorkingFluidType) {
    1743            0 :             case WorkingFluidEnum::LIQUID: {
    1744            0 :                 int InletNode = this->PlantInletNodeNum;
    1745            0 :                 int OutletNode = this->PlantOutletNodeNum;
    1746              : 
    1747            0 :                 PlantUtilities::SafeCopyPlantNode(state, InletNode, OutletNode);
    1748            0 :                 state.dataLoopNodes->Node(OutletNode).Temp = this->Report.ToutletWorkFluid;
    1749            0 :             } break;
    1750            0 :             case WorkingFluidEnum::AIR: {
    1751            0 :                 int InletNode = this->HVACInletNodeNum;
    1752            0 :                 int OutletNode = this->HVACOutletNodeNum;
    1753              : 
    1754              :                 // Set the outlet nodes for properties that just pass through & not used
    1755            0 :                 state.dataLoopNodes->Node(OutletNode).Quality = state.dataLoopNodes->Node(InletNode).Quality;
    1756            0 :                 state.dataLoopNodes->Node(OutletNode).Press = state.dataLoopNodes->Node(InletNode).Press;
    1757            0 :                 state.dataLoopNodes->Node(OutletNode).MassFlowRate = state.dataLoopNodes->Node(InletNode).MassFlowRate;
    1758            0 :                 state.dataLoopNodes->Node(OutletNode).MassFlowRateMin = state.dataLoopNodes->Node(InletNode).MassFlowRateMin;
    1759            0 :                 state.dataLoopNodes->Node(OutletNode).MassFlowRateMax = state.dataLoopNodes->Node(InletNode).MassFlowRateMax;
    1760            0 :                 state.dataLoopNodes->Node(OutletNode).MassFlowRateMinAvail = state.dataLoopNodes->Node(InletNode).MassFlowRateMinAvail;
    1761            0 :                 state.dataLoopNodes->Node(OutletNode).MassFlowRateMaxAvail = state.dataLoopNodes->Node(InletNode).MassFlowRateMaxAvail;
    1762              : 
    1763              :                 // Set outlet node variables that are possibly changed
    1764            0 :                 state.dataLoopNodes->Node(OutletNode).Temp = this->Report.ToutletWorkFluid;
    1765            0 :                 state.dataLoopNodes->Node(OutletNode).HumRat = state.dataLoopNodes->Node(InletNode).HumRat; // assumes dewpoint bound on cooling ....
    1766            0 :                 state.dataLoopNodes->Node(OutletNode).Enthalpy =
    1767            0 :                     Psychrometrics::PsyHFnTdbW(this->Report.ToutletWorkFluid, state.dataLoopNodes->Node(OutletNode).HumRat);
    1768              : 
    1769              :                 // update the OtherSideConditionsModel coefficients for BIPVT
    1770            0 :                 if (this->ModelType == PVTModelType::BIPVT) {
    1771            0 :                     int thisOSCM = this->BIPVT.OSCMPtr;
    1772            0 :                     state.dataSurface->OSCM(thisOSCM).TConv = this->BIPVT.Tplen;
    1773            0 :                     state.dataSurface->OSCM(thisOSCM).HConv = this->BIPVT.HcPlen;
    1774            0 :                     state.dataSurface->OSCM(thisOSCM).TRad = this->BIPVT.Tcoll;
    1775            0 :                     state.dataSurface->OSCM(thisOSCM).HRad = this->BIPVT.HrPlen;
    1776              :                 }
    1777            0 :             } break;
    1778            0 :             default:
    1779            0 :                 break;
    1780              :             }
    1781              :         }
    1782            0 :     }
    1783              : 
    1784            1 :     void PVTCollectorStruct::oneTimeInit(EnergyPlusData &state)
    1785              :     {
    1786              : 
    1787            1 :         if (this->MyOneTimeFlag) {
    1788            1 :             this->setupReportVars(state);
    1789            1 :             this->MyOneTimeFlag = false;
    1790              :         }
    1791              : 
    1792            1 :         if (this->SetLoopIndexFlag) {
    1793            1 :             if (allocated(state.dataPlnt->PlantLoop) && (this->PlantInletNodeNum > 0)) {
    1794            1 :                 bool errFlag = false;
    1795            1 :                 PlantUtilities::ScanPlantLoopsForObject(state, this->Name, this->Type, this->WPlantLoc, errFlag, _, _, _, _, _);
    1796            1 :                 if (errFlag) {
    1797            0 :                     ShowFatalError(state, "InitPVTcollectors: Program terminated for previous conditions.");
    1798              :                 }
    1799            1 :                 this->SetLoopIndexFlag = false;
    1800              :             }
    1801              :         }
    1802            1 :     }
    1803              : 
    1804            0 :     void GetPVTThermalPowerProduction(EnergyPlusData &state, int const PVindex, Real64 &ThermalPower, Real64 &ThermalEnergy)
    1805              :     {
    1806            0 :         int PVTnum(0);
    1807              : 
    1808              :         // first find PVT index that is associated with this PV generator
    1809            0 :         for (int loop = 1; loop <= state.dataPhotovoltaicThermalCollector->NumPVT; ++loop) {
    1810            0 :             if (!state.dataPhotovoltaicThermalCollector->PVT(loop).PVfound) continue;
    1811            0 :             if (state.dataPhotovoltaicThermalCollector->PVT(loop).PVnum == PVindex) { // we found it
    1812            0 :                 PVTnum = loop;
    1813              :             }
    1814              :         }
    1815              : 
    1816            0 :         if (PVTnum > 0) {
    1817            0 :             ThermalPower = state.dataPhotovoltaicThermalCollector->PVT(PVTnum).Report.ThermPower;
    1818            0 :             ThermalEnergy = state.dataPhotovoltaicThermalCollector->PVT(PVTnum).Report.ThermEnergy;
    1819              :         } else {
    1820            0 :             ThermalPower = 0.0;
    1821            0 :             ThermalEnergy = 0.0;
    1822              :         }
    1823            0 :     }
    1824              : 
    1825            0 :     int GetAirInletNodeNum(EnergyPlusData &state, std::string_view PVTName, bool &ErrorsFound)
    1826              :     {
    1827              :         // FUNCTION INFORMATION:
    1828              :         //       AUTHOR         Lixing Gu
    1829              :         //       DATE WRITTEN   May 2019
    1830              :         //       MODIFIED       na
    1831              :         //       RE-ENGINEERED  na
    1832              : 
    1833              :         // PURPOSE OF THIS FUNCTION:
    1834              :         // This function looks up the given PVT and returns the air inlet node number.
    1835              :         // If incorrect PVT name is given, ErrorsFound is returned as true and node number as zero.
    1836              : 
    1837              :         int NodeNum; // node number returned
    1838              :         int WhichPVT;
    1839              : 
    1840            0 :         if (state.dataPhotovoltaicThermalCollector->GetInputFlag) {
    1841            0 :             GetPVTcollectorsInput(state);
    1842            0 :             state.dataPhotovoltaicThermalCollector->GetInputFlag = false;
    1843              :         }
    1844              : 
    1845            0 :         WhichPVT = Util::FindItemInList(PVTName, state.dataPhotovoltaicThermalCollector->PVT);
    1846            0 :         if (WhichPVT != 0) {
    1847            0 :             NodeNum = state.dataPhotovoltaicThermalCollector->PVT(WhichPVT).HVACInletNodeNum;
    1848              :         } else {
    1849            0 :             ShowSevereError(state, format("GetAirInletNodeNum: Could not find SolarCollector FlatPlate PhotovoltaicThermal = \"{}\"", PVTName));
    1850            0 :             ErrorsFound = true;
    1851            0 :             NodeNum = 0;
    1852              :         }
    1853              : 
    1854            0 :         return NodeNum;
    1855              :     }
    1856            0 :     int GetAirOutletNodeNum(EnergyPlusData &state, std::string_view PVTName, bool &ErrorsFound)
    1857              :     {
    1858              :         // FUNCTION INFORMATION:
    1859              :         //       AUTHOR         Lixing Gu
    1860              :         //       DATE WRITTEN   May 2019
    1861              :         //       MODIFIED       na
    1862              :         //       RE-ENGINEERED  na
    1863              : 
    1864              :         // PURPOSE OF THIS FUNCTION:
    1865              :         // This function looks up the given PVT and returns the air outlet node number.
    1866              :         // If incorrect PVT name is given, ErrorsFound is returned as true and node number as zero.
    1867              : 
    1868              :         int NodeNum; // node number returned
    1869              :         int WhichPVT;
    1870              : 
    1871            0 :         if (state.dataPhotovoltaicThermalCollector->GetInputFlag) {
    1872            0 :             GetPVTcollectorsInput(state);
    1873            0 :             state.dataPhotovoltaicThermalCollector->GetInputFlag = false;
    1874              :         }
    1875              : 
    1876            0 :         WhichPVT = Util::FindItemInList(PVTName, state.dataPhotovoltaicThermalCollector->PVT);
    1877            0 :         if (WhichPVT != 0) {
    1878            0 :             NodeNum = state.dataPhotovoltaicThermalCollector->PVT(WhichPVT).HVACOutletNodeNum;
    1879              :         } else {
    1880            0 :             ShowSevereError(state, format("GetAirInletNodeNum: Could not find SolarCollector FlatPlate PhotovoltaicThermal = \"{}\"", PVTName));
    1881            0 :             ErrorsFound = true;
    1882            0 :             NodeNum = 0;
    1883              :         }
    1884              : 
    1885            0 :         return NodeNum;
    1886              :     }
    1887              : 
    1888            0 :     int getPVTindexFromName(EnergyPlusData &state, std::string_view objectName)
    1889              :     {
    1890            0 :         if (state.dataPhotovoltaicThermalCollector->GetInputFlag) {
    1891            0 :             GetPVTcollectorsInput(state);
    1892            0 :             state.dataPhotovoltaicThermalCollector->GetInputFlag = false;
    1893              :         }
    1894              : 
    1895            0 :         for (auto it = state.dataPhotovoltaicThermalCollector->PVT.begin(); it != state.dataPhotovoltaicThermalCollector->PVT.end(); ++it) {
    1896            0 :             if (it->Name == objectName) {
    1897            0 :                 return static_cast<int>(std::distance(state.dataPhotovoltaicThermalCollector->PVT.begin(), it) + 1);
    1898              :             }
    1899              :         }
    1900              : 
    1901              :         // If we didn't find it, fatal
    1902            0 :         ShowFatalError(state, format("Solar Thermal Collector GetIndexFromName: Error getting inputs for object named: {}", objectName));
    1903            0 :         assert(false);
    1904              :         return 0; // Shutup compiler
    1905              :     }
    1906              : 
    1907            0 :     void simPVTfromOASys(EnergyPlusData &state, int const index, bool const FirstHVACIteration)
    1908              :     {
    1909            0 :         PlantLocation dummyLoc(0, DataPlant::LoopSideLocation::Invalid, 0, 0);
    1910            0 :         Real64 dummyCurLoad(0.0);
    1911            0 :         bool dummyRunFlag(true);
    1912              : 
    1913            0 :         state.dataPhotovoltaicThermalCollector->PVT(index).simulate(state, dummyLoc, FirstHVACIteration, dummyCurLoad, dummyRunFlag);
    1914            0 :     }
    1915              : 
    1916            2 :     int GetPVTmodelIndex(EnergyPlusData &state, int const SurfacePtr)
    1917              :     {
    1918              :         // PURPOSE OF THIS SUBROUTINE:
    1919              :         // object oriented "Get" routine for establishing correct integer index from outside this module
    1920              : 
    1921              :         // METHODOLOGY EMPLOYED:
    1922              :         // mine Surface derived type for correct index/number of surface
    1923              :         // mine PVT derived type that has the surface.
    1924              : 
    1925              :         // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    1926              :         int PVTNum; // temporary
    1927              :         int thisPVT;
    1928              :         bool Found;
    1929            2 :         int PVTIndex(0);
    1930              : 
    1931            2 :         if (state.dataPhotovoltaicThermalCollector->GetInputFlag) {
    1932            2 :             GetPVTcollectorsInput(state);
    1933            2 :             state.dataPhotovoltaicThermalCollector->GetInputFlag = false;
    1934              :         }
    1935              : 
    1936            2 :         if (SurfacePtr == 0) {
    1937            0 :             ShowFatalError(state, "Invalid surface passed to GetPVTmodelIndex");
    1938              :         }
    1939              : 
    1940            2 :         PVTNum = 0;
    1941            2 :         Found = false;
    1942            4 :         for (thisPVT = 1; thisPVT <= state.dataPhotovoltaicThermalCollector->NumPVT; ++thisPVT) {
    1943            2 :             if (SurfacePtr == state.dataPhotovoltaicThermalCollector->PVT(thisPVT).SurfNum) {
    1944            2 :                 Found = true;
    1945            2 :                 PVTNum = thisPVT;
    1946              :             }
    1947              :         }
    1948              : 
    1949            2 :         if (!Found) {
    1950            0 :             ShowFatalError(
    1951            0 :                 state, "Did not find surface in PVT description in GetPVTmodelIndex, Surface name = " + state.dataSurface->Surface(SurfacePtr).Name);
    1952              :         } else {
    1953              : 
    1954            2 :             PVTIndex = PVTNum;
    1955              :         }
    1956              : 
    1957            2 :         return PVTIndex;
    1958              :     }
    1959              : 
    1960            0 :     void SetPVTQdotSource(EnergyPlusData &state,
    1961              :                           int const PVTNum,
    1962              :                           Real64 const QSource // source term in Watts
    1963              :     )
    1964              :     {
    1965              :         // PURPOSE OF THIS SUBROUTINE:
    1966              :         // object oriented "Set" routine for updating sink term without exposing variables
    1967              : 
    1968              :         // METHODOLOGY EMPLOYED:
    1969              :         // update derived type with new data , turn power into W/m2
    1970              : 
    1971            0 :         state.dataPhotovoltaicThermalCollector->PVT(PVTNum).QdotSource = QSource / state.dataPhotovoltaicThermalCollector->PVT(PVTNum).AreaCol;
    1972            0 :     }
    1973              : 
    1974            0 :     void GetPVTTsColl(EnergyPlusData &state, int const PVTNum, Real64 &TsColl)
    1975              :     {
    1976              :         // PURPOSE OF THIS SUBROUTINE:
    1977              :         // object oriented "Get" routine for collector surface temperature
    1978              : 
    1979              :         // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    1980            0 :         if (state.dataPhotovoltaicThermalCollector->PVT(PVTNum).ModelType == PVTModelType::BIPVT) {
    1981            0 :             TsColl = state.dataPhotovoltaicThermalCollector->PVT(PVTNum).BIPVT.LastCollectorTemp;
    1982              :         }
    1983            0 :     }
    1984              : 
    1985              : } // namespace PhotovoltaicThermalCollectors
    1986              : 
    1987              : } // namespace EnergyPlus
        

Generated by: LCOV version 2.0-1