LCOV - code coverage report
Current view: top level - EnergyPlus - PhotovoltaicThermalCollectors.cc (source / functions) Hit Total Coverage
Test: lcov.output.filtered Lines: 861 1098 78.4 %
Date: 2024-08-23 23:50:59 Functions: 26 28 92.9 %

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

Generated by: LCOV version 1.14