LCOV - code coverage report
Current view: top level - EnergyPlus - SolarCollectors.cc (source / functions) Coverage Total Hit
Test: lcov.output.filtered Lines: 2.7 % 986 27
Test Date: 2025-05-22 16:09:37 Functions: 5.3 % 19 1

            Line data    Source code
       1              : // EnergyPlus, Copyright (c) 1996-2025, The Board of Trustees of the University of Illinois,
       2              : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
       3              : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
       4              : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
       5              : // contributors. All rights reserved.
       6              : //
       7              : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
       8              : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
       9              : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
      10              : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
      11              : // derivative works, and perform publicly and display publicly, and to permit others to do so.
      12              : //
      13              : // Redistribution and use in source and binary forms, with or without modification, are permitted
      14              : // provided that the following conditions are met:
      15              : //
      16              : // (1) Redistributions of source code must retain the above copyright notice, this list of
      17              : //     conditions and the following disclaimer.
      18              : //
      19              : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
      20              : //     conditions and the following disclaimer in the documentation and/or other materials
      21              : //     provided with the distribution.
      22              : //
      23              : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
      24              : //     the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
      25              : //     used to endorse or promote products derived from this software without specific prior
      26              : //     written permission.
      27              : //
      28              : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
      29              : //     without changes from the version obtained under this License, or (ii) Licensee makes a
      30              : //     reference solely to the software portion of its product, Licensee must refer to the
      31              : //     software as "EnergyPlus version X" software, where "X" is the version number Licensee
      32              : //     obtained under this License and may not use a different name for the software. Except as
      33              : //     specifically required in this Section (4), Licensee shall not use in a company name, a
      34              : //     product name, in advertising, publicity, or other promotional activities any name, trade
      35              : //     name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
      36              : //     similar designation, without the U.S. Department of Energy's prior written consent.
      37              : //
      38              : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
      39              : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
      40              : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
      41              : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
      42              : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
      43              : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
      44              : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
      45              : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
      46              : // POSSIBILITY OF SUCH DAMAGE.
      47              : 
      48              : // C++ Headers
      49              : #include <cmath>
      50              : 
      51              : // ObjexxFCL Headers
      52              : #include <ObjexxFCL/Array.functions.hh>
      53              : 
      54              : // EnergyPlus Headers
      55              : #include <EnergyPlus/BranchNodeConnections.hh>
      56              : #include <EnergyPlus/Data/EnergyPlusData.hh>
      57              : #include <EnergyPlus/DataEnvironment.hh>
      58              : #include <EnergyPlus/DataHVACGlobals.hh>
      59              : #include <EnergyPlus/DataHeatBalance.hh>
      60              : #include <EnergyPlus/DataIPShortCuts.hh>
      61              : #include <EnergyPlus/DataLoopNode.hh>
      62              : #include <EnergyPlus/DataSurfaces.hh>
      63              : #include <EnergyPlus/FluidProperties.hh>
      64              : #include <EnergyPlus/General.hh>
      65              : #include <EnergyPlus/GlobalNames.hh>
      66              : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
      67              : #include <EnergyPlus/NodeInputManager.hh>
      68              : #include <EnergyPlus/OutputProcessor.hh>
      69              : #include <EnergyPlus/Plant/DataPlant.hh>
      70              : #include <EnergyPlus/PlantUtilities.hh>
      71              : #include <EnergyPlus/Psychrometrics.hh>
      72              : #include <EnergyPlus/SolarCollectors.hh>
      73              : #include <EnergyPlus/UtilityRoutines.hh>
      74              : 
      75              : namespace EnergyPlus {
      76              : 
      77              : namespace SolarCollectors {
      78              : 
      79              :     // MODULE INFORMATION:
      80              :     //       AUTHOR         Peter Graham Ellis
      81              :     //       DATE WRITTEN   December 2003
      82              :     //       MODIFIED       B. Nigusse, FSEC/UCF, March 2012, added ICS Collector
      83              :     //       RE-ENGINEERED  Brent Griffith, for plant upgrade, general fluid props
      84              : 
      85              :     // PURPOSE OF THIS MODULE:
      86              :     // Simulates solar collectors as a component on the plant loop.  Currently only flat-plate collectors (glazed and
      87              :     // unglazed) are implemented.
      88              : 
      89              :     // METHODOLOGY EMPLOYED:
      90              :     // Solar collectors are called as non-zone equipment on the demand side of the plant loop.  The collector object
      91              :     // must be connected to a WATER HEATER object on the supply side of the plant loop.  Water is assumed to be
      92              :     // the heat transfer fluid.
      93              : 
      94            0 :     PlantComponent *CollectorData::factory(EnergyPlusData &state, std::string const &objectName)
      95              :     {
      96              :         // Process the input data
      97            0 :         if (state.dataSolarCollectors->GetInputFlag) {
      98            0 :             GetSolarCollectorInput(state);
      99            0 :             state.dataSolarCollectors->GetInputFlag = false;
     100              :         }
     101              :         // Now look for this particular object
     102            0 :         for (auto &thisSC : state.dataSolarCollectors->Collector) {
     103            0 :             if (thisSC.Name == objectName) {
     104            0 :                 return &thisSC;
     105              :             }
     106              :         }
     107              :         // If we didn't find it, fatal
     108              :         ShowFatalError(state, format("LocalSolarCollectorFactory: Error getting inputs for object named: {}", objectName)); // LCOV_EXCL_LINE
     109              :         // Shut up the compiler
     110              :         return nullptr; // LCOV_EXCL_LINE
     111              :     }
     112              : 
     113            0 :     void GetSolarCollectorInput(EnergyPlusData &state)
     114              :     {
     115              : 
     116              :         // SUBROUTINE INFORMATION:
     117              :         //       AUTHOR         Peter Graham Ellis
     118              :         //       DATE WRITTEN   December 2003
     119              :         //       MODIFIED       na
     120              :         //       RE-ENGINEERED  na
     121              : 
     122              :         // PURPOSE OF THIS SUBROUTINE:
     123              :         // Gets the solar collector input from the input file and sets up the parameters and collector objects.
     124              : 
     125            0 :         bool ErrorsFound(false);              // Set to true if errors in input, fatal at end of routine
     126              :         int IOStatus;                         // Used in GetObjectItem
     127              :         int NumAlphas;                        // Number of Alphas for each GetObjectItem call
     128              :         int NumNumbers;                       // Number of Numbers for each GetObjectItem call
     129            0 :         std::string CurrentModuleObject;      // for ease in renaming.
     130            0 :         std::string CurrentModuleParamObject; // for ease in renaming.
     131              : 
     132              :         int NumFields;  // Total number of fields in object
     133              :         int MaxAlphas;  // Maximum number of alpha fields in all objects
     134              :         int MaxNumbers; // Maximum number of numeric fields in all objects
     135              : 
     136            0 :         Array1D<Real64> Numbers;       // Numeric data
     137            0 :         Array1D_string Alphas;         // Alpha data
     138            0 :         Array1D_string cAlphaFields;   // Alpha field names
     139            0 :         Array1D_string cNumericFields; // Numeric field names
     140            0 :         Array1D_bool lAlphaBlanks;     // Logical array, alpha field input BLANK = .TRUE.
     141            0 :         Array1D_bool lNumericBlanks;   // Logical array, numeric field input BLANK = .TRUE.
     142              : 
     143            0 :         MaxNumbers = 0;
     144            0 :         MaxAlphas = 0;
     145              : 
     146            0 :         CurrentModuleParamObject = "SolarCollectorPerformance:FlatPlate";
     147            0 :         int NumOfFlatPlateParam = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, CurrentModuleParamObject);
     148            0 :         state.dataInputProcessing->inputProcessor->getObjectDefMaxArgs(state, CurrentModuleParamObject, NumFields, NumAlphas, NumNumbers);
     149            0 :         MaxNumbers = max(MaxNumbers, NumNumbers);
     150            0 :         MaxAlphas = max(MaxAlphas, NumAlphas);
     151              : 
     152            0 :         CurrentModuleObject = "SolarCollector:FlatPlate:Water";
     153            0 :         int NumFlatPlateUnits = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, CurrentModuleObject);
     154            0 :         state.dataInputProcessing->inputProcessor->getObjectDefMaxArgs(state, CurrentModuleObject, NumFields, NumAlphas, NumNumbers);
     155            0 :         MaxNumbers = max(MaxNumbers, NumNumbers);
     156            0 :         MaxAlphas = max(MaxAlphas, NumAlphas);
     157              : 
     158            0 :         CurrentModuleParamObject = "SolarCollectorPerformance:IntegralCollectorStorage";
     159            0 :         int NumOfICSParam = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, CurrentModuleParamObject);
     160            0 :         state.dataInputProcessing->inputProcessor->getObjectDefMaxArgs(state, CurrentModuleParamObject, NumFields, NumAlphas, NumNumbers);
     161            0 :         MaxNumbers = max(MaxNumbers, NumNumbers);
     162            0 :         MaxAlphas = max(MaxAlphas, NumAlphas);
     163              : 
     164            0 :         CurrentModuleObject = "SolarCollector:IntegralCollectorStorage";
     165            0 :         int NumOfICSUnits = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, CurrentModuleObject);
     166            0 :         state.dataInputProcessing->inputProcessor->getObjectDefMaxArgs(state, CurrentModuleObject, NumFields, NumAlphas, NumNumbers);
     167            0 :         MaxNumbers = max(MaxNumbers, NumNumbers);
     168            0 :         MaxAlphas = max(MaxAlphas, NumAlphas);
     169              : 
     170            0 :         Alphas.allocate(MaxAlphas);
     171            0 :         Numbers.dimension(MaxNumbers, 0.0);
     172            0 :         cAlphaFields.allocate(MaxAlphas);
     173            0 :         cNumericFields.allocate(MaxNumbers);
     174            0 :         lAlphaBlanks.dimension(MaxAlphas, true);
     175            0 :         lNumericBlanks.dimension(MaxNumbers, true);
     176              : 
     177            0 :         state.dataSolarCollectors->NumOfCollectors = NumFlatPlateUnits + NumOfICSUnits;
     178            0 :         state.dataSolarCollectors->NumOfParameters = NumOfFlatPlateParam + NumOfICSParam;
     179              : 
     180            0 :         if (state.dataSolarCollectors->NumOfParameters > 0) {
     181            0 :             state.dataSolarCollectors->Parameters.allocate(state.dataSolarCollectors->NumOfParameters);
     182              : 
     183            0 :             CurrentModuleParamObject = "SolarCollectorPerformance:FlatPlate";
     184              : 
     185            0 :             for (int FlatPlateParamNum = 1; FlatPlateParamNum <= NumOfFlatPlateParam; ++FlatPlateParamNum) {
     186              : 
     187            0 :                 int ParametersNum = FlatPlateParamNum;
     188            0 :                 state.dataInputProcessing->inputProcessor->getObjectItem(state,
     189              :                                                                          CurrentModuleParamObject,
     190              :                                                                          ParametersNum,
     191            0 :                                                                          state.dataIPShortCut->cAlphaArgs,
     192              :                                                                          NumAlphas,
     193            0 :                                                                          state.dataIPShortCut->rNumericArgs,
     194              :                                                                          NumNumbers,
     195              :                                                                          IOStatus,
     196            0 :                                                                          state.dataIPShortCut->lNumericFieldBlanks,
     197              :                                                                          _,
     198            0 :                                                                          state.dataIPShortCut->cAlphaFieldNames,
     199            0 :                                                                          state.dataIPShortCut->cNumericFieldNames);
     200              : 
     201              :                 // Collector module parameters name
     202            0 :                 GlobalNames::VerifyUniqueInterObjectName(state,
     203            0 :                                                          state.dataSolarCollectors->UniqueParametersNames,
     204            0 :                                                          state.dataIPShortCut->cAlphaArgs(1),
     205              :                                                          CurrentModuleObject,
     206            0 :                                                          state.dataIPShortCut->cAlphaFieldNames(1),
     207              :                                                          ErrorsFound);
     208            0 :                 state.dataSolarCollectors->Parameters(ParametersNum).Name = state.dataIPShortCut->cAlphaArgs(1);
     209              : 
     210              :                 // NOTE:  This values serves mainly as a reference.  The area of the associated surface object is used in all calculations.
     211            0 :                 state.dataSolarCollectors->Parameters(ParametersNum).Area = state.dataIPShortCut->rNumericArgs(1);
     212              : 
     213              :                 //                The TestFluid member variable was never accessed, so this input field seems to not do anything as of right now
     214              :                 //                if (state.dataIPShortCut->cAlphaArgs(2) == "WATER") {
     215              :                 //                    state.dataSolarCollectors->Parameters(ParametersNum).TestFluid = FluidEnum::WATER;
     216              :                 //                    // CASE('AIR')
     217              :                 //                    //  Parameters(ParametersNum)%TestFluid = AIR
     218              :                 //                } else {
     219              :                 //                    ShowSevereError(state, format("{}{} = {}:  {}{} is an unsupported Test Fluid for {}{}", //,
     220              :                 //                    CurrentModuleParamObject, state.dataIPShortCut->cAlphaArgs(1), //, state.dataIPShortCut->cAlphaArgs(2), //,
     221              :                 //                    state.dataIPShortCut->cAlphaFieldNames(2))); ErrorsFound = true;
     222              :                 //                }
     223              : 
     224            0 :                 if (state.dataIPShortCut->rNumericArgs(2) > 0.0) {
     225            0 :                     state.dataSolarCollectors->Parameters(ParametersNum).TestMassFlowRate =
     226            0 :                         state.dataIPShortCut->rNumericArgs(2) * Psychrometrics::RhoH2O(Constant::InitConvTemp);
     227              :                 } else {
     228            0 :                     ShowSevereError(state,
     229            0 :                                     format("{} = {}:  flow rate must be greater than zero for {}",
     230              :                                            CurrentModuleParamObject,
     231            0 :                                            state.dataIPShortCut->cAlphaArgs(1),
     232            0 :                                            state.dataIPShortCut->cNumericFieldNames(2)));
     233            0 :                     ErrorsFound = true;
     234              :                 }
     235              : 
     236            0 :                 std::string_view const key = state.dataIPShortCut->cAlphaArgs(3);
     237            0 :                 state.dataSolarCollectors->Parameters(ParametersNum).TestType = static_cast<TestTypeEnum>(getEnumValue(testTypesUC, key));
     238            0 :                 if (state.dataSolarCollectors->Parameters(ParametersNum).TestType == TestTypeEnum::INVALID) {
     239            0 :                     ShowSevereError(state,
     240            0 :                                     format("{} = {}: {} is not supported for {}",
     241              :                                            CurrentModuleParamObject,
     242            0 :                                            state.dataIPShortCut->cAlphaArgs(1),
     243              :                                            key,
     244            0 :                                            state.dataIPShortCut->cAlphaFieldNames(3)));
     245            0 :                     ErrorsFound = true;
     246              :                 }
     247              : 
     248              :                 // Efficiency equation coefficients
     249            0 :                 state.dataSolarCollectors->Parameters(ParametersNum).eff0 = state.dataIPShortCut->rNumericArgs(3);
     250            0 :                 state.dataSolarCollectors->Parameters(ParametersNum).eff1 = state.dataIPShortCut->rNumericArgs(4);
     251              : 
     252            0 :                 if (NumNumbers > 4) {
     253            0 :                     state.dataSolarCollectors->Parameters(ParametersNum).eff2 = state.dataIPShortCut->rNumericArgs(5);
     254              :                 } else {
     255            0 :                     state.dataSolarCollectors->Parameters(ParametersNum).eff2 = 0.0;
     256              :                 }
     257              : 
     258              :                 // Incident angle modifier coefficients
     259            0 :                 if (NumNumbers > 5) {
     260            0 :                     state.dataSolarCollectors->Parameters(ParametersNum).iam1 = state.dataIPShortCut->rNumericArgs(6);
     261              :                 } else {
     262            0 :                     state.dataSolarCollectors->Parameters(ParametersNum).iam1 = 0.0;
     263              :                 }
     264              : 
     265            0 :                 if (NumNumbers > 6) {
     266            0 :                     state.dataSolarCollectors->Parameters(FlatPlateParamNum).iam2 = state.dataIPShortCut->rNumericArgs(7);
     267              :                 } else {
     268            0 :                     state.dataSolarCollectors->Parameters(ParametersNum).iam2 = 0.0;
     269              :                 }
     270              :             } // ParametersNum
     271              : 
     272            0 :             if (ErrorsFound) ShowFatalError(state, format("Errors in {} input.", CurrentModuleParamObject));
     273              :         }
     274              : 
     275            0 :         if (state.dataSolarCollectors->NumOfCollectors > 0) {
     276            0 :             state.dataSolarCollectors->Collector.allocate(state.dataSolarCollectors->NumOfCollectors);
     277              : 
     278            0 :             CurrentModuleObject = "SolarCollector:FlatPlate:Water";
     279              : 
     280            0 :             for (int FlatPlateUnitsNum = 1; FlatPlateUnitsNum <= NumFlatPlateUnits; ++FlatPlateUnitsNum) {
     281              : 
     282            0 :                 int CollectorNum = FlatPlateUnitsNum;
     283              : 
     284            0 :                 state.dataInputProcessing->inputProcessor->getObjectItem(state,
     285              :                                                                          CurrentModuleObject,
     286              :                                                                          CollectorNum,
     287            0 :                                                                          state.dataIPShortCut->cAlphaArgs,
     288              :                                                                          NumAlphas,
     289            0 :                                                                          state.dataIPShortCut->rNumericArgs,
     290              :                                                                          NumNumbers,
     291              :                                                                          IOStatus);
     292              : 
     293              :                 // Collector name
     294            0 :                 GlobalNames::VerifyUniqueInterObjectName(
     295            0 :                     state, state.dataSolarCollectors->UniqueCollectorNames, state.dataIPShortCut->cAlphaArgs(1), CurrentModuleObject, ErrorsFound);
     296            0 :                 state.dataSolarCollectors->Collector(CollectorNum).Name = state.dataIPShortCut->cAlphaArgs(1);
     297            0 :                 state.dataSolarCollectors->Collector(CollectorNum).Type =
     298              :                     DataPlant::PlantEquipmentType::SolarCollectorFlatPlate; // parameter assigned in DataPlant
     299              : 
     300              :                 // Get parameters object
     301            0 :                 int ParametersNum = Util::FindItemInList(state.dataIPShortCut->cAlphaArgs(2), state.dataSolarCollectors->Parameters);
     302              : 
     303            0 :                 if (ParametersNum == 0) {
     304            0 :                     ShowSevereError(state,
     305            0 :                                     format("{} = {}: {} object called {} not found.",
     306              :                                            CurrentModuleObject,
     307            0 :                                            state.dataIPShortCut->cAlphaArgs(1),
     308              :                                            CurrentModuleParamObject,
     309            0 :                                            state.dataIPShortCut->cAlphaArgs(2)));
     310            0 :                     ErrorsFound = true;
     311              :                 } else {
     312            0 :                     state.dataSolarCollectors->Collector(CollectorNum).Parameters = ParametersNum;
     313              :                 }
     314              : 
     315              :                 // Get surface object
     316            0 :                 int SurfNum = Util::FindItemInList(state.dataIPShortCut->cAlphaArgs(3), state.dataSurface->Surface);
     317              : 
     318            0 :                 if (SurfNum == 0) {
     319            0 :                     ShowSevereError(state,
     320            0 :                                     format("{} = {}:  Surface {} not found.",
     321              :                                            CurrentModuleObject,
     322            0 :                                            state.dataIPShortCut->cAlphaArgs(1),
     323            0 :                                            state.dataIPShortCut->cAlphaArgs(3)));
     324            0 :                     ErrorsFound = true;
     325            0 :                     continue; // avoid hard crash
     326              :                 } else {
     327              : 
     328            0 :                     if (!state.dataSurface->Surface(SurfNum).ExtSolar) {
     329            0 :                         ShowWarningError(state,
     330            0 :                                          format("{} = {}:  Surface {} is not exposed to exterior radiation.",
     331              :                                                 CurrentModuleObject,
     332            0 :                                                 state.dataIPShortCut->cAlphaArgs(1),
     333            0 :                                                 state.dataIPShortCut->cAlphaArgs(3)));
     334              :                     }
     335              : 
     336              :                     // check surface orientation, warn if upside down
     337            0 :                     if ((state.dataSurface->Surface(SurfNum).Tilt < -95.0) || (state.dataSurface->Surface(SurfNum).Tilt > 95.0)) {
     338            0 :                         ShowWarningError(state,
     339            0 :                                          format("Suspected input problem with {} = {}",
     340            0 :                                                 state.dataIPShortCut->cAlphaFieldNames(3),
     341            0 :                                                 state.dataIPShortCut->cAlphaArgs(3)));
     342            0 :                         ShowContinueError(
     343            0 :                             state, format("Entered in {} = {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
     344            0 :                         ShowContinueError(state, "Surface used for solar collector faces down");
     345            0 :                         ShowContinueError(
     346              :                             state,
     347            0 :                             format("Surface tilt angle (degrees from ground outward normal) = {:.2R}", state.dataSurface->Surface(SurfNum).Tilt));
     348              :                     }
     349              : 
     350              :                     // Check to make sure other solar collectors are not using the same surface
     351              :                     // NOTE:  Must search over all solar collector types
     352            0 :                     for (int CollectorNum2 = 1; CollectorNum2 <= NumFlatPlateUnits; ++CollectorNum2) {
     353            0 :                         if (state.dataSolarCollectors->Collector(CollectorNum2).Surface == SurfNum) {
     354            0 :                             ShowSevereError(state,
     355            0 :                                             format("{} = {}:  Surface {} is referenced by more than one {}",
     356              :                                                    CurrentModuleObject,
     357            0 :                                                    state.dataIPShortCut->cAlphaArgs(1),
     358            0 :                                                    state.dataIPShortCut->cAlphaArgs(3),
     359              :                                                    CurrentModuleObject));
     360            0 :                             ErrorsFound = true;
     361            0 :                             break;
     362              :                         }
     363              :                     } // CollectorNum2
     364              : 
     365            0 :                     state.dataSolarCollectors->Collector(CollectorNum).Surface = SurfNum;
     366              :                 }
     367              : 
     368              :                 // Give warning if surface area and gross area do not match within tolerance
     369            0 :                 if (SurfNum > 0 && ParametersNum > 0 && state.dataSolarCollectors->Parameters(ParametersNum).Area > 0.0 &&
     370            0 :                     std::abs(state.dataSolarCollectors->Parameters(ParametersNum).Area - state.dataSurface->Surface(SurfNum).Area) /
     371            0 :                             state.dataSurface->Surface(SurfNum).Area >
     372              :                         0.01) {
     373              : 
     374            0 :                     ShowWarningError(state,
     375            0 :                                      format("{} = {}:  Gross Area of solar collector parameters and surface object differ by more than 1%.",
     376              :                                             CurrentModuleObject,
     377            0 :                                             state.dataIPShortCut->cAlphaArgs(1)));
     378            0 :                     ShowContinueError(state, "Area of surface object will be used in all calculations.");
     379              :                 }
     380              : 
     381            0 :                 state.dataSolarCollectors->Collector(CollectorNum).InletNode =
     382            0 :                     NodeInputManager::GetOnlySingleNode(state,
     383            0 :                                                         state.dataIPShortCut->cAlphaArgs(4),
     384              :                                                         ErrorsFound,
     385              :                                                         DataLoopNode::ConnectionObjectType::SolarCollectorFlatPlateWater,
     386            0 :                                                         state.dataIPShortCut->cAlphaArgs(1),
     387              :                                                         DataLoopNode::NodeFluidType::Water,
     388              :                                                         DataLoopNode::ConnectionType::Inlet,
     389              :                                                         NodeInputManager::CompFluidStream::Primary,
     390              :                                                         DataLoopNode::ObjectIsNotParent);
     391            0 :                 state.dataSolarCollectors->Collector(CollectorNum).OutletNode =
     392            0 :                     NodeInputManager::GetOnlySingleNode(state,
     393            0 :                                                         state.dataIPShortCut->cAlphaArgs(5),
     394              :                                                         ErrorsFound,
     395              :                                                         DataLoopNode::ConnectionObjectType::SolarCollectorFlatPlateWater,
     396            0 :                                                         state.dataIPShortCut->cAlphaArgs(1),
     397              :                                                         DataLoopNode::NodeFluidType::Water,
     398              :                                                         DataLoopNode::ConnectionType::Outlet,
     399              :                                                         NodeInputManager::CompFluidStream::Primary,
     400              :                                                         DataLoopNode::ObjectIsNotParent);
     401              : 
     402            0 :                 if (NumNumbers > 0) {
     403            0 :                     state.dataSolarCollectors->Collector(CollectorNum).VolFlowRateMax =
     404            0 :                         state.dataIPShortCut->rNumericArgs(1); // Max volumetric flow rate used for plant sizing calculation
     405              :                 } else {
     406            0 :                     state.dataSolarCollectors->Collector(CollectorNum).VolFlowRateMax =
     407              :                         0.0; // Max vol flow rate is not specified; no flow for plant sizing calculation
     408            0 :                     state.dataSolarCollectors->Collector(CollectorNum).MassFlowRateMax =
     409              :                         999999.9; // But...set a very high value so that it demands as much as possible
     410              :                 }
     411              : 
     412            0 :                 BranchNodeConnections::TestCompSet(state,
     413              :                                                    CurrentModuleObject,
     414            0 :                                                    state.dataIPShortCut->cAlphaArgs(1),
     415            0 :                                                    state.dataIPShortCut->cAlphaArgs(4),
     416            0 :                                                    state.dataIPShortCut->cAlphaArgs(5),
     417              :                                                    "Water Nodes");
     418              : 
     419              :             } // FlatPlateUnitsNum
     420              : 
     421              :             // Get data for ICS collector
     422            0 :             CurrentModuleParamObject = "SolarCollectorPerformance:IntegralCollectorStorage";
     423              : 
     424            0 :             for (int ICSParamNum = 1; ICSParamNum <= NumOfICSParam; ++ICSParamNum) {
     425              : 
     426            0 :                 int ParametersNum = ICSParamNum + NumOfFlatPlateParam;
     427              : 
     428            0 :                 state.dataInputProcessing->inputProcessor->getObjectItem(state,
     429              :                                                                          CurrentModuleParamObject,
     430              :                                                                          ICSParamNum,
     431            0 :                                                                          state.dataIPShortCut->cAlphaArgs,
     432              :                                                                          NumAlphas,
     433            0 :                                                                          state.dataIPShortCut->rNumericArgs,
     434              :                                                                          NumNumbers,
     435              :                                                                          IOStatus,
     436            0 :                                                                          state.dataIPShortCut->lNumericFieldBlanks,
     437              :                                                                          _,
     438            0 :                                                                          state.dataIPShortCut->cAlphaFieldNames,
     439            0 :                                                                          state.dataIPShortCut->cNumericFieldNames);
     440              : 
     441              :                 // Collector module parameters name
     442            0 :                 GlobalNames::VerifyUniqueInterObjectName(state,
     443            0 :                                                          state.dataSolarCollectors->UniqueParametersNames,
     444            0 :                                                          state.dataIPShortCut->cAlphaArgs(1),
     445              :                                                          CurrentModuleObject,
     446            0 :                                                          state.dataIPShortCut->cAlphaFieldNames(1),
     447              :                                                          ErrorsFound);
     448            0 :                 state.dataSolarCollectors->Parameters(ParametersNum).Name = state.dataIPShortCut->cAlphaArgs(1);
     449              :                 // NOTE:  currently the only available choice is RectangularTank.  In the future progressive tube type will be
     450              :                 //        added
     451              :                 //                if (Util::SameString(state.dataIPShortCut->cAlphaArgs(2), "RectangularTank")) {
     452              :                 //                    state.dataSolarCollectors->Parameters(ParametersNum).ICSType_Num = TankTypeEnum::ICSRectangularTank;
     453              :                 //                } else {
     454              :                 //                    ShowSevereError(state, format("{}{} not found={}{} in {}{} ={}{}", //,
     455              :                 //                    state.dataIPShortCut->cAlphaFieldNames(2), state.dataIPShortCut->cAlphaArgs(2), //, //,
     456              :                 //                    CurrentModuleParamObject, //, state.dataSolarCollectors->Parameters(ParametersNum).Name)); ErrorsFound = true;
     457              :                 //                }
     458              :                 // NOTE:  This collector gross area is used in all the calculations.
     459            0 :                 state.dataSolarCollectors->Parameters(ParametersNum).Area = state.dataIPShortCut->rNumericArgs(1);
     460            0 :                 if (state.dataIPShortCut->rNumericArgs(1) <= 0.0) {
     461            0 :                     ShowSevereError(state, format("{} = {}", CurrentModuleParamObject, state.dataIPShortCut->cAlphaArgs(1)));
     462            0 :                     ShowContinueError(
     463            0 :                         state, format("Illegal {} = {:.2R}", state.dataIPShortCut->cNumericFieldNames(1), state.dataIPShortCut->rNumericArgs(1)));
     464            0 :                     ShowContinueError(state, " Collector gross area must be always greater than zero.");
     465            0 :                     ErrorsFound = true;
     466              :                 }
     467            0 :                 state.dataSolarCollectors->Parameters(ParametersNum).Volume = state.dataIPShortCut->rNumericArgs(2);
     468            0 :                 if (state.dataIPShortCut->rNumericArgs(2) <= 0.0) {
     469            0 :                     ShowSevereError(state, format("{} = {}", CurrentModuleParamObject, state.dataIPShortCut->cAlphaArgs(1)));
     470            0 :                     ShowContinueError(
     471            0 :                         state, format("Illegal {} = {:.2R}", state.dataIPShortCut->cNumericFieldNames(2), state.dataIPShortCut->rNumericArgs(2)));
     472            0 :                     ShowContinueError(state, " Collector water volume must be always greater than zero.");
     473            0 :                     ErrorsFound = true;
     474              :                 }
     475              :                 // Note: this value is used to calculate the heat loss through the bottom and side of the collector
     476            0 :                 state.dataSolarCollectors->Parameters(ParametersNum).ULossBottom = state.dataIPShortCut->rNumericArgs(3);
     477            0 :                 state.dataSolarCollectors->Parameters(ParametersNum).ULossSide = state.dataIPShortCut->rNumericArgs(4);
     478            0 :                 state.dataSolarCollectors->Parameters(ParametersNum).AspectRatio = state.dataIPShortCut->rNumericArgs(5);
     479            0 :                 state.dataSolarCollectors->Parameters(ParametersNum).SideHeight = state.dataIPShortCut->rNumericArgs(6);
     480            0 :                 state.dataSolarCollectors->Parameters(ParametersNum).ThermalMass = state.dataIPShortCut->rNumericArgs(7);
     481            0 :                 state.dataSolarCollectors->Parameters(ParametersNum).NumOfCovers = state.dataIPShortCut->rNumericArgs(8);
     482            0 :                 state.dataSolarCollectors->Parameters(ParametersNum).CoverSpacing = state.dataIPShortCut->rNumericArgs(9);
     483              : 
     484            0 :                 if (state.dataSolarCollectors->Parameters(ParametersNum).NumOfCovers == 2) {
     485              :                     // Outer cover refractive index
     486            0 :                     state.dataSolarCollectors->Parameters(ParametersNum).RefractiveIndex[0] = state.dataIPShortCut->rNumericArgs(10);
     487              :                     // Outer cover extinction coefficient times thickness of the cover
     488            0 :                     state.dataSolarCollectors->Parameters(ParametersNum).ExtCoefTimesThickness[0] = state.dataIPShortCut->rNumericArgs(11);
     489              :                     // Outer cover Emissivity
     490            0 :                     state.dataSolarCollectors->Parameters(ParametersNum).EmissOfCover[0] = state.dataIPShortCut->rNumericArgs(12);
     491              : 
     492            0 :                     if (!state.dataIPShortCut->lNumericFieldBlanks(13) || !state.dataIPShortCut->lNumericFieldBlanks(14) ||
     493            0 :                         !state.dataIPShortCut->lNumericFieldBlanks(15)) {
     494            0 :                         state.dataSolarCollectors->Parameters(ParametersNum).RefractiveIndex[1] = state.dataIPShortCut->rNumericArgs(13);
     495            0 :                         state.dataSolarCollectors->Parameters(ParametersNum).ExtCoefTimesThickness[1] = state.dataIPShortCut->rNumericArgs(14);
     496            0 :                         state.dataSolarCollectors->Parameters(ParametersNum).EmissOfCover[1] = state.dataIPShortCut->rNumericArgs(15);
     497              :                     } else {
     498            0 :                         ShowSevereError(state, format("{} = {}", CurrentModuleParamObject, state.dataIPShortCut->cAlphaArgs(1)));
     499            0 :                         ShowContinueError(state, "Illegal input for one of the three inputs of the inner cover optical properties");
     500            0 :                         ErrorsFound = true;
     501              :                     }
     502            0 :                 } else if (state.dataSolarCollectors->Parameters(ParametersNum).NumOfCovers == 1) {
     503              :                     // Outer cover refractive index
     504            0 :                     state.dataSolarCollectors->Parameters(ParametersNum).RefractiveIndex[0] = state.dataIPShortCut->rNumericArgs(10);
     505              :                     // Outer cover extinction coefficient times thickness of the cover
     506            0 :                     state.dataSolarCollectors->Parameters(ParametersNum).ExtCoefTimesThickness[0] = state.dataIPShortCut->rNumericArgs(11);
     507              :                     // Outer cover emissivity
     508            0 :                     state.dataSolarCollectors->Parameters(ParametersNum).EmissOfCover[0] = state.dataIPShortCut->rNumericArgs(12);
     509              :                 } else {
     510            0 :                     ShowSevereError(state, format("{} = {}", CurrentModuleParamObject, state.dataIPShortCut->cAlphaArgs(1)));
     511            0 :                     ShowContinueError(
     512            0 :                         state, format("Illegal {} = {:.2R}", state.dataIPShortCut->cNumericFieldNames(8), state.dataIPShortCut->rNumericArgs(8)));
     513            0 :                     ErrorsFound = true;
     514              :                 }
     515              :                 // Solar absorptance of the absorber plate
     516            0 :                 state.dataSolarCollectors->Parameters(ParametersNum).AbsorOfAbsPlate = state.dataIPShortCut->rNumericArgs(16);
     517              :                 // thermal emissivity of the absorber plate
     518            0 :                 state.dataSolarCollectors->Parameters(ParametersNum).EmissOfAbsPlate = state.dataIPShortCut->rNumericArgs(17);
     519              : 
     520              :             } // end of ParametersNum
     521              : 
     522            0 :             if (ErrorsFound) ShowFatalError(state, format("Errors in {} input.", CurrentModuleParamObject));
     523              : 
     524            0 :             CurrentModuleObject = "SolarCollector:IntegralCollectorStorage";
     525              : 
     526            0 :             for (int ICSUnitsNum = 1; ICSUnitsNum <= NumOfICSUnits; ++ICSUnitsNum) {
     527              : 
     528            0 :                 int CollectorNum = ICSUnitsNum + NumFlatPlateUnits;
     529              : 
     530            0 :                 state.dataInputProcessing->inputProcessor->getObjectItem(state,
     531              :                                                                          CurrentModuleObject,
     532              :                                                                          ICSUnitsNum,
     533            0 :                                                                          state.dataIPShortCut->cAlphaArgs,
     534              :                                                                          NumAlphas,
     535            0 :                                                                          state.dataIPShortCut->rNumericArgs,
     536              :                                                                          NumNumbers,
     537              :                                                                          IOStatus,
     538            0 :                                                                          state.dataIPShortCut->lNumericFieldBlanks,
     539              :                                                                          lAlphaBlanks,
     540            0 :                                                                          state.dataIPShortCut->cAlphaFieldNames,
     541            0 :                                                                          state.dataIPShortCut->cNumericFieldNames);
     542              : 
     543              :                 // Collector name
     544            0 :                 GlobalNames::VerifyUniqueInterObjectName(state,
     545            0 :                                                          state.dataSolarCollectors->UniqueCollectorNames,
     546            0 :                                                          state.dataIPShortCut->cAlphaArgs(1),
     547              :                                                          CurrentModuleObject,
     548            0 :                                                          state.dataIPShortCut->cAlphaFieldNames(1),
     549              :                                                          ErrorsFound);
     550            0 :                 state.dataSolarCollectors->Collector(CollectorNum).Name = state.dataIPShortCut->cAlphaArgs(1);
     551            0 :                 state.dataSolarCollectors->Collector(CollectorNum).Type =
     552              :                     DataPlant::PlantEquipmentType::SolarCollectorICS; // parameter assigned in DataPlant
     553              : 
     554            0 :                 state.dataSolarCollectors->Collector(CollectorNum).InitICS = true;
     555              : 
     556              :                 // Get parameters object
     557            0 :                 int ParametersNum = Util::FindItemInList(state.dataIPShortCut->cAlphaArgs(2), state.dataSolarCollectors->Parameters);
     558              : 
     559            0 :                 if (ParametersNum == 0) {
     560            0 :                     ShowSevereError(state,
     561            0 :                                     format("{} = {}: {} object called {} not found.",
     562              :                                            CurrentModuleObject,
     563            0 :                                            state.dataIPShortCut->cAlphaArgs(1),
     564              :                                            CurrentModuleParamObject,
     565            0 :                                            state.dataIPShortCut->cAlphaArgs(2)));
     566            0 :                     ErrorsFound = true;
     567              :                 } else {
     568            0 :                     state.dataSolarCollectors->Collector(CollectorNum).Parameters = ParametersNum;
     569              :                 }
     570              : 
     571            0 :                 if (ParametersNum > 0) {
     572              :                     // Calculate constant collector parameters only once
     573            0 :                     Real64 Perimeter = 2.0 * std::sqrt(state.dataSolarCollectors->Parameters(ParametersNum).Area) *
     574            0 :                                        (std::sqrt(state.dataSolarCollectors->Parameters(ParametersNum).AspectRatio) +
     575            0 :                                         1.0 / std::sqrt(state.dataSolarCollectors->Parameters(ParametersNum).AspectRatio));
     576            0 :                     state.dataSolarCollectors->Collector(CollectorNum).Length = std::sqrt(
     577            0 :                         state.dataSolarCollectors->Parameters(ParametersNum).Area / state.dataSolarCollectors->Parameters(ParametersNum).AspectRatio);
     578              : 
     579              :                     // calculate the collector side heat transfer area and loss coefficient
     580            0 :                     state.dataSolarCollectors->Collector(CollectorNum).Area = state.dataSolarCollectors->Parameters(ParametersNum).Area;
     581            0 :                     state.dataSolarCollectors->Collector(CollectorNum).Volume = state.dataSolarCollectors->Parameters(ParametersNum).Volume;
     582            0 :                     state.dataSolarCollectors->Collector(CollectorNum).SideArea =
     583            0 :                         Perimeter * state.dataSolarCollectors->Parameters(ParametersNum).SideHeight;
     584            0 :                     state.dataSolarCollectors->Collector(CollectorNum).AreaRatio =
     585            0 :                         state.dataSolarCollectors->Collector(CollectorNum).SideArea / state.dataSolarCollectors->Collector(CollectorNum).Area;
     586              :                 }
     587              :                 // Get surface object
     588            0 :                 int SurfNum = Util::FindItemInList(state.dataIPShortCut->cAlphaArgs(3), state.dataSurface->Surface);
     589              : 
     590            0 :                 if (SurfNum == 0) {
     591            0 :                     ShowSevereError(state,
     592            0 :                                     format("{} = {}:  Surface {} not found.",
     593              :                                            CurrentModuleObject,
     594            0 :                                            state.dataIPShortCut->cAlphaArgs(1),
     595            0 :                                            state.dataIPShortCut->cAlphaArgs(3)));
     596            0 :                     ErrorsFound = true;
     597            0 :                     continue; // avoid hard crash
     598              :                 } else {
     599              : 
     600            0 :                     if (!state.dataSurface->Surface(SurfNum).ExtSolar) {
     601            0 :                         ShowWarningError(state,
     602            0 :                                          format("{} = {}:  Surface {} is not exposed to exterior radiation.",
     603              :                                                 CurrentModuleObject,
     604            0 :                                                 state.dataIPShortCut->cAlphaArgs(1),
     605            0 :                                                 state.dataIPShortCut->cAlphaArgs(3)));
     606              :                     }
     607              : 
     608              :                     // check surface orientation, warn if upside down
     609            0 :                     if ((state.dataSurface->Surface(SurfNum).Tilt < -95.0) || (state.dataSurface->Surface(SurfNum).Tilt > 95.0)) {
     610            0 :                         ShowWarningError(state,
     611            0 :                                          format("Suspected input problem with {} = {}",
     612            0 :                                                 state.dataIPShortCut->cAlphaFieldNames(3),
     613            0 :                                                 state.dataIPShortCut->cAlphaArgs(3)));
     614            0 :                         ShowContinueError(
     615            0 :                             state, format("Entered in {} = {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
     616            0 :                         ShowContinueError(state, "Surface used for solar collector faces down");
     617            0 :                         ShowContinueError(
     618              :                             state,
     619            0 :                             format("Surface tilt angle (degrees from ground outward normal) = {:.2R}", state.dataSurface->Surface(SurfNum).Tilt));
     620              :                     }
     621              : 
     622              :                     // Check to make sure other solar collectors are not using the same surface
     623              :                     // NOTE:  Must search over all solar collector types
     624            0 :                     for (int CollectorNum2 = 1; CollectorNum2 <= state.dataSolarCollectors->NumOfCollectors; ++CollectorNum2) {
     625            0 :                         if (state.dataSolarCollectors->Collector(CollectorNum2).Surface == SurfNum) {
     626            0 :                             ShowSevereError(state,
     627            0 :                                             format("{} = {}:  Surface {} is referenced by more than one {}",
     628              :                                                    CurrentModuleObject,
     629            0 :                                                    state.dataIPShortCut->cAlphaArgs(1),
     630            0 :                                                    state.dataIPShortCut->cAlphaArgs(3),
     631              :                                                    CurrentModuleObject));
     632            0 :                             ErrorsFound = true;
     633            0 :                             break;
     634              :                         }
     635              :                     } // ICSNum2
     636              : 
     637            0 :                     state.dataSolarCollectors->Collector(CollectorNum).Surface = SurfNum;
     638              :                 }
     639              : 
     640              :                 // Give warning if surface area and gross area do not match within tolerance
     641            0 :                 if (SurfNum > 0 && ParametersNum > 0 && state.dataSolarCollectors->Parameters(ParametersNum).Area > 0.0 &&
     642            0 :                     std::abs(state.dataSolarCollectors->Parameters(ParametersNum).Area - state.dataSurface->Surface(SurfNum).Area) /
     643            0 :                             state.dataSurface->Surface(SurfNum).Area >
     644              :                         0.01) {
     645              : 
     646            0 :                     ShowWarningError(state, format("{} = {}: ", CurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
     647            0 :                     ShowContinueError(state, "Gross area of solar collector parameters and surface object differ by more than 1%.");
     648            0 :                     ShowContinueError(state, "Gross collector area is always used in the calculation.  Modify the surface ");
     649            0 :                     ShowContinueError(state, "coordinates to match its area with collector gross area. Otherwise, the underlying ");
     650            0 :                     ShowContinueError(state, "surface is assumed to be fully shaded when it is not.");
     651              :                 }
     652              : 
     653            0 :                 state.dataSolarCollectors->Collector(CollectorNum).BCType = state.dataIPShortCut->cAlphaArgs(4);
     654            0 :                 if (Util::SameString(state.dataIPShortCut->cAlphaArgs(4), "AmbientAir")) {
     655            0 :                     state.dataSolarCollectors->Collector(CollectorNum).OSCMName = "";
     656            0 :                 } else if (Util::SameString(state.dataIPShortCut->cAlphaArgs(4), "OtherSideConditionsModel")) {
     657            0 :                     state.dataSolarCollectors->Collector(CollectorNum).OSCMName = state.dataIPShortCut->cAlphaArgs(5);
     658            0 :                     state.dataSolarCollectors->Collector(CollectorNum).OSCM_ON = true;
     659            0 :                     int Found = Util::FindItemInList(state.dataSolarCollectors->Collector(CollectorNum).OSCMName, state.dataSurface->OSCM);
     660            0 :                     if (Found == 0) {
     661            0 :                         ShowSevereError(state,
     662            0 :                                         format("{} not found={} in {} ={}",
     663            0 :                                                state.dataIPShortCut->cAlphaFieldNames(5),
     664            0 :                                                state.dataSolarCollectors->Collector(CollectorNum).OSCMName,
     665              :                                                CurrentModuleObject,
     666            0 :                                                state.dataSolarCollectors->Collector(CollectorNum).Name));
     667            0 :                         ErrorsFound = true;
     668              :                     }
     669              :                 } else {
     670            0 :                     ShowSevereError(state,
     671            0 :                                     format("{} not found={} in {} ={}",
     672            0 :                                            state.dataIPShortCut->cAlphaFieldNames(5),
     673            0 :                                            state.dataSolarCollectors->Collector(CollectorNum).BCType,
     674              :                                            CurrentModuleObject,
     675            0 :                                            state.dataSolarCollectors->Collector(CollectorNum).Name));
     676            0 :                     ErrorsFound = true;
     677              :                 }
     678              : 
     679            0 :                 if (state.dataSolarCollectors->Collector(CollectorNum).OSCM_ON) {
     680              :                     // get index of ventilated cavity object
     681            0 :                     int VentCavIndex = 0;
     682            0 :                     SolarCollectors::CollectorData::GetExtVentedCavityIndex(state, SurfNum, VentCavIndex);
     683            0 :                     state.dataSolarCollectors->Collector(CollectorNum).VentCavIndex = VentCavIndex;
     684              :                 }
     685              : 
     686            0 :                 state.dataSolarCollectors->Collector(CollectorNum).InletNode =
     687            0 :                     NodeInputManager::GetOnlySingleNode(state,
     688            0 :                                                         state.dataIPShortCut->cAlphaArgs(6),
     689              :                                                         ErrorsFound,
     690              :                                                         DataLoopNode::ConnectionObjectType::SolarCollectorIntegralCollectorStorage,
     691            0 :                                                         state.dataIPShortCut->cAlphaArgs(1),
     692              :                                                         DataLoopNode::NodeFluidType::Water,
     693              :                                                         DataLoopNode::ConnectionType::Inlet,
     694              :                                                         NodeInputManager::CompFluidStream::Primary,
     695              :                                                         DataLoopNode::ObjectIsNotParent);
     696            0 :                 state.dataSolarCollectors->Collector(CollectorNum).OutletNode =
     697            0 :                     NodeInputManager::GetOnlySingleNode(state,
     698            0 :                                                         state.dataIPShortCut->cAlphaArgs(7),
     699              :                                                         ErrorsFound,
     700              :                                                         DataLoopNode::ConnectionObjectType::SolarCollectorIntegralCollectorStorage,
     701            0 :                                                         state.dataIPShortCut->cAlphaArgs(1),
     702              :                                                         DataLoopNode::NodeFluidType::Water,
     703              :                                                         DataLoopNode::ConnectionType::Outlet,
     704              :                                                         NodeInputManager::CompFluidStream::Primary,
     705              :                                                         DataLoopNode::ObjectIsNotParent);
     706              : 
     707            0 :                 if (NumNumbers > 0) {
     708            0 :                     state.dataSolarCollectors->Collector(CollectorNum).VolFlowRateMax =
     709            0 :                         state.dataIPShortCut->rNumericArgs(1); // Max volumetric flow rate used for plant sizing calculation
     710              :                 } else {
     711            0 :                     state.dataSolarCollectors->Collector(CollectorNum).VolFlowRateMax =
     712              :                         0.0; // Max vol flow rate is not specified; no flow for plant sizing calculation
     713            0 :                     state.dataSolarCollectors->Collector(CollectorNum).MassFlowRateMax =
     714              :                         999999.9; // But...set a very high value so that it demands as much as possible
     715              :                 }
     716              : 
     717            0 :                 BranchNodeConnections::TestCompSet(state,
     718              :                                                    CurrentModuleObject,
     719            0 :                                                    state.dataIPShortCut->cAlphaArgs(1),
     720            0 :                                                    state.dataIPShortCut->cAlphaArgs(6),
     721            0 :                                                    state.dataIPShortCut->cAlphaArgs(7),
     722              :                                                    "Water Nodes");
     723              : 
     724              :             } // ICSNum
     725              : 
     726            0 :             if (ErrorsFound) ShowFatalError(state, format("Errors in {} input.", CurrentModuleObject));
     727              :         }
     728            0 :     }
     729              : 
     730            0 :     void CollectorData::setupOutputVars(EnergyPlusData &state)
     731              :     {
     732            0 :         if (this->Type == DataPlant::PlantEquipmentType::SolarCollectorFlatPlate) {
     733              :             // Setup report variables
     734            0 :             SetupOutputVariable(state,
     735              :                                 "Solar Collector Incident Angle Modifier",
     736              :                                 Constant::Units::None,
     737            0 :                                 this->IncidentAngleModifier,
     738              :                                 OutputProcessor::TimeStepType::System,
     739              :                                 OutputProcessor::StoreType::Average,
     740            0 :                                 this->Name);
     741              : 
     742            0 :             SetupOutputVariable(state,
     743              :                                 "Solar Collector Efficiency",
     744              :                                 Constant::Units::None,
     745            0 :                                 this->Efficiency,
     746              :                                 OutputProcessor::TimeStepType::System,
     747              :                                 OutputProcessor::StoreType::Average,
     748            0 :                                 this->Name);
     749              : 
     750            0 :             SetupOutputVariable(state,
     751              :                                 "Solar Collector Heat Transfer Rate",
     752              :                                 Constant::Units::W,
     753            0 :                                 this->Power,
     754              :                                 OutputProcessor::TimeStepType::System,
     755              :                                 OutputProcessor::StoreType::Average,
     756            0 :                                 this->Name);
     757              : 
     758            0 :             SetupOutputVariable(state,
     759              :                                 "Solar Collector Heat Gain Rate",
     760              :                                 Constant::Units::W,
     761            0 :                                 this->HeatGain,
     762              :                                 OutputProcessor::TimeStepType::System,
     763              :                                 OutputProcessor::StoreType::Average,
     764            0 :                                 this->Name);
     765              : 
     766            0 :             SetupOutputVariable(state,
     767              :                                 "Solar Collector Heat Loss Rate",
     768              :                                 Constant::Units::W,
     769            0 :                                 this->HeatLoss,
     770              :                                 OutputProcessor::TimeStepType::System,
     771              :                                 OutputProcessor::StoreType::Average,
     772            0 :                                 this->Name);
     773              : 
     774            0 :             SetupOutputVariable(state,
     775              :                                 "Solar Collector Heat Transfer Energy",
     776              :                                 Constant::Units::J,
     777            0 :                                 this->Energy,
     778              :                                 OutputProcessor::TimeStepType::System,
     779              :                                 OutputProcessor::StoreType::Sum,
     780            0 :                                 this->Name,
     781              :                                 Constant::eResource::SolarWater,
     782              :                                 OutputProcessor::Group::Plant,
     783              :                                 OutputProcessor::EndUseCat::HeatProduced);
     784            0 :         } else if (this->Type == DataPlant::PlantEquipmentType::SolarCollectorICS) {
     785              : 
     786            0 :             SetupOutputVariable(state,
     787              :                                 "Solar Collector Transmittance Absorptance Product",
     788              :                                 Constant::Units::None,
     789            0 :                                 this->TauAlpha,
     790              :                                 OutputProcessor::TimeStepType::System,
     791              :                                 OutputProcessor::StoreType::Average,
     792            0 :                                 this->Name);
     793              : 
     794            0 :             SetupOutputVariable(state,
     795              :                                 "Solar Collector Overall Top Heat Loss Coefficient",
     796              :                                 Constant::Units::W_m2C,
     797            0 :                                 this->UTopLoss,
     798              :                                 OutputProcessor::TimeStepType::System,
     799              :                                 OutputProcessor::StoreType::Average,
     800            0 :                                 this->Name);
     801              : 
     802            0 :             SetupOutputVariable(state,
     803              :                                 "Solar Collector Absorber Plate Temperature",
     804              :                                 Constant::Units::C,
     805            0 :                                 this->TempOfAbsPlate,
     806              :                                 OutputProcessor::TimeStepType::System,
     807              :                                 OutputProcessor::StoreType::Average,
     808            0 :                                 this->Name);
     809              : 
     810            0 :             SetupOutputVariable(state,
     811              :                                 "Solar Collector Storage Water Temperature",
     812              :                                 Constant::Units::C,
     813            0 :                                 this->TempOfWater,
     814              :                                 OutputProcessor::TimeStepType::System,
     815              :                                 OutputProcessor::StoreType::Average,
     816            0 :                                 this->Name);
     817              : 
     818            0 :             SetupOutputVariable(state,
     819              :                                 "Solar Collector Thermal Efficiency",
     820              :                                 Constant::Units::None,
     821            0 :                                 this->Efficiency,
     822              :                                 OutputProcessor::TimeStepType::System,
     823              :                                 OutputProcessor::StoreType::Average,
     824            0 :                                 this->Name);
     825              : 
     826            0 :             SetupOutputVariable(state,
     827              :                                 "Solar Collector Storage Heat Transfer Rate",
     828              :                                 Constant::Units::W,
     829            0 :                                 this->StoredHeatRate,
     830              :                                 OutputProcessor::TimeStepType::System,
     831              :                                 OutputProcessor::StoreType::Average,
     832            0 :                                 this->Name);
     833              : 
     834            0 :             SetupOutputVariable(state,
     835              :                                 "Solar Collector Storage Heat Transfer Energy",
     836              :                                 Constant::Units::J,
     837            0 :                                 this->StoredHeatEnergy,
     838              :                                 OutputProcessor::TimeStepType::System,
     839              :                                 OutputProcessor::StoreType::Sum,
     840            0 :                                 this->Name,
     841              :                                 Constant::eResource::SolarWater,
     842              :                                 OutputProcessor::Group::Plant,
     843              :                                 OutputProcessor::EndUseCat::HeatProduced);
     844              : 
     845            0 :             SetupOutputVariable(state,
     846              :                                 "Solar Collector Skin Heat Transfer Rate",
     847              :                                 Constant::Units::W,
     848            0 :                                 this->SkinHeatLossRate,
     849              :                                 OutputProcessor::TimeStepType::System,
     850              :                                 OutputProcessor::StoreType::Average,
     851            0 :                                 this->Name);
     852              : 
     853            0 :             SetupOutputVariable(state,
     854              :                                 "Solar Collector Skin Heat Transfer Energy",
     855              :                                 Constant::Units::J,
     856            0 :                                 this->CollHeatLossEnergy,
     857              :                                 OutputProcessor::TimeStepType::System,
     858              :                                 OutputProcessor::StoreType::Sum,
     859            0 :                                 this->Name,
     860              :                                 Constant::eResource::SolarWater,
     861              :                                 OutputProcessor::Group::Plant,
     862              :                                 OutputProcessor::EndUseCat::HeatProduced);
     863              : 
     864            0 :             SetupOutputVariable(state,
     865              :                                 "Solar Collector Heat Transfer Rate",
     866              :                                 Constant::Units::W,
     867            0 :                                 this->HeatRate,
     868              :                                 OutputProcessor::TimeStepType::System,
     869              :                                 OutputProcessor::StoreType::Average,
     870            0 :                                 this->Name);
     871              : 
     872            0 :             SetupOutputVariable(state,
     873              :                                 "Solar Collector Heat Transfer Energy",
     874              :                                 Constant::Units::J,
     875            0 :                                 this->HeatEnergy,
     876              :                                 OutputProcessor::TimeStepType::System,
     877              :                                 OutputProcessor::StoreType::Sum,
     878            0 :                                 this->Name,
     879              :                                 Constant::eResource::SolarWater,
     880              :                                 OutputProcessor::Group::Plant,
     881              :                                 OutputProcessor::EndUseCat::HeatProduced);
     882              :         }
     883            0 :     }
     884              : 
     885            0 :     void CollectorData::simulate(EnergyPlusData &state,
     886              :                                  [[maybe_unused]] const PlantLocation &calledFromLocation,
     887              :                                  [[maybe_unused]] bool const FirstHVACIteration,
     888              :                                  [[maybe_unused]] Real64 &CurLoad,
     889              :                                  [[maybe_unused]] bool const RunFlag)
     890              :     {
     891            0 :         this->initialize(state);
     892              : 
     893            0 :         switch (this->Type) {
     894              :             // Select and CALL models based on collector type
     895            0 :         case DataPlant::PlantEquipmentType::SolarCollectorFlatPlate: {
     896            0 :             this->CalcSolarCollector(state);
     897            0 :         } break;
     898            0 :         case DataPlant::PlantEquipmentType::SolarCollectorICS: {
     899            0 :             this->CalcICSSolarCollector(state);
     900            0 :         } break;
     901            0 :         default: {
     902              :             assert(false); // LCOV_EXCL_LINE
     903              :         } break;
     904              :         }
     905              : 
     906            0 :         this->update(state);
     907              : 
     908            0 :         this->report(state);
     909            0 :     }
     910              : 
     911            0 :     void CollectorData::initialize(EnergyPlusData &state)
     912              :     {
     913              : 
     914              :         // SUBROUTINE INFORMATION:
     915              :         //       AUTHOR         Peter Graham Ellis
     916              :         //       DATE WRITTEN   January 2004
     917              :         //       MODIFIED       na
     918              :         //       RE-ENGINEERED  na
     919              : 
     920              :         // PURPOSE OF THIS SUBROUTINE:
     921              :         // Initializes the solar collector object during the plant simulation.
     922              : 
     923              :         // METHODOLOGY EMPLOYED:
     924              :         // Inlet and outlet nodes are initialized.  The maximum collector flow rate is requested.
     925              : 
     926              :         static constexpr std::string_view RoutineName("InitSolarCollector");
     927            0 :         Real64 constexpr BigNumber(9999.9); // Component desired mass flow rate
     928              : 
     929            0 :         if (!state.dataGlobal->SysSizingCalc && this->InitSizing) {
     930            0 :             PlantUtilities::RegisterPlantCompDesignFlow(state, this->InletNode, this->VolFlowRateMax);
     931            0 :             this->InitSizing = false;
     932              :         }
     933              : 
     934            0 :         if (state.dataGlobal->BeginEnvrnFlag && this->Init) {
     935              :             // Clear node initial conditions
     936            0 :             if (this->VolFlowRateMax > 0) {
     937            0 :                 Real64 rho = this->plantLoc.loop->glycol->getDensity(state, Constant::InitConvTemp, RoutineName);
     938              : 
     939            0 :                 this->MassFlowRateMax = this->VolFlowRateMax * rho;
     940              :             } else {
     941            0 :                 this->MassFlowRateMax = BigNumber;
     942              :             }
     943              : 
     944            0 :             PlantUtilities::InitComponentNodes(state, 0.0, this->MassFlowRateMax, this->InletNode, this->OutletNode);
     945              : 
     946            0 :             this->Init = false;
     947              : 
     948            0 :             if (this->InitICS) {
     949            0 :                 this->TempOfWater = 20.0;
     950            0 :                 this->SavedTempOfWater = this->TempOfWater;
     951            0 :                 this->SavedTempOfAbsPlate = this->TempOfWater;
     952            0 :                 this->TempOfAbsPlate = this->TempOfWater;
     953            0 :                 this->TempOfInnerCover = this->TempOfWater;
     954            0 :                 this->TempOfOuterCover = this->TempOfWater;
     955            0 :                 this->SavedTempOfInnerCover = this->TempOfWater;
     956            0 :                 this->SavedTempOfOuterCover = this->TempOfWater;
     957            0 :                 this->SavedTempCollectorOSCM = this->TempOfWater;
     958              :             }
     959              :         }
     960              : 
     961            0 :         if (!state.dataGlobal->BeginEnvrnFlag) this->Init = true;
     962              : 
     963            0 :         if (this->SetDiffRadFlag && this->InitICS) {
     964              :             // calculates the sky and ground reflective diffuse radiation optical properties (only one time)
     965            0 :             int SurfNum = this->Surface;
     966            0 :             int ParamNum = this->Parameters;
     967              : 
     968            0 :             this->Tilt = state.dataSurface->Surface(SurfNum).Tilt;
     969            0 :             this->TiltR2V = std::abs(90.0 - Tilt);
     970            0 :             this->CosTilt = std::cos(Tilt * Constant::DegToRad);
     971            0 :             this->SinTilt = std::sin(1.8 * Tilt * Constant::DegToRad);
     972              : 
     973              :             // Diffuse reflectance of the cover for solar radiation diffusely reflected back from the absorber
     974              :             // plate to the cover.  The diffuse solar radiation reflected back from the absorber plate to the
     975              :             // cover is represented by the 60 degree equivalent incident angle.  This diffuse reflectance is
     976              :             // used to calculate the transmittance - absorptance product (Duffie and Beckman, 1991)
     977            0 :             Real64 Theta = 60.0 * Constant::DegToRad;
     978            0 :             Real64 TransSys = 0.0;
     979            0 :             Real64 RefSys = 0.0;
     980            0 :             Real64 AbsCover1 = 0.0;
     981            0 :             Real64 AbsCover2 = 0.0;
     982            0 :             Real64 RefSysDiffuse = 0.0;
     983            0 :             this->CalcTransRefAbsOfCover(state, Theta, TransSys, RefSys, AbsCover1, AbsCover2, true, RefSysDiffuse);
     984            0 :             this->RefDiffInnerCover = RefSysDiffuse;
     985              : 
     986              :             // transmittance-absorptance product normal incident:
     987            0 :             Theta = 0.0;
     988            0 :             this->CalcTransRefAbsOfCover(state, Theta, TransSys, RefSys, AbsCover1, AbsCover2);
     989              : 
     990              :             // transmittance-absorptance product for sky diffuse radiation.  Uses equivalent incident angle
     991              :             // of sky radiation (radians), and is calculated according to Brandemuehl and Beckman (1980):
     992            0 :             Theta = (59.68 - 0.1388 * Tilt + 0.001497 * pow_2(Tilt)) * Constant::DegToRad;
     993            0 :             this->CalcTransRefAbsOfCover(state, Theta, TransSys, RefSys, AbsCover1, AbsCover2);
     994            0 :             this->TauAlphaSkyDiffuse = TransSys * state.dataSolarCollectors->Parameters(ParamNum).AbsorOfAbsPlate /
     995            0 :                                        (1.0 - (1.0 - state.dataSolarCollectors->Parameters(ParamNum).AbsorOfAbsPlate) * this->RefDiffInnerCover);
     996            0 :             this->CoversAbsSkyDiffuse[0] = AbsCover1;
     997            0 :             this->CoversAbsSkyDiffuse[1] = AbsCover2;
     998              : 
     999              :             // transmittance-absorptance product for ground diffuse radiation.  Uses equivalent incident angle
    1000              :             // of ground radiation (radians), and is calculated according to Brandemuehl and Beckman (1980):
    1001            0 :             Theta = (90.0 - 0.5788 * Tilt + 0.002693 * pow_2(Tilt)) * Constant::DegToRad;
    1002            0 :             this->CalcTransRefAbsOfCover(state, Theta, TransSys, RefSys, AbsCover1, AbsCover2);
    1003            0 :             this->TauAlphaGndDiffuse = TransSys * state.dataSolarCollectors->Parameters(ParamNum).AbsorOfAbsPlate /
    1004            0 :                                        (1.0 - (1.0 - state.dataSolarCollectors->Parameters(ParamNum).AbsorOfAbsPlate) * this->RefDiffInnerCover);
    1005            0 :             this->CoversAbsGndDiffuse[0] = AbsCover1;
    1006            0 :             this->CoversAbsGndDiffuse[1] = AbsCover2;
    1007              : 
    1008            0 :             this->SetDiffRadFlag = false;
    1009              :         }
    1010              : 
    1011            0 :         this->InletTemp = state.dataLoopNodes->Node(this->InletNode).Temp;
    1012              : 
    1013            0 :         this->MassFlowRate = this->MassFlowRateMax;
    1014              : 
    1015              :         // Request the mass flow rate from the plant component flow utility routine
    1016            0 :         PlantUtilities::SetComponentFlowRate(state, this->MassFlowRate, this->InletNode, this->OutletNode, this->plantLoc);
    1017              : 
    1018            0 :         if (this->InitICS) {
    1019              : 
    1020              :             Real64 timeElapsed =
    1021            0 :                 state.dataGlobal->HourOfDay + state.dataGlobal->TimeStep * state.dataGlobal->TimeStepZone + state.dataHVACGlobal->SysTimeElapsed;
    1022              : 
    1023            0 :             if (this->TimeElapsed != timeElapsed) {
    1024              :                 // The simulation has advanced to the next system timestep.  Save conditions from the end of the previous
    1025              :                 // system timestep for use as initial condition of each iteration that does not advance system timestep.
    1026            0 :                 this->SavedTempOfWater = this->TempOfWater;
    1027            0 :                 this->SavedTempOfAbsPlate = this->TempOfAbsPlate;
    1028            0 :                 this->SavedTempOfInnerCover = this->TempOfInnerCover;
    1029            0 :                 this->SavedTempOfOuterCover = this->TempOfOuterCover;
    1030            0 :                 if (this->OSCM_ON) {
    1031            0 :                     this->SavedTempCollectorOSCM = state.dataHeatBal->ExtVentedCavity(this->VentCavIndex).Tbaffle;
    1032              :                 }
    1033            0 :                 this->TimeElapsed = timeElapsed;
    1034              :             }
    1035              :         }
    1036            0 :     }
    1037              : 
    1038            0 :     void CollectorData::CalcSolarCollector(EnergyPlusData &state)
    1039              :     {
    1040              : 
    1041              :         // SUBROUTINE INFORMATION:
    1042              :         //       AUTHOR         Peter Graham Ellis
    1043              :         //       DATE WRITTEN   January 2004
    1044              :         //       MODIFIED       na
    1045              :         //       RE-ENGINEERED  na
    1046              : 
    1047              :         // PURPOSE OF THIS SUBROUTINE:
    1048              :         // Calculates the heat gain (or loss), outlet temperature, and solar energy conversion efficiency for a flat-plate
    1049              :         // solar collector when there is a fluid flow.  For the no flow condition, the fluid stagnation temperature is
    1050              :         // calculated as the outlet temperature.  Glazed and unglazed collectors are both handled.
    1051              : 
    1052              :         // METHODOLOGY EMPLOYED:
    1053              :         // Calculation is performed using the methodology described in the ASHRAE standards and references below.  Measured
    1054              :         // collector performance coefficients (available from the Solar Rating & Certification Corporation, for example)
    1055              :         // are modified from the test conditions to match the actual optical (incident angle modifier) and thermal (flow rate
    1056              :         // modifier) conditions.  Water is assumed to be the heat transfer fluid.
    1057              : 
    1058              :         // REFERENCES:
    1059              :         // ASHRAE Standard 93-1986 (RA 91), "Methods of Testing to Determine the Thermal Performance of Solar Collectors".
    1060              :         // ASHRAE Standard 96-1980 (RA 89), "Methods of Testing to Determine the Thermal Performance of Unglazed Flat-Plate
    1061              :         //   Liquid-Type Solar Collectors".
    1062              :         // Duffie, J. A., and Beckman, W. A.  Solar Engineering of Thermal Processes, Second Edition.  Wiley-Interscience:
    1063              :         //   New York (1991).
    1064              : 
    1065              :         // NOTES:
    1066              :         // This subroutine has been validated against the TRNSYS Type 1 flat-plate solar collector module.  Results are
    1067              :         // identical except for slight differences at extreme incident angles (>80 degrees) and extreme surface tilts (<20
    1068              :         // degrees).  The differences are due to the fact that Type 1 does not prevent the *component* incident angle
    1069              :         // modifiers from being less than zero.  There is an effect on the net incident angle modifier if one or more
    1070              :         // components are less than zero but the net adds up to greater than zero.  The EnergyPlus subroutine, on the other
    1071              :         // hand, requires each component incident angle modifier always to be greater than zero.
    1072              : 
    1073              :         static constexpr std::string_view RoutineName("CalcSolarCollector");
    1074            0 :         Real64 efficiency = 0.0; // Thermal efficiency of solar energy conversion
    1075              : 
    1076            0 :         int SurfNum = this->Surface;
    1077            0 :         int ParamNum = this->Parameters;
    1078              :         Real64 incidentAngleModifier; // Net incident angle modifier combining beam, sky, and ground radiation
    1079              : 
    1080              :         // Calculate incident angle modifier
    1081            0 :         if (state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) > 0.0) {
    1082              :             // Equivalent incident angle of sky radiation (radians)
    1083            0 :             Real64 ThetaBeam = std::acos(state.dataHeatBal->SurfCosIncidenceAngle(SurfNum));
    1084              : 
    1085              :             // Calculate equivalent incident angles for sky and ground radiation according to Brandemuehl and Beckman (1980)
    1086              :             // Surface tilt angle (degrees)
    1087            0 :             Real64 tilt = state.dataSurface->Surface(SurfNum).Tilt;
    1088              : 
    1089              :             // Equivalent incident angle of sky radiation (radians)
    1090            0 :             Real64 ThetaSky = (59.68 - 0.1388 * tilt + 0.001497 * pow_2(tilt)) * Constant::DegToRad;
    1091              : 
    1092              :             // Equivalent incident angle of ground radiation (radians)
    1093            0 :             Real64 ThetaGnd = (90.0 - 0.5788 * tilt + 0.002693 * pow_2(tilt)) * Constant::DegToRad;
    1094              : 
    1095            0 :             incidentAngleModifier =
    1096            0 :                 (state.dataHeatBal->SurfQRadSWOutIncidentBeam(SurfNum) * state.dataSolarCollectors->Parameters(ParamNum).IAM(state, ThetaBeam) +
    1097            0 :                  state.dataHeatBal->SurfQRadSWOutIncidentSkyDiffuse(SurfNum) * state.dataSolarCollectors->Parameters(ParamNum).IAM(state, ThetaSky) +
    1098            0 :                  state.dataHeatBal->SurfQRadSWOutIncidentGndDiffuse(SurfNum) * state.dataSolarCollectors->Parameters(ParamNum).IAM(state, ThetaGnd)) /
    1099            0 :                 state.dataHeatBal->SurfQRadSWOutIncident(SurfNum);
    1100              :         } else {
    1101            0 :             incidentAngleModifier = 0.0;
    1102              :         }
    1103              : 
    1104              :         // Inlet temperature from plant (C)
    1105            0 :         Real64 inletTemp = this->InletTemp;
    1106              : 
    1107              :         // Mass flow rate through collector (kg/s)
    1108            0 :         Real64 massFlowRate = this->MassFlowRate;
    1109              : 
    1110              :         // Specific heat of collector fluid (J/kg-K)
    1111            0 :         Real64 Cp = this->plantLoc.loop->glycol->getSpecificHeat(state, inletTemp, RoutineName);
    1112              : 
    1113              :         // Gross area of collector (m2)
    1114            0 :         Real64 area = state.dataSurface->Surface(SurfNum).Area;
    1115              : 
    1116              :         // = MassFlowRate * Cp / Area
    1117            0 :         Real64 mCpA = massFlowRate * Cp / area;
    1118              : 
    1119              :         // = MassFlowRateTest * Cp / Area (tested area)
    1120              :         Real64 mCpATest =
    1121            0 :             state.dataSolarCollectors->Parameters(ParamNum).TestMassFlowRate * Cp / state.dataSolarCollectors->Parameters(this->Parameters).Area;
    1122              : 
    1123            0 :         int Iteration = 1;
    1124              : 
    1125              :         // Outlet temperature or stagnation temperature in the collector (C)
    1126            0 :         Real64 outletTemp = 0.0;
    1127              : 
    1128              :         // Outlet temperature saved from previous iteration for convergence check (C)
    1129            0 :         Real64 OutletTempPrev = 999.9; // Set to a ridiculous number so that DO loop runs at least once
    1130              : 
    1131              :         // Heat gain or loss to collector fluid (W)
    1132            0 :         Real64 Q = 0.0;
    1133              : 
    1134            0 :         while (std::abs(outletTemp - OutletTempPrev) > state.dataHeatBal->TempConvergTol) { // Check for temperature convergence
    1135              : 
    1136            0 :             OutletTempPrev = outletTemp; // Save previous outlet temperature
    1137              : 
    1138              :             // Modifier for test correlation type:  INLET, AVERAGE, or OUTLET
    1139            0 :             Real64 TestTypeMod = 0.0;
    1140              : 
    1141              :             // FR * ULoss "prime" for test conditions = (eff1 + eff2 * deltaT)
    1142            0 :             Real64 FRULpTest = 0.0;
    1143              : 
    1144              :             // Modify coefficients depending on test correlation type
    1145            0 :             switch (state.dataSolarCollectors->Parameters(ParamNum).TestType) {
    1146            0 :             case TestTypeEnum::INLET: {
    1147            0 :                 FRULpTest = state.dataSolarCollectors->Parameters(ParamNum).eff1 +
    1148            0 :                             state.dataSolarCollectors->Parameters(ParamNum).eff2 * (inletTemp - state.dataSurface->SurfOutDryBulbTemp(SurfNum));
    1149            0 :                 TestTypeMod = 1.0;
    1150            0 :             } break;
    1151            0 :             case TestTypeEnum::AVERAGE: {
    1152            0 :                 FRULpTest = state.dataSolarCollectors->Parameters(ParamNum).eff1 +
    1153            0 :                             state.dataSolarCollectors->Parameters(ParamNum).eff2 *
    1154            0 :                                 ((inletTemp + outletTemp) * 0.5 - state.dataSurface->SurfOutDryBulbTemp(SurfNum));
    1155            0 :                 TestTypeMod = 1.0 / (1.0 - FRULpTest / (2.0 * mCpATest));
    1156            0 :             } break;
    1157            0 :             case TestTypeEnum::OUTLET: {
    1158            0 :                 FRULpTest = state.dataSolarCollectors->Parameters(ParamNum).eff1 +
    1159            0 :                             state.dataSolarCollectors->Parameters(ParamNum).eff2 * (outletTemp - state.dataSurface->SurfOutDryBulbTemp(SurfNum));
    1160            0 :                 TestTypeMod = 1.0 / (1.0 - FRULpTest / mCpATest);
    1161            0 :             } break;
    1162            0 :             default:
    1163            0 :                 break;
    1164              :             }
    1165              : 
    1166              :             // FR * tau * alpha at normal incidence = Y-intercept of collector efficiency
    1167            0 :             Real64 FRTAN = state.dataSolarCollectors->Parameters(ParamNum).eff0 * TestTypeMod;
    1168              : 
    1169              :             // FR * ULoss = 1st order coefficient of collector efficiency
    1170            0 :             Real64 FRUL = state.dataSolarCollectors->Parameters(ParamNum).eff1 * TestTypeMod;
    1171              : 
    1172              :             // FR * ULoss / T = 2nd order coefficient of collector efficiency
    1173            0 :             Real64 FRULT = state.dataSolarCollectors->Parameters(ParamNum).eff2 * TestTypeMod;
    1174            0 :             FRULpTest *= TestTypeMod;
    1175              : 
    1176            0 :             if (massFlowRate > 0.0) { // Calculate efficiency and heat transfer with flow
    1177              : 
    1178              :                 // Modifier for flow rate different from test flow rate
    1179            0 :                 Real64 FlowMod = 0.0;
    1180              : 
    1181              :                 // F prime * ULoss for test conditions = collector efficiency factor * overall loss coefficient
    1182              :                 Real64 FpULTest;
    1183              : 
    1184            0 :                 if ((1.0 + FRULpTest / mCpATest) > 0.0) {
    1185            0 :                     FpULTest = -mCpATest * std::log(1.0 + FRULpTest / mCpATest);
    1186              :                 } else {
    1187            0 :                     FpULTest = FRULpTest; // Avoid LOG( <0 )
    1188              :                 }
    1189              : 
    1190            0 :                 if ((-FpULTest / mCpA) < 700.0) {
    1191            0 :                     FlowMod = mCpA * (1.0 - std::exp(-FpULTest / mCpA));
    1192              :                 } else { // avoid EXP(too large #)
    1193              :                          // FlowMod = FlowMod; // Self-assignment commented out
    1194              :                 }
    1195            0 :                 if ((-FpULTest / mCpATest) < 700.0) {
    1196            0 :                     FlowMod /= (mCpATest * (1.0 - std::exp(-FpULTest / mCpATest)));
    1197              :                 } else {
    1198              :                     // FlowMod = FlowMod; // Self-assignment commented out
    1199              :                 }
    1200              : 
    1201              :                 // Calculate fluid heat gain (or loss)
    1202              :                 // Heat loss is possible if there is no incident radiation and fluid is still flowing.
    1203            0 :                 Q = (FRTAN * incidentAngleModifier * state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) +
    1204            0 :                      FRULpTest * (inletTemp - state.dataSurface->SurfOutDryBulbTemp(SurfNum))) *
    1205              :                     area * FlowMod;
    1206              : 
    1207            0 :                 outletTemp = inletTemp + Q / (massFlowRate * Cp);
    1208              : 
    1209              :                 // CR 7877 bound unreasonable result
    1210            0 :                 if (outletTemp < -100) {
    1211            0 :                     outletTemp = -100.0;
    1212            0 :                     Q = massFlowRate * Cp * (outletTemp - inletTemp);
    1213              :                 }
    1214            0 :                 if (outletTemp > 200) {
    1215            0 :                     outletTemp = 200.0;
    1216            0 :                     Q = massFlowRate * Cp * (outletTemp - inletTemp);
    1217              :                 }
    1218              : 
    1219            0 :                 if (state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) > 0.0) { // Calculate thermal efficiency
    1220              :                     // NOTE: Efficiency can be > 1 if Q > SurfQRadSWOutIncident because of favorable delta T, i.e. warm outdoor temperature
    1221            0 :                     efficiency =
    1222            0 :                         Q / (state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) * area); // Q has units of W; SurfQRadSWOutIncident has units of W/m2
    1223              :                 } else {
    1224            0 :                     efficiency = 0.0;
    1225              :                 }
    1226              : 
    1227              :             } else { // Calculate stagnation temperature of fluid in collector (no flow)
    1228            0 :                 Q = 0.0;
    1229            0 :                 efficiency = 0.0;
    1230              : 
    1231              :                 // Calculate temperature of stagnant fluid in collector
    1232            0 :                 Real64 A = -FRULT;
    1233            0 :                 Real64 B = -FRUL + 2.0 * FRULT * state.dataSurface->SurfOutDryBulbTemp(SurfNum);
    1234            0 :                 Real64 C = -FRULT * pow_2(state.dataSurface->SurfOutDryBulbTemp(SurfNum)) + FRUL * state.dataSurface->SurfOutDryBulbTemp(SurfNum) -
    1235            0 :                            FRTAN * incidentAngleModifier * state.dataHeatBal->SurfQRadSWOutIncident(SurfNum);
    1236            0 :                 Real64 qEquation = (pow_2(B) - 4.0 * A * C);
    1237            0 :                 if (qEquation < 0.0) {
    1238            0 :                     if (this->ErrIndex == 0) {
    1239            0 :                         ShowSevereMessage(state,
    1240            0 :                                           format("CalcSolarCollector: {}=\"{}\", possible bad input coefficients.",
    1241            0 :                                                  DataPlant::PlantEquipTypeNames[static_cast<int>(this->Type)],
    1242            0 :                                                  this->Name));
    1243            0 :                         ShowContinueError(state,
    1244              :                                           "...coefficients cause negative quadratic equation part in calculating temperature of stagnant fluid.");
    1245            0 :                         ShowContinueError(state, "...examine input coefficients for accuracy. Calculation will be treated as linear.");
    1246              :                     }
    1247            0 :                     ShowRecurringSevereErrorAtEnd(state,
    1248            0 :                                                   format("CalcSolarCollector: {}=\"{}\", coefficient error continues.",
    1249            0 :                                                          DataPlant::PlantEquipTypeNames[static_cast<int>(this->Type)],
    1250            0 :                                                          this->Name),
    1251            0 :                                                   this->ErrIndex,
    1252              :                                                   qEquation,
    1253              :                                                   qEquation);
    1254              :                 }
    1255            0 :                 if (FRULT == 0.0 || qEquation < 0.0) { // Linear, 1st order solution
    1256            0 :                     outletTemp = state.dataSurface->SurfOutDryBulbTemp(SurfNum) -
    1257            0 :                                  FRTAN * incidentAngleModifier * state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) / FRUL;
    1258              :                 } else { // Quadratic, 2nd order solution
    1259            0 :                     outletTemp = (-B + std::sqrt(qEquation)) / (2.0 * A);
    1260              :                 }
    1261              :             }
    1262              : 
    1263            0 :             if (state.dataSolarCollectors->Parameters(ParamNum).TestType == TestTypeEnum::INLET)
    1264            0 :                 break; // Inlet temperature test correlations do not need to iterate
    1265              : 
    1266            0 :             if (Iteration > 100) {
    1267            0 :                 if (this->IterErrIndex == 0) {
    1268            0 :                     ShowWarningMessage(state,
    1269            0 :                                        format("CalcSolarCollector: {}=\"{}\":  Solution did not converge.",
    1270            0 :                                               DataPlant::PlantEquipTypeNames[static_cast<int>(this->Type)],
    1271            0 :                                               this->Name));
    1272              :                 }
    1273            0 :                 ShowRecurringWarningErrorAtEnd(state,
    1274            0 :                                                format("CalcSolarCollector: {}=\"{}\", solution not converge error continues.",
    1275            0 :                                                       DataPlant::PlantEquipTypeNames[static_cast<int>(this->Type)],
    1276            0 :                                                       this->Name),
    1277            0 :                                                this->IterErrIndex);
    1278            0 :                 break;
    1279              :             } else {
    1280            0 :                 ++Iteration;
    1281              :             }
    1282              : 
    1283              :         } // Check for temperature convergence
    1284              : 
    1285            0 :         this->IncidentAngleModifier = incidentAngleModifier;
    1286            0 :         this->Power = Q;
    1287            0 :         this->HeatGain = max(Q, 0.0);
    1288            0 :         this->HeatLoss = min(Q, 0.0);
    1289            0 :         this->OutletTemp = outletTemp;
    1290            0 :         this->Efficiency = efficiency;
    1291            0 :     }
    1292              : 
    1293            0 :     Real64 ParametersData::IAM(EnergyPlusData &state, Real64 const IncidentAngle)
    1294              :     {
    1295              : 
    1296              :         // SUBROUTINE INFORMATION:
    1297              :         //       AUTHOR         Peter Graham Ellis
    1298              :         //       DATE WRITTEN   December 2003
    1299              :         //       MODIFIED       Sept 2008, BG cut off IAM beyond 60 degrees.
    1300              :         //       RE-ENGINEERED  na
    1301              : 
    1302              :         // PURPOSE OF THIS SUBROUTINE:
    1303              :         // Calculates the incident angle modifier based on the solar collector parameters.  Both first and second order
    1304              :         // correlations are allowed.
    1305              : 
    1306              :         // METHODOLOGY EMPLOYED:
    1307              :         // A simple function.
    1308              : 
    1309              :         // REFERENCES:
    1310              :         // ASHRAE Standard 93-1986 (RA 91), "Methods of Testing to Determine the Thermal Performance of Solar Collectors".
    1311              :         // ASHRAE Standard 96-1980 (RA 89), "Methods of Testing to Determine the Thermal Performance of Unglazed Flat-Plate
    1312              :         //   Liquid-Type Solar Collectors".
    1313              :         // Duffie, J. A., and Beckman, W. A.  Solar Engineering of Thermal Processes, Second Edition.  Wiley-Interscience:
    1314              :         //   New York (1991).
    1315              : 
    1316              :         Real64 IAM;
    1317              : 
    1318              :         // cut off IAM for angles greater than 60 degrees. (CR 7534)
    1319            0 :         Real64 CutoffAngle = 60.0 * Constant::DegToRad;
    1320            0 :         if (std::abs(IncidentAngle) > CutoffAngle) { // cut off, model curves not robust beyond cutoff
    1321              :             // curves from FSEC/SRCC testing are only certified to 60 degrees, larger angles can cause numerical problems in curves
    1322            0 :             IAM = 0.0;
    1323              :         } else {
    1324              : 
    1325            0 :             Real64 s = (1.0 / std::cos(IncidentAngle)) - 1.0;
    1326              : 
    1327            0 :             IAM = 1.0 + this->iam1 * s + this->iam2 * pow_2(s);
    1328            0 :             IAM = max(IAM, 0.0); // Never allow to be less than zero, but greater than one is a possibility
    1329              : 
    1330            0 :             if (IAM > 10.0) { // Greater than 10 is probably not a possibility
    1331            0 :                 ShowSevereError(
    1332              :                     state,
    1333            0 :                     format(
    1334              :                         "IAM Function: SolarCollectorPerformance:FlatPlate = {}:  Incident Angle Modifier is out of bounds due to bad coefficients.",
    1335            0 :                         this->Name));
    1336            0 :                 ShowContinueError(state, format("Coefficient 2 of Incident Angle Modifier = {}", this->iam1));
    1337            0 :                 ShowContinueError(state, format("Coefficient 3 of Incident Angle Modifier = {}", this->iam2));
    1338            0 :                 ShowContinueError(state, format("Calculated Incident Angle Modifier = {}", IAM));
    1339            0 :                 ShowContinueError(state, "Expected Incident Angle Modifier should be approximately 1.5 or less.");
    1340            0 :                 ShowFatalError(state, "Errors in SolarCollectorPerformance:FlatPlate input.");
    1341              :             }
    1342              : 
    1343              :         } // not greater than cut off angle
    1344              : 
    1345            0 :         return IAM;
    1346              :     }
    1347              : 
    1348            0 :     void CollectorData::CalcICSSolarCollector(EnergyPlusData &state)
    1349              :     {
    1350              : 
    1351              :         // SUBROUTINE INFORMATION:
    1352              :         //       AUTHOR         Bereket Nigusse, FSEC/UCF
    1353              :         //       DATE WRITTEN   February 2012
    1354              :         //       MODIFIED       na
    1355              :         //       RE-ENGINEERED  na
    1356              : 
    1357              :         // PURPOSE OF THIS SUBROUTINE:
    1358              :         // Calculates the heat transferred (gain or loss), energy stored, skin heat loss, outlet temperature, solar energy
    1359              :         // conversion efficiency, and transmittance-absorptance product of an ICS solar collector.
    1360              : 
    1361              :         // METHODOLOGY EMPLOYED:
    1362              :         // The governing equations for the absorber and collector water heat balance equations are solved simultaneously.
    1363              :         // The two coupled first ODE are solved analytically.
    1364              :         // The transmittance-absorptance product of the collector cover-absorber system is calculated using ray tracing
    1365              :         // method according to Duffie and Beckman(1991).
    1366              :         // REFERENCES:
    1367              :         // Duffie, J. A., and Beckman, W. A.  Solar Engineering of Thermal Processes, 2nd. Edition.  Wiley-Interscience:
    1368              :         // New York (1991).
    1369              :         // NOTES:
    1370              : 
    1371              :         static constexpr std::string_view RoutineName("CalcICSSolarCollector");
    1372              : 
    1373            0 :         int SurfNum = this->Surface;
    1374            0 :         int ParamNum = this->Parameters;
    1375            0 :         Real64 SecInTimeStep = state.dataHVACGlobal->TimeStepSysSec;
    1376            0 :         Real64 TempWater = this->SavedTempOfWater;
    1377            0 :         Real64 TempAbsPlate = this->SavedTempOfAbsPlate;
    1378            0 :         Real64 TempOutdoorAir = state.dataSurface->SurfOutDryBulbTemp(SurfNum);
    1379              : 
    1380              :         Real64 TempOSCM; // Otherside condition model temperature [C]
    1381            0 :         if (this->OSCM_ON) {
    1382            0 :             TempOSCM = this->SavedTempCollectorOSCM;
    1383              :         } else {
    1384            0 :             TempOSCM = TempOutdoorAir;
    1385              :         }
    1386              : 
    1387              :         // Calculate transmittance-absorptance product of the system
    1388              :         // Incident angle of beam radiation (radians)
    1389            0 :         Real64 ThetaBeam = std::acos(state.dataHeatBal->SurfCosIncidenceAngle(SurfNum));
    1390            0 :         this->CalcTransAbsorProduct(state, ThetaBeam);
    1391              : 
    1392            0 :         Real64 inletTemp = this->InletTemp;
    1393              : 
    1394            0 :         Real64 massFlowRate = this->MassFlowRate;
    1395              : 
    1396              :         // Specific heat of collector fluid (J/kg-K)
    1397            0 :         Real64 Cpw = this->plantLoc.loop->glycol->getSpecificHeat(state, inletTemp, RoutineName);
    1398              : 
    1399              :         // density of collector fluid (kg/m3)
    1400            0 :         Real64 Rhow = this->plantLoc.loop->glycol->getDensity(state, inletTemp, RoutineName);
    1401              : 
    1402              :         // calculate heat transfer coefficients and covers temperature:
    1403            0 :         this->CalcHeatTransCoeffAndCoverTemp(state);
    1404              : 
    1405              :         // Calc convection heat transfer coefficient between the absorber plate and water:
    1406              : 
    1407              :         // convection coeff between absorber plate and water [W/m2K]
    1408              :         Real64 hConvCoefA2W =
    1409            0 :             EnergyPlus::SolarCollectors::CollectorData::CalcConvCoeffAbsPlateAndWater(state, TempAbsPlate, TempWater, this->Length, this->TiltR2V);
    1410            0 :         Real64 TempWaterOld = TempWater;
    1411            0 :         Real64 TempAbsPlateOld = TempAbsPlate;
    1412              : 
    1413              :         // flag if the absorber has thermal mass or not
    1414              :         bool AbsPlateMassFlag;
    1415              : 
    1416              :         Real64 a1; // coefficient of ODE for absorber temperature Tp
    1417              :         Real64 a2; // coefficient of ODE for absorber temperature Tw
    1418              :         Real64 a3; // constant term of ODE for absorber temperature
    1419              : 
    1420              :         // Gross area of collector (m2)
    1421            0 :         Real64 area = state.dataSolarCollectors->Parameters(ParamNum).Area;
    1422              : 
    1423            0 :         if (state.dataSolarCollectors->Parameters(ParamNum).ThermalMass > 0.0) {
    1424            0 :             AbsPlateMassFlag = true;
    1425              : 
    1426              :             // thermal mass of the absorber plate [J/K]
    1427            0 :             Real64 ap = state.dataSolarCollectors->Parameters(ParamNum).ThermalMass * area;
    1428            0 :             a1 = -area * (hConvCoefA2W + this->UTopLoss) / ap;
    1429            0 :             a2 = area * hConvCoefA2W / ap;
    1430            0 :             a3 = area * (this->TauAlpha * state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) + this->UTopLoss * TempOutdoorAir) / ap;
    1431              :         } else {
    1432            0 :             AbsPlateMassFlag = false;
    1433            0 :             a1 = -area * (hConvCoefA2W + this->UTopLoss);
    1434            0 :             a2 = area * hConvCoefA2W;
    1435            0 :             a3 = area * (this->TauAlpha * state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) + this->UTopLoss * TempOutdoorAir);
    1436              :         }
    1437              : 
    1438              :         // thermal mass of the collector water [J/K]
    1439            0 :         Real64 aw = state.dataSolarCollectors->Parameters(ParamNum).Volume * Rhow * Cpw;
    1440              : 
    1441              :         // coefficient of ODE for water temperature Tp
    1442            0 :         Real64 b1 = area * hConvCoefA2W / aw;
    1443              : 
    1444              :         // coefficient of ODE for water temperature Tw
    1445            0 :         Real64 b2 = -(area * (hConvCoefA2W + this->UbLoss + this->UsLoss) + massFlowRate * Cpw) / aw;
    1446              : 
    1447              :         // constant term of ODE for water temperature
    1448            0 :         Real64 b3 = (area * (this->UbLoss * TempOSCM + this->UsLoss * TempOutdoorAir) + massFlowRate * Cpw * inletTemp) / aw;
    1449              : 
    1450            0 :         EnergyPlus::SolarCollectors::CollectorData::ICSCollectorAnalyticalSolution(
    1451              :             state, SecInTimeStep, a1, a2, a3, b1, b2, b3, TempAbsPlateOld, TempWaterOld, TempAbsPlate, TempWater, AbsPlateMassFlag);
    1452              : 
    1453            0 :         this->SkinHeatLossRate = area * (this->UTopLoss * (TempOutdoorAir - TempAbsPlate) + this->UsLoss * (TempOutdoorAir - TempWater) +
    1454            0 :                                          this->UbLoss * (TempOSCM - TempWater));
    1455            0 :         this->StoredHeatRate = aw * (TempWater - TempWaterOld) / SecInTimeStep;
    1456              : 
    1457              :         // heat gain rate (W)
    1458            0 :         Real64 QHeatRate = massFlowRate * Cpw * (TempWater - inletTemp);
    1459            0 :         this->HeatRate = QHeatRate;
    1460            0 :         this->HeatGainRate = max(0.0, QHeatRate);
    1461              : 
    1462            0 :         Real64 outletTemp = TempWater;
    1463            0 :         this->OutletTemp = outletTemp;
    1464            0 :         this->TempOfWater = TempWater;
    1465            0 :         this->TempOfAbsPlate = TempAbsPlate;
    1466              : 
    1467            0 :         Real64 efficiency = 0.0; // Thermal efficiency of solar energy conversion
    1468            0 :         if (state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) > 0.0) {
    1469            0 :             efficiency = (this->HeatGainRate + this->StoredHeatRate) / (state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) * area);
    1470            0 :             if (efficiency < 0.0) efficiency = 0.0;
    1471              :         }
    1472            0 :         this->Efficiency = efficiency;
    1473            0 :     }
    1474              : 
    1475            0 :     void CollectorData::ICSCollectorAnalyticalSolution(EnergyPlusData &state,
    1476              :                                                        Real64 const SecInTimeStep,     // seconds in a time step
    1477              :                                                        Real64 const a1,                // coefficient of ODE for Tp
    1478              :                                                        Real64 const a2,                // coefficient of ODE for Tp
    1479              :                                                        Real64 const a3,                // coefficient of ODE for Tp
    1480              :                                                        Real64 const b1,                // coefficient of ODE for TW
    1481              :                                                        Real64 const b2,                // coefficient of ODE for TW
    1482              :                                                        Real64 const b3,                // coefficient of ODE for TW
    1483              :                                                        Real64 const TempAbsPlateOld,   // absorber plate temperature at previous time step [C]
    1484              :                                                        Real64 const TempWaterOld,      // collector water temperature at previous time step [C]
    1485              :                                                        Real64 &TempAbsPlate,           // absorber plate temperature at current time step [C]
    1486              :                                                        Real64 &TempWater,              // collector water temperature at current time step [C]
    1487              :                                                        bool const AbsorberPlateHasMass // flag for absorber thermal mass
    1488              :     )
    1489              :     {
    1490              : 
    1491              :         // SUBROUTINE INFORMATION:
    1492              :         //       AUTHOR         Bereket Nigusse, FSEC/UCF
    1493              :         //       DATE WRITTEN   February 2012
    1494              :         //       MODIFIED       na
    1495              :         //       RE-ENGINEERED  na
    1496              : 
    1497              :         // PURPOSE OF THIS SUBROUTINE:
    1498              :         // Calculates the absorber plate and collector water temperatures.
    1499              :         // METHODOLOGY EMPLOYED:
    1500              :         // Analytical method: Solves the coupled absorber plate and collector water energy balance
    1501              :         // equations.  The two non-homogeneous ordinary differential equations of the form.
    1502              :         //          Tp' = a1*Tp + a2*Tw + a3.
    1503              :         //          Tw' = b1*Tp + b2*Tw + b3.
    1504              :         // The general solution of these coupled equation with real routes has the following form:
    1505              :         //          Tp = ConstantC1*exp(lamda1*t) + ConstantC2*exp(lamda2*t) + ConstOfTpSln
    1506              :         //          Tw = r1*ConstantC2*exp(lamda1*t) + r2*ConstantC2*exp(lamda2*t) + ConstOfTwSln
    1507              : 
    1508            0 :         if (AbsorberPlateHasMass) {
    1509              : 
    1510              :             // coefficients of quadratic equation a*m2+b*m+c=0
    1511            0 :             Real64 a = 1.0;
    1512            0 :             Real64 b = -(a1 + b2);
    1513            0 :             Real64 c = a1 * b2 - a2 * b1;
    1514            0 :             Real64 BSquareM4TimesATimesC = pow_2(b) - 4.0 * a * c;
    1515              : 
    1516            0 :             if (BSquareM4TimesATimesC > 0.0) {
    1517              : 
    1518              :                 // the real roots of the quadratic equation
    1519            0 :                 Real64 lamda1 = (-b + std::sqrt(BSquareM4TimesATimesC)) / (2.0 * a);
    1520            0 :                 Real64 lamda2 = (-b - std::sqrt(BSquareM4TimesATimesC)) / (2.0 * a);
    1521              : 
    1522              :                 // the particular solution for the ODE
    1523            0 :                 Real64 ConstOfTpSln = (-a3 * b2 + b3 * a2) / c;
    1524            0 :                 Real64 ConstOfTwSln = (-a1 * b3 + b1 * a3) / c;
    1525              : 
    1526              :                 // ratio of the ODE solution constant coefficients
    1527            0 :                 Real64 r1 = (lamda1 - a1) / a2;
    1528            0 :                 Real64 r2 = (lamda2 - a1) / a2;
    1529              : 
    1530              :                 // coefficients of the ODE solution
    1531            0 :                 Real64 ConstantC2 = (TempWaterOld + r1 * ConstOfTpSln - r1 * TempAbsPlateOld - ConstOfTwSln) / (r2 - r1);
    1532            0 :                 Real64 ConstantC1 = (TempAbsPlateOld - ConstOfTpSln - ConstantC2);
    1533              : 
    1534            0 :                 TempAbsPlate = ConstantC1 * std::exp(lamda1 * SecInTimeStep) + ConstantC2 * std::exp(lamda2 * SecInTimeStep) + ConstOfTpSln;
    1535            0 :                 TempWater = r1 * ConstantC1 * std::exp(lamda1 * SecInTimeStep) + r2 * ConstantC2 * std::exp(lamda2 * SecInTimeStep) + ConstOfTwSln;
    1536              : 
    1537              :             } else { // this should never occur
    1538            0 :                 ShowSevereError(
    1539              :                     state, "ICSCollectorAnalyticalSoluton: Unanticipated differential equation coefficient - report to EnergyPlus Development Team");
    1540            0 :                 ShowFatalError(state, "Program terminates due to above conditions.");
    1541              :             }
    1542              :         } else {
    1543              :             // In the absence of absorber plate thermal mass, only the collector water heat balance has a
    1544              :             // differential equation of the form: Tw' = b1*Tp + b2*Tw + b3. The absorber plate energy balance
    1545              :             // equation in the absence of thermal mass is a steady state form:  b1*Tp + b2*Tw + b3 = 0
    1546            0 :             Real64 b = b2 - b1 * (a2 / a1);
    1547            0 :             Real64 c = b3 - b1 * (a3 / a1);
    1548            0 :             TempWater = (TempWaterOld + c / b) * std::exp(b * SecInTimeStep) - c / b;
    1549            0 :             TempAbsPlate = -(a2 * TempWater + a3) / a1;
    1550              :         }
    1551            0 :     }
    1552              : 
    1553            0 :     void CollectorData::CalcTransAbsorProduct(EnergyPlusData &state, Real64 const IncidAngle)
    1554              :     {
    1555              : 
    1556              :         // SUBROUTINE INFORMATION:
    1557              :         //       AUTHOR         Bereket A Nigusse
    1558              :         //       DATE WRITTEN   February 2012
    1559              :         //       MODIFIED       na
    1560              :         //       RE-ENGINEERED  na
    1561              : 
    1562              :         // PURPOSE OF THIS SUBROUTINE:
    1563              :         // Calculates transmittance-absorptance product and the fraction of total solar radiation
    1564              :         // absorbed by each cover of a multicover ICS solar collector.
    1565              : 
    1566              :         // METHODOLOGY EMPLOYED:
    1567              :         // Uses a ray tracing method.
    1568              : 
    1569              :         // REFERENCES:
    1570              :         // Duffie, J. A., and Beckman, W. A.  Solar Engineering of Thermal Processes, Second Edition.
    1571              :         // Wiley-Interscience: New York (1991).
    1572              : 
    1573            0 :         Real64 TransSys = 1.0;  // cover system solar transmittance
    1574            0 :         Real64 ReflSys = 0.0;   // cover system solar reflectance
    1575            0 :         Real64 AbsCover1 = 0.0; // Inner cover solar absorbtance
    1576            0 :         Real64 AbsCover2 = 0.0; // Outer cover solar absorbtance
    1577              :         Real64 TuaAlpha;        // weighted trans-abs product of system
    1578              :         Real64 TuaAlphaBeam;    // trans-abs product of beam radiation
    1579            0 :         this->CoverAbs[0] = 0.0;
    1580            0 :         this->CoverAbs[1] = 0.0;
    1581              : 
    1582            0 :         int SurfNum = this->Surface;
    1583            0 :         int ParamNum = this->Parameters;
    1584              : 
    1585            0 :         if (state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) > 0.0) {
    1586              : 
    1587              :             // cover system transmittance and reflectance from outer to inner cover
    1588            0 :             this->CalcTransRefAbsOfCover(state, IncidAngle, TransSys, ReflSys, AbsCover1, AbsCover2);
    1589              : 
    1590            0 :             TuaAlphaBeam = TransSys * state.dataSolarCollectors->Parameters(ParamNum).AbsorOfAbsPlate /
    1591            0 :                            (1.0 - (1.0 - state.dataSolarCollectors->Parameters(ParamNum).AbsorOfAbsPlate) * this->RefDiffInnerCover);
    1592              : 
    1593            0 :             this->TauAlphaBeam = max(0.0, TuaAlphaBeam);
    1594              : 
    1595            0 :             Array1D<Real64> CoversAbsBeam(2); // Inner and Outer Cover absorptance
    1596            0 :             CoversAbsBeam(1) = AbsCover1;
    1597            0 :             CoversAbsBeam(2) = AbsCover2;
    1598              : 
    1599              :             // calc total solar radiation weighted transmittance-absorptance product
    1600            0 :             TuaAlpha = (state.dataHeatBal->SurfQRadSWOutIncidentBeam(SurfNum) * this->TauAlphaBeam +
    1601            0 :                         state.dataHeatBal->SurfQRadSWOutIncidentSkyDiffuse(SurfNum) * this->TauAlphaSkyDiffuse +
    1602            0 :                         state.dataHeatBal->SurfQRadSWOutIncidentGndDiffuse(SurfNum) * this->TauAlphaGndDiffuse) /
    1603            0 :                        state.dataHeatBal->SurfQRadSWOutIncident(SurfNum);
    1604              : 
    1605            0 :             if (state.dataSolarCollectors->Parameters(ParamNum).NumOfCovers == 1) {
    1606              :                 // calc total solar radiation weighted cover absorptance
    1607            0 :                 this->CoverAbs[0] = (state.dataHeatBal->SurfQRadSWOutIncidentBeam(SurfNum) * CoversAbsBeam(1) +
    1608            0 :                                      state.dataHeatBal->SurfQRadSWOutIncidentSkyDiffuse(SurfNum) * this->CoversAbsSkyDiffuse[0] +
    1609            0 :                                      state.dataHeatBal->SurfQRadSWOutIncidentGndDiffuse(SurfNum) * this->CoversAbsGndDiffuse[0]) /
    1610            0 :                                     state.dataHeatBal->SurfQRadSWOutIncident(SurfNum);
    1611              : 
    1612            0 :             } else if (state.dataSolarCollectors->Parameters(ParamNum).NumOfCovers == 2) {
    1613              :                 // Num = 1 represents outer cover and Num = 2 represents inner cover
    1614            0 :                 for (int Num = 0; Num < state.dataSolarCollectors->Parameters(ParamNum).NumOfCovers; ++Num) {
    1615            0 :                     this->CoverAbs[Num] = (state.dataHeatBal->SurfQRadSWOutIncidentBeam(SurfNum) * CoversAbsBeam(Num + 1) +
    1616            0 :                                            state.dataHeatBal->SurfQRadSWOutIncidentSkyDiffuse(SurfNum) * this->CoversAbsSkyDiffuse[Num] +
    1617            0 :                                            state.dataHeatBal->SurfQRadSWOutIncidentGndDiffuse(SurfNum) * this->CoversAbsGndDiffuse[Num]) /
    1618            0 :                                           state.dataHeatBal->SurfQRadSWOutIncident(SurfNum);
    1619              :                 }
    1620              :             }
    1621              : 
    1622            0 :         } else {
    1623            0 :             TuaAlpha = 0.0;
    1624              :         }
    1625            0 :         this->TauAlpha = TuaAlpha;
    1626            0 :     }
    1627              : 
    1628            0 :     void CollectorData::CalcTransRefAbsOfCover(EnergyPlusData &state,
    1629              :                                                Real64 const IncidentAngle,               // Angle of incidence (radians)
    1630              :                                                Real64 &TransSys,                         // cover system solar transmittance
    1631              :                                                Real64 &ReflSys,                          // cover system solar reflectance
    1632              :                                                Real64 &AbsCover1,                        // Inner cover solar absorbtance
    1633              :                                                Real64 &AbsCover2,                        // Outer cover solar absorbtance
    1634              :                                                ObjexxFCL::Optional_bool_const InOUTFlag, // flag for calc. diffuse solar refl of cover from inside out
    1635              :                                                ObjexxFCL::Optional<Real64> RefSysDiffuse // cover system solar reflectance from inner to outer cover
    1636              :     ) const
    1637              :     {
    1638              : 
    1639              :         // SUBROUTINE INFORMATION:
    1640              :         //       AUTHOR         Bereket A Nigusse
    1641              :         //       DATE WRITTEN   February 2012
    1642              : 
    1643              :         // PURPOSE OF THIS SUBROUTINE:
    1644              :         // Calculates the transmittance, reflectance, and absorptance of the collector covers based on
    1645              :         // solar collector optical parameters specified.
    1646              : 
    1647              :         // METHODOLOGY EMPLOYED:
    1648              :         // Uses a ray tracing method.
    1649              : 
    1650              :         // REFERENCES:
    1651              :         // Duffie, J. A., and Beckman, W. A.  Solar Engineering of Thermal Processes, Second Edition.
    1652              :         // Wiley-Interscience: New York (1991).
    1653              : 
    1654            0 :         Real64 constexpr AirRefIndex(1.0003); // refractive index of air
    1655              : 
    1656            0 :         Array1D<Real64> TransPara(2);    // cover transmittance parallel component
    1657            0 :         Array1D<Real64> TransPerp(2);    // cover transmittance perpendicular component
    1658            0 :         Array1D<Real64> ReflPara(2);     // cover reflectance parallel component
    1659            0 :         Array1D<Real64> ReflPerp(2);     // cover reflectance Perpendicular component
    1660            0 :         Array1D<Real64> AbsorPara(2);    // cover absorbtance parallel component
    1661            0 :         Array1D<Real64> AbsorPerp(2);    // cover absorbtance Perpendicular component
    1662            0 :         Array1D<Real64> TransAbsOnly(2); // cover transmittance with absorptance only considered
    1663              : 
    1664            0 :         TransPerp = 1.0;
    1665            0 :         TransPara = 1.0;
    1666            0 :         ReflPerp = 0.0;
    1667            0 :         ReflPara = 0.0;
    1668            0 :         AbsorPerp = 0.0;
    1669            0 :         AbsorPara = 0.0;
    1670            0 :         TransAbsOnly = 1.0;
    1671            0 :         TransSys = 0.0;
    1672            0 :         ReflSys = 0.0;
    1673            0 :         AbsCover1 = 0.0;
    1674            0 :         AbsCover2 = 0.0;
    1675              : 
    1676              :         bool DiffRefFlag; // flag for calc. diffuse refl of cover from inside to outside
    1677            0 :         if (present(InOUTFlag)) {
    1678            0 :             DiffRefFlag = InOUTFlag;
    1679              :         } else {
    1680            0 :             DiffRefFlag = false;
    1681              :         }
    1682              : 
    1683              :         // get the incidence and refraction angles
    1684            0 :         int ParamNum = this->Parameters;
    1685            0 :         Real64 const sin_IncAngle(std::sin(IncidentAngle));
    1686              : 
    1687            0 :         for (int nCover = 1; nCover <= state.dataSolarCollectors->Parameters(ParamNum).NumOfCovers; ++nCover) {
    1688              : 
    1689              :             // refractive index of collector cover
    1690            0 :             Real64 CoverRefrIndex = state.dataSolarCollectors->Parameters(ParamNum).RefractiveIndex[nCover - 1];
    1691              : 
    1692              :             // angle of refraction
    1693            0 :             Real64 RefrAngle = std::asin(sin_IncAngle * AirRefIndex / CoverRefrIndex);
    1694              : 
    1695              :             // transmitted component with absorption only considered:
    1696            0 :             TransAbsOnly(nCover) = std::exp(-state.dataSolarCollectors->Parameters(ParamNum).ExtCoefTimesThickness[nCover - 1] / std::cos(RefrAngle));
    1697              : 
    1698              :             // parallel reflected component of unpolarized solar radiation
    1699              :             Real64 ParaRad;
    1700              : 
    1701              :             // Perpendicular reflected component of unpolarized solar radiation
    1702              :             Real64 PerpRad;
    1703              : 
    1704              :             // parallel and perpendicular reflection components:
    1705            0 :             if (IncidentAngle == 0.0) {
    1706            0 :                 ParaRad = pow_2((CoverRefrIndex - AirRefIndex) / (CoverRefrIndex + AirRefIndex));
    1707            0 :                 PerpRad = pow_2((CoverRefrIndex - AirRefIndex) / (CoverRefrIndex + AirRefIndex));
    1708              :             } else {
    1709            0 :                 ParaRad = pow_2(std::tan(RefrAngle - IncidentAngle)) / pow_2(std::tan(RefrAngle + IncidentAngle));
    1710            0 :                 PerpRad = pow_2(std::sin(RefrAngle - IncidentAngle)) / pow_2(std::sin(RefrAngle + IncidentAngle));
    1711              :             }
    1712              : 
    1713              :             // parallel and perpendicular transmitted components:
    1714            0 :             TransPerp(nCover) =
    1715            0 :                 TransAbsOnly(nCover) * ((1.0 - PerpRad) / (1.0 + PerpRad)) * ((1.0 - pow_2(PerpRad)) / (1.0 - pow_2(PerpRad * TransAbsOnly(nCover))));
    1716            0 :             TransPara(nCover) =
    1717            0 :                 TransAbsOnly(nCover) * ((1.0 - ParaRad) / (1.0 + ParaRad)) * ((1.0 - pow_2(ParaRad)) / (1.0 - pow_2(ParaRad * TransAbsOnly(nCover))));
    1718              : 
    1719            0 :             ReflPerp(nCover) =
    1720            0 :                 (PerpRad + (pow_2(1.0 - PerpRad) * pow_2(TransAbsOnly(nCover)) * PerpRad) / (1.0 - pow_2(PerpRad * TransAbsOnly(nCover))));
    1721            0 :             ReflPara(nCover) =
    1722            0 :                 (ParaRad + (pow_2(1.0 - ParaRad) * pow_2(TransAbsOnly(nCover)) * ParaRad) / (1.0 - pow_2(ParaRad * TransAbsOnly(nCover))));
    1723              : 
    1724            0 :             AbsorPerp(nCover) = 1.0 - TransPerp(nCover) - ReflPerp(nCover);
    1725            0 :             AbsorPara(nCover) = 1.0 - TransPara(nCover) - ReflPara(nCover);
    1726              :         }
    1727              : 
    1728              :         // solar absorptance of the individual cover
    1729            0 :         AbsCover1 = 0.5 * (AbsorPerp(1) + AbsorPara(1));
    1730            0 :         if (state.dataSolarCollectors->Parameters(ParamNum).NumOfCovers == 2) AbsCover2 = 0.5 * (AbsorPerp(2) + AbsorPara(2));
    1731              : 
    1732              :         // calculate from outer to inner cover:
    1733            0 :         TransSys =
    1734            0 :             0.5 * (TransPerp(1) * TransPerp(2) / (1.0 - ReflPerp(1) * ReflPerp(2)) + TransPara(1) * TransPara(2) / (1.0 - ReflPara(1) * ReflPara(2)));
    1735            0 :         ReflSys = 0.5 * (ReflPerp(1) + TransSys * ReflPerp(2) * TransPerp(1) / TransPerp(2) + ReflPara(1) +
    1736            0 :                          TransSys * ReflPara(2) * TransPara(1) / TransPara(2));
    1737            0 :         if (DiffRefFlag) {
    1738              :             // calculate from inner to outer cover:
    1739              : 
    1740              :             // cover system solar transmittance from inner to outer cover
    1741            0 :             Real64 TransSysDiff = 0.5 * (TransPerp(2) * TransPerp(1) / (1.0 - ReflPerp(2) * ReflPerp(1)) +
    1742            0 :                                          TransPara(2) * TransPara(1) / (1.0 - ReflPara(2) * ReflPara(1)));
    1743            0 :             RefSysDiffuse = 0.5 * (ReflPerp(2) + TransSysDiff * ReflPerp(1) * TransPerp(2) / TransPerp(1) + ReflPara(2) +
    1744            0 :                                    TransSysDiff * ReflPara(1) * TransPara(2) / TransPara(1));
    1745              :         }
    1746            0 :     }
    1747              : 
    1748            0 :     void CollectorData::CalcHeatTransCoeffAndCoverTemp(EnergyPlusData &state) // Collector object number
    1749              :     {
    1750              :         // SUBROUTINE INFORMATION:
    1751              :         //       AUTHOR         Bereket A Nigusse, FSEC/UCF
    1752              :         //       DATE WRITTEN   February 2012
    1753              : 
    1754              :         // PURPOSE OF THIS SUBROUTINE:
    1755              :         // Calculates the various heat transfer coefficients, and collector cover temperatures.
    1756              : 
    1757              :         // METHODOLOGY EMPLOYED:
    1758              : 
    1759              :         // REFERENCES:
    1760              :         // Duffie, J. A., and Beckman, W. A.  Solar Engineering of Thermal Processes, Second Edition.
    1761              :         // Wiley-Interscience: New York (1991).
    1762              : 
    1763              :         Real64 tempnom;             // intermediate variable
    1764              :         Real64 tempdenom;           // intermediate variable
    1765              :         Real64 hRadCoefC2Sky;       // radiation coeff from collector to the sky [W/m2C]
    1766            0 :         Real64 hRadCoefC2Gnd = 0.0; // radiation coeff from collector to the ground [W/m2C]
    1767            0 :         Real64 hConvCoefA2C = 0.0;  // convection coeff. between abs plate and cover [W/m2C]
    1768            0 :         Real64 hConvCoefC2C = 0.0;  // convection coeff. between covers [W/m2C]
    1769            0 :         Real64 hConvCoefC2O = 0.0;  // convection coeff. between outer cover and the ambient [W/m2C]
    1770            0 :         Real64 hRadCoefA2C = 0.0;   // radiation coeff. between abs plate and cover [W/m2C]
    1771            0 :         Real64 hRadCoefC2C = 0.0;   // radiation coeff. between covers [W/m2C]
    1772            0 :         Real64 hRadCoefC2O = 0.0;   // radiation coeff. between outer covers and the ambient [W/m2C]
    1773              : 
    1774            0 :         int ParamNum = this->Parameters;
    1775            0 :         int NumCovers = state.dataSolarCollectors->Parameters(ParamNum).NumOfCovers;
    1776            0 :         int SurfNum = this->Surface;
    1777              : 
    1778            0 :         Real64 TempAbsPlate = this->SavedTempOfAbsPlate;                        // absorber plate average temperature [C]
    1779            0 :         Real64 TempInnerCover = this->SavedTempOfInnerCover;                    // inner cover average temperature [C]
    1780            0 :         Real64 TempOuterCover = this->SavedTempOfOuterCover;                    // outer cover average temperature [C]
    1781            0 :         Real64 TempOutdoorAir = state.dataSurface->SurfOutDryBulbTemp(SurfNum); // outdoor air temperature [C]
    1782              : 
    1783            0 :         Real64 EmissOfAbsPlate = state.dataSolarCollectors->Parameters(ParamNum).EmissOfAbsPlate;   // emissivity of absorber plate
    1784            0 :         Real64 EmissOfOuterCover = state.dataSolarCollectors->Parameters(ParamNum).EmissOfCover[0]; // emissivity of outer cover
    1785            0 :         Real64 EmissOfInnerCover = state.dataSolarCollectors->Parameters(ParamNum).EmissOfCover[1]; // emissivity of inner cover
    1786            0 :         Real64 AirGapDepth = state.dataSolarCollectors->Parameters(ParamNum).CoverSpacing;          // characteristic length [m]
    1787              : 
    1788            0 :         if (NumCovers == 1) {
    1789              :             // calc linearized radiation coefficient
    1790            0 :             tempnom = Constant::StefanBoltzmann * ((TempAbsPlate + Constant::Kelvin) + (TempOuterCover + Constant::Kelvin)) *
    1791            0 :                       (pow_2(TempAbsPlate + Constant::Kelvin) + pow_2(TempOuterCover + Constant::Kelvin));
    1792            0 :             tempdenom = 1.0 / EmissOfAbsPlate + 1.0 / EmissOfOuterCover - 1.0;
    1793            0 :             hRadCoefA2C = tempnom / tempdenom;
    1794            0 :             hRadCoefC2C = 0.0;
    1795            0 :             hConvCoefC2C = 0.0;
    1796              :             // Calc convection heat transfer coefficient:
    1797            0 :             hConvCoefA2C = EnergyPlus::SolarCollectors::CollectorData::CalcConvCoeffBetweenPlates(
    1798              :                 TempAbsPlate, TempOuterCover, AirGapDepth, this->CosTilt, this->SinTilt);
    1799            0 :         } else if (NumCovers == 2) {
    1800            0 :             for (int CoverNum = 1; CoverNum <= NumCovers; ++CoverNum) {
    1801            0 :                 if (CoverNum == 1) {
    1802              :                     // calc linearized radiation coefficient
    1803            0 :                     tempnom = Constant::StefanBoltzmann * ((TempAbsPlate + Constant::Kelvin) + (TempInnerCover + Constant::Kelvin)) *
    1804            0 :                               (pow_2(TempAbsPlate + Constant::Kelvin) + pow_2(TempInnerCover + Constant::Kelvin));
    1805            0 :                     tempdenom = 1.0 / EmissOfAbsPlate + 1.0 / EmissOfInnerCover - 1.0;
    1806            0 :                     hRadCoefA2C = tempnom / tempdenom;
    1807              :                     // Calc convection heat transfer coefficient:
    1808            0 :                     hConvCoefA2C = EnergyPlus::SolarCollectors::CollectorData::CalcConvCoeffBetweenPlates(
    1809              :                         TempAbsPlate, TempOuterCover, AirGapDepth, this->CosTilt, this->SinTilt);
    1810              :                 } else {
    1811              :                     // calculate the linearized radiation coeff.
    1812            0 :                     tempnom = Constant::StefanBoltzmann * ((TempInnerCover + Constant::Kelvin) + (TempOuterCover + Constant::Kelvin)) *
    1813            0 :                               (pow_2(TempInnerCover + Constant::Kelvin) + pow_2(TempOuterCover + Constant::Kelvin));
    1814            0 :                     tempdenom = 1.0 / EmissOfInnerCover + 1.0 / EmissOfOuterCover - 1.0;
    1815            0 :                     hRadCoefC2C = tempnom / tempdenom;
    1816              :                     // Calc convection heat transfer coefficient:
    1817            0 :                     hConvCoefC2C = EnergyPlus::SolarCollectors::CollectorData::CalcConvCoeffBetweenPlates(
    1818              :                         TempInnerCover, TempOuterCover, AirGapDepth, this->CosTilt, this->SinTilt);
    1819              :                 }
    1820              :             }
    1821              :         }
    1822              : 
    1823              :         // Calc collector outside surface convection heat transfer coefficient:
    1824            0 :         hConvCoefC2O = 2.8 + 3.0 * state.dataSurface->SurfOutWindSpeed(SurfNum);
    1825              : 
    1826              :         // Calc linearized radiation coefficient between outer cover and the surrounding:
    1827            0 :         tempnom = state.dataSurface->Surface(SurfNum).ViewFactorSky * EmissOfOuterCover * Constant::StefanBoltzmann *
    1828            0 :                   ((TempOuterCover + Constant::Kelvin) + state.dataEnvrn->SkyTempKelvin) *
    1829            0 :                   (pow_2(TempOuterCover + Constant::Kelvin) + pow_2(state.dataEnvrn->SkyTempKelvin));
    1830            0 :         tempdenom = (TempOuterCover - TempOutdoorAir) / (TempOuterCover - state.dataEnvrn->SkyTemp);
    1831            0 :         if (tempdenom < 0.0) {
    1832              :             // use approximate linearized radiation coefficient
    1833            0 :             hRadCoefC2Sky = tempnom;
    1834            0 :         } else if (tempdenom == 0.0) {
    1835              :             // if temperature difference is zero, no radiation exchange
    1836            0 :             hRadCoefC2Sky = 0.0;
    1837              :         } else {
    1838            0 :             hRadCoefC2Sky = tempnom / tempdenom;
    1839              :         }
    1840              : 
    1841            0 :         tempnom = state.dataSurface->Surface(SurfNum).ViewFactorGround * EmissOfOuterCover * Constant::StefanBoltzmann *
    1842            0 :                   ((TempOuterCover + Constant::Kelvin) + state.dataEnvrn->GroundTempKelvin) *
    1843            0 :                   (pow_2(TempOuterCover + Constant::Kelvin) + pow_2(state.dataEnvrn->GroundTempKelvin));
    1844            0 :         tempdenom =
    1845            0 :             (TempOuterCover - TempOutdoorAir) / (TempOuterCover - state.dataEnvrn->GroundTemp[(int)DataEnvironment::GroundTempType::BuildingSurface]);
    1846            0 :         if (tempdenom < 0.0) {
    1847              :             // use approximate linearized radiation coefficient
    1848            0 :             hRadCoefC2Gnd = tempnom;
    1849            0 :         } else if (tempdenom == 0.0) {
    1850              :             // if temperature difference is zero, no radiation exchange
    1851            0 :             hRadCoefC2Gnd = 0.0;
    1852              :         } else {
    1853            0 :             hRadCoefC2Gnd = tempnom / tempdenom;
    1854              :         }
    1855              : 
    1856              :         // combine the radiation coefficients
    1857            0 :         hRadCoefC2O = hRadCoefC2Sky + hRadCoefC2Gnd;
    1858              : 
    1859              :         // calculate the overall top heat loss coefficient:
    1860              : 
    1861            0 :         if (NumCovers == 1) {
    1862            0 :             this->UTopLoss = 1.0 / (1.0 / (hRadCoefA2C + hConvCoefA2C) + 1.0 / (hRadCoefC2O + hConvCoefC2O));
    1863              :         } else {
    1864            0 :             this->UTopLoss = 1.0 / (1.0 / (hRadCoefA2C + hConvCoefA2C) + 1.0 / (hRadCoefC2C + hConvCoefC2C) + 1.0 / (hRadCoefC2O + hConvCoefC2O));
    1865              :         }
    1866              : 
    1867              :         // calculate the side loss coefficient.  Adds the insulation resistance and the combined
    1868              :         // convection-radiation coefficients in series.
    1869            0 :         Real64 hRadConvOut = 5.7 + 3.8 * state.dataSurface->SurfOutWindSpeed(SurfNum);
    1870            0 :         this->UsLoss =
    1871            0 :             1.0 / (1.0 / (state.dataSolarCollectors->Parameters(ParamNum).ULossSide * this->AreaRatio) + 1.0 / (hRadConvOut * this->AreaRatio));
    1872              : 
    1873              :         // the bottom loss coefficient calculation depends on the boundary condition
    1874            0 :         if (this->OSCM_ON) { // OtherSideConditionsModel
    1875            0 :             this->UbLoss = state.dataSolarCollectors->Parameters(ParamNum).ULossBottom;
    1876              :         } else { // AmbientAir
    1877            0 :             this->UbLoss = 1.0 / (1.0 / state.dataSolarCollectors->Parameters(ParamNum).ULossBottom + 1.0 / hRadConvOut);
    1878              :         }
    1879              : 
    1880              :         // Calculate current timestep covers temperature
    1881            0 :         if (NumCovers == 1) {
    1882            0 :             tempnom = this->CoverAbs[0] * state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) + TempOutdoorAir * (hConvCoefC2O + hRadCoefC2O) +
    1883            0 :                       TempAbsPlate * (hConvCoefA2C + hRadCoefA2C);
    1884            0 :             tempdenom = (hConvCoefC2O + hRadCoefC2O) + (hConvCoefA2C + hRadCoefA2C);
    1885            0 :             TempOuterCover = tempnom / tempdenom;
    1886            0 :         } else if (NumCovers == 2) {
    1887            0 :             for (int Num = 0; Num < NumCovers; ++Num) {
    1888            0 :                 if (Num == 0) {
    1889            0 :                     tempnom = this->CoverAbs[Num] * state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) +
    1890            0 :                               TempOutdoorAir * (hConvCoefC2O + hRadCoefC2O) + TempInnerCover * (hConvCoefC2C + hRadCoefC2C);
    1891            0 :                     tempdenom = (hConvCoefC2O + hRadCoefC2O) + (hConvCoefC2C + hRadCoefC2C);
    1892            0 :                     TempOuterCover = tempnom / tempdenom;
    1893            0 :                 } else if (Num == 1) {
    1894            0 :                     tempnom = this->CoverAbs[Num] * state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) + TempAbsPlate * (hConvCoefA2C + hRadCoefA2C) +
    1895            0 :                               TempOuterCover * (hConvCoefC2C + hRadCoefC2C);
    1896            0 :                     tempdenom = (hConvCoefC2C + hRadCoefC2C + hConvCoefA2C + hRadCoefA2C);
    1897            0 :                     TempInnerCover = tempnom / tempdenom;
    1898              :                 }
    1899              :             }
    1900              :         }
    1901            0 :         this->TempOfInnerCover = TempInnerCover;
    1902            0 :         this->TempOfOuterCover = TempOuterCover;
    1903            0 :     }
    1904              : 
    1905            1 :     Real64 CollectorData::CalcConvCoeffBetweenPlates(Real64 const TempSurf1, // temperature of surface 1
    1906              :                                                      Real64 const TempSurf2, // temperature of surface 1
    1907              :                                                      Real64 const AirGap,    // characteristic length [m]
    1908              :                                                      Real64 const CosTilt,   // cosine of surface tilt angle relative to the horizontal
    1909              :                                                      Real64 const SinTilt    // sine of surface tilt angle relative to the horizontal
    1910              :     )
    1911              :     {
    1912              : 
    1913              :         // FUNCTION INFORMATION:
    1914              :         //       AUTHOR         Bereket Nigusse, FSEC/UCF
    1915              :         //       DATE WRITTEN   February 2012
    1916              : 
    1917              :         // PURPOSE OF THIS FUNCTION:
    1918              :         //   Calculates the convection coefficient for an enclosure between two parallel surfaces
    1919              :         //   at different temperatures.
    1920              :         // METHODOLOGY EMPLOYED:
    1921              :         //   Uses empirical correlation by Holands et al (1976) to determine free convection between
    1922              :         //   inclined parallel plates at different temperature.
    1923              :         // REFERENCES:
    1924              :         //   Duffie, J. A., and Beckman, W. A.  Solar Engineering of Thermal Processes, 2nd. Edition.
    1925              :         //   Wiley-Interscience: New York (1991).
    1926              :         //   Property data for air at atmospheric pressure were taken from Table A-11, Yunus A Cengel
    1927              :         //   Heat Transfer: A Practical Approach, McGraw-Hill, Boston, MA, 1998.
    1928              : 
    1929            1 :         Real64 constexpr gravity(9.806); // gravitational constant [m/s^2]
    1930              : 
    1931            1 :         int constexpr NumOfPropDivisions(11);
    1932              :         static constexpr std::array<Real64, NumOfPropDivisions> Temps = {
    1933              :             -23.15, 6.85, 16.85, 24.85, 26.85, 36.85, 46.85, 56.85, 66.85, 76.85, 126.85}; // Temperature, in C
    1934              :         static constexpr std::array<Real64, NumOfPropDivisions> Mu = {
    1935              :             0.0000161, 0.0000175, 0.000018, 0.0000184, 0.0000185, 0.000019, 0.0000194, 0.0000199, 0.0000203, 0.0000208, 0.0000229}; // Viscosity, in
    1936              :                                                                                                                                     // kg/(m.s)
    1937              :         static constexpr std::array<Real64, NumOfPropDivisions> Conductivity = {
    1938              :             0.0223, 0.0246, 0.0253, 0.0259, 0.0261, 0.0268, 0.0275, 0.0283, 0.0290, 0.0297, 0.0331}; // Conductivity, in W/mK
    1939              :         static constexpr std::array<Real64, NumOfPropDivisions> Pr = {
    1940              :             0.724, 0.717, 0.714, 0.712, 0.712, 0.711, 0.71, 0.708, 0.707, 0.706, 0.703}; // Prandtl number (dimensionless)
    1941              :         static constexpr std::array<Real64, NumOfPropDivisions> Density = {
    1942              :             1.413, 1.271, 1.224, 1.186, 1.177, 1.143, 1.110, 1.076, 1.043, 1.009, 0.883}; // Density, in kg/m3
    1943              : 
    1944              :         Real64 CondOfAir; // thermal conductivity of air [W/mK]
    1945              :         Real64 VisDOfAir; // dynamic viscosity of air [kg/m.s]
    1946              :         Real64 DensOfAir; // density of air [W/mK]
    1947              :         Real64 PrOfAir;   // Prandtl number of air [W/mK]
    1948              :         Real64 VolExpAir; // volumetric expansion of air [1/K]
    1949              : 
    1950            1 :         Real64 DeltaT = std::abs(TempSurf1 - TempSurf2);
    1951            1 :         Real64 Tref = 0.5 * (TempSurf1 + TempSurf2);
    1952            1 :         int Index = 0;
    1953           12 :         while (Index < NumOfPropDivisions) {
    1954           11 :             if (Tref < Temps[Index]) break; // DO loop
    1955           11 :             ++Index;
    1956              :         }
    1957              : 
    1958              :         // Initialize thermal properties of air
    1959            1 :         if (Index == 0) {
    1960            0 :             VisDOfAir = Mu[Index];
    1961            0 :             CondOfAir = Conductivity[Index];
    1962            0 :             PrOfAir = Pr[Index];
    1963            0 :             DensOfAir = Density[Index];
    1964            1 :         } else if (Index >= NumOfPropDivisions) { // 0-index, hence MaxIndex = NumOfPropDivisions - 1
    1965            1 :             Index = NumOfPropDivisions - 1;
    1966            1 :             VisDOfAir = Mu[Index];
    1967            1 :             CondOfAir = Conductivity[Index];
    1968            1 :             PrOfAir = Pr[Index];
    1969            1 :             DensOfAir = Density[Index];
    1970              :         } else {
    1971            0 :             Real64 InterpFrac = (Tref - Temps[Index - 1]) / (Temps[Index] - Temps[Index - 1]);
    1972            0 :             VisDOfAir = Mu[Index - 1] + InterpFrac * (Mu[Index] - Mu[Index - 1]);
    1973            0 :             CondOfAir = Conductivity[Index - 1] + InterpFrac * (Conductivity[Index] - Conductivity[Index - 1]);
    1974            0 :             PrOfAir = Pr[Index - 1] + InterpFrac * (Pr[Index] - Pr[Index - 1]);
    1975            0 :             DensOfAir = Density[Index - 1] + InterpFrac * (Density[Index] - Density[Index - 1]);
    1976              :         }
    1977              : 
    1978            1 :         VolExpAir = 1.0 / (Tref + Constant::Kelvin);
    1979              : 
    1980              :         // Rayleigh number
    1981            1 :         Real64 RaNum = gravity * pow_2(DensOfAir) * VolExpAir * PrOfAir * DeltaT * pow_3(AirGap) / pow_2(VisDOfAir);
    1982              : 
    1983              :         // Rayleigh number of air times cosine of collector tilt []
    1984            1 :         Real64 RaNumCosTilt = RaNum * CosTilt;
    1985              : 
    1986              :         Real64 NuL; // Nusselt number
    1987            1 :         if (RaNum == 0.0) {
    1988            0 :             NuL = 0.0;
    1989              :         } else {
    1990            1 :             if (RaNumCosTilt > 1708.0) {
    1991            1 :                 NuL = 1.44 * (1.0 - 1708.0 * std::pow(SinTilt, 1.6) / (RaNum * CosTilt)) * (1.0 - 1708.0 / RaNumCosTilt);
    1992              :             } else {
    1993            0 :                 NuL = 0.0;
    1994              :             }
    1995              :         }
    1996            1 :         if (RaNumCosTilt > 5830.0) {
    1997            1 :             NuL += std::pow(RaNumCosTilt / 5830.0 - 1.0, 1.0 / 3.0);
    1998              :         }
    1999            1 :         ++NuL;
    2000            1 :         Real64 hConvCoef = NuL * CondOfAir / AirGap;
    2001              : 
    2002            1 :         return hConvCoef;
    2003              :     }
    2004              : 
    2005            0 :     Real64 CollectorData::CalcConvCoeffAbsPlateAndWater(EnergyPlusData &state,
    2006              :                                                         Real64 const TAbsorber, // temperature of absorber plate [C]
    2007              :                                                         Real64 const TWater,    // temperature of water [C]
    2008              :                                                         Real64 const Lc,        // characteristic length [m]
    2009              :                                                         Real64 const TiltR2V    // collector tilt angle relative to the vertical [degree]
    2010              :     )
    2011              :     {
    2012              : 
    2013              :         // FUNCTION INFORMATION:
    2014              :         //       AUTHOR         Bereket Nigusse, FSEC/UCF
    2015              :         //       DATE WRITTEN   February 2012
    2016              : 
    2017              :         // PURPOSE OF THIS FUNCTION:
    2018              :         //  Calculates the free convection coefficient between the absorber plate and water.
    2019              :         // METHODOLOGY EMPLOYED:
    2020              :         //  The convection coefficient calculation were based on the Fujii and Imura empirical correlations
    2021              :         // REFERENCES:
    2022              :         //  T.Fujii, and H.Imura,Natural convection heat transfer from a plate with arbitrary inclination.
    2023              :         //  International Journal of Heat and Mass Transfer: 15(4), (1972), 755-764.
    2024              : 
    2025              :         Real64 hConvA2W; // convection coefficient, [W/m2K]
    2026              : 
    2027            0 :         Real64 constexpr gravity(9.806); // gravitational constant [m/s^2]
    2028              :         static constexpr std::string_view CalledFrom("SolarCollectors:CalcConvCoeffAbsPlateAndWater");
    2029              : 
    2030            0 :         Real64 DeltaT = std::abs(TAbsorber - TWater);
    2031            0 :         Real64 TReference = TAbsorber - 0.25 * (TAbsorber - TWater);
    2032              :         // record fluid prop index for water
    2033              : 
    2034            0 :         auto *water = Fluid::GetWater(state);
    2035              :         // find properties of water - always assume water
    2036            0 :         Real64 WaterSpecHeat = water->getSpecificHeat(state, max(TReference, 0.0), CalledFrom);
    2037            0 :         Real64 CondOfWater = water->getConductivity(state, max(TReference, 0.0), CalledFrom);
    2038            0 :         Real64 VisOfWater = water->getViscosity(state, max(TReference, 0.0), CalledFrom);
    2039            0 :         Real64 DensOfWater = water->getDensity(state, max(TReference, 0.0), CalledFrom);
    2040            0 :         Real64 PrOfWater = VisOfWater * WaterSpecHeat / CondOfWater;
    2041              :         // Requires a different reference temperature for volumetric expansion coefficient
    2042            0 :         TReference = TWater - 0.25 * (TWater - TAbsorber);
    2043              :         Real64 VolExpWater =
    2044            0 :             -(water->getDensity(state, max(TReference, 10.0) + 5.0, CalledFrom) - water->getDensity(state, max(TReference, 10.0) - 5.0, CalledFrom)) /
    2045            0 :             (10.0 * DensOfWater);
    2046              : 
    2047              :         // Grashof number
    2048            0 :         Real64 GrNum = gravity * VolExpWater * DensOfWater * DensOfWater * PrOfWater * DeltaT * pow_3(Lc) / pow_2(VisOfWater);
    2049            0 :         Real64 CosTilt = std::cos(TiltR2V * Constant::DegToRad);
    2050              : 
    2051              :         Real64 RaNum; // Raleigh number
    2052              :         Real64 NuL;   // Nusselt number
    2053            0 :         if (TAbsorber > TWater) {
    2054              :             // hot absorber plate facing down
    2055            0 :             if (std::abs(TiltR2V - 90.0) < 1.0) {
    2056              :                 // It is a horizontal surface
    2057            0 :                 RaNum = GrNum * PrOfWater;
    2058            0 :                 if (RaNum <= 1708.0) {
    2059            0 :                     NuL = 1.0;
    2060              :                 } else {
    2061            0 :                     NuL = 0.58 * std::pow(RaNum, 0.20);
    2062              :                 }
    2063              :             } else {
    2064            0 :                 RaNum = GrNum * PrOfWater * CosTilt;
    2065            0 :                 if (RaNum <= 1708.0) {
    2066            0 :                     NuL = 1.0;
    2067              :                 } else {
    2068            0 :                     NuL = 0.56 * root_4(RaNum);
    2069              :                 }
    2070              :             }
    2071              :         } else {
    2072              :             // cold plate facing down or hot plate facing up
    2073            0 :             RaNum = GrNum * PrOfWater;
    2074            0 :             if (RaNum > 5.0e8) {
    2075            0 :                 NuL = 0.13 * std::pow(RaNum, 1.0 / 3.0);
    2076              :             } else {
    2077            0 :                 NuL = 0.16 * std::pow(RaNum, 1.0 / 3.0);
    2078            0 :                 if (RaNum <= 1708.0) NuL = 1.0;
    2079              :             }
    2080              :         }
    2081            0 :         hConvA2W = NuL * CondOfWater / Lc;
    2082              : 
    2083            0 :         return hConvA2W;
    2084              :     }
    2085              : 
    2086            0 :     void CollectorData::update(EnergyPlusData &state) // NOLINT(readability-make-member-function-const)
    2087              :     {
    2088              : 
    2089              :         // SUBROUTINE INFORMATION:
    2090              :         //       AUTHOR         Peter Graham Ellis
    2091              :         //       DATE WRITTEN   January 2004
    2092              : 
    2093              :         // PURPOSE OF THIS SUBROUTINE:
    2094              :         // Updates the node variables with local variables.
    2095              : 
    2096              :         static constexpr std::string_view RoutineName("UpdateSolarCollector");
    2097              : 
    2098            0 :         PlantUtilities::SafeCopyPlantNode(state, this->InletNode, this->OutletNode);
    2099              :         // Set outlet node variables that are possibly changed
    2100            0 :         state.dataLoopNodes->Node(this->OutletNode).Temp = this->OutletTemp;
    2101            0 :         Real64 Cp = this->plantLoc.loop->glycol->getSpecificHeat(state, this->OutletTemp, RoutineName);
    2102            0 :         state.dataLoopNodes->Node(this->OutletNode).Enthalpy = Cp * state.dataLoopNodes->Node(this->OutletNode).Temp;
    2103            0 :     }
    2104              : 
    2105            0 :     void CollectorData::report(EnergyPlusData &state)
    2106              :     {
    2107              : 
    2108              :         // SUBROUTINE INFORMATION:
    2109              :         //       AUTHOR         Peter Graham Ellis
    2110              :         //       DATE WRITTEN   January 2004
    2111              : 
    2112              :         // PURPOSE OF THIS SUBROUTINE:
    2113              :         // Calculates report variables.
    2114              : 
    2115            0 :         Real64 TimeStepInSecond = state.dataHVACGlobal->TimeStepSysSec;
    2116              : 
    2117            0 :         this->Energy = this->Power * TimeStepInSecond;
    2118            0 :         this->HeatEnergy = this->HeatRate * TimeStepInSecond;
    2119            0 :         this->CollHeatLossEnergy = this->SkinHeatLossRate * TimeStepInSecond;
    2120            0 :         this->StoredHeatEnergy = this->StoredHeatRate * TimeStepInSecond;
    2121            0 :     }
    2122              : 
    2123            0 :     void CollectorData::GetExtVentedCavityIndex(EnergyPlusData &state, int const SurfacePtr, int &VentCavIndex)
    2124              :     {
    2125              : 
    2126              :         // SUBROUTINE INFORMATION:
    2127              :         //       AUTHOR         B. Nigusse, FSEC. Adopted from Photovoltaics module
    2128              :         //       DATE WRITTEN   February 2012
    2129              : 
    2130              :         // PURPOSE OF THIS SUBROUTINE:
    2131              :         // object oriented "Get" routine for establishing correct integer index from outside this module
    2132              : 
    2133              :         // METHODOLOGY EMPLOYED:
    2134              :         // mine Surface derived type for correct index/number of surface
    2135              :         // mine  ExtVentedCavity derived type that has the surface.
    2136              :         // Adapted from Photovoltaics module, originally developed by Brent G. (2004)
    2137              : 
    2138              :         bool Found;
    2139              : 
    2140            0 :         if (SurfacePtr == 0) {
    2141              :             // should be trapped already
    2142            0 :             ShowFatalError(state, "Invalid surface passed to GetExtVentedCavityIndex");
    2143              :         }
    2144              : 
    2145            0 :         int CavNum = 0;
    2146            0 :         Found = false;
    2147            0 :         for (int thisCav = 1; thisCav <= state.dataSurface->TotExtVentCav; ++thisCav) {
    2148            0 :             for (int ThisSurf = 1; ThisSurf <= state.dataHeatBal->ExtVentedCavity(thisCav).NumSurfs; ++ThisSurf) {
    2149            0 :                 if (SurfacePtr == state.dataHeatBal->ExtVentedCavity(thisCav).SurfPtrs(ThisSurf)) {
    2150            0 :                     Found = true;
    2151            0 :                     CavNum = thisCav;
    2152              :                 }
    2153              :             }
    2154              :         }
    2155              : 
    2156            0 :         if (!Found) {
    2157            0 :             ShowFatalError(state,
    2158            0 :                            format("Did not find surface in Exterior Vented Cavity description in GetExtVentedCavityIndex, Surface name = {}",
    2159            0 :                                   state.dataSurface->Surface(SurfacePtr).Name));
    2160              :         } else {
    2161              : 
    2162            0 :             VentCavIndex = CavNum;
    2163              :         }
    2164            0 :     }
    2165            0 :     void CollectorData::oneTimeInit_new(EnergyPlusData &state)
    2166              :     {
    2167              : 
    2168            0 :         this->setupOutputVars(state);
    2169              : 
    2170            0 :         if (this->SetLoopIndexFlag) {
    2171            0 :             if (allocated(state.dataPlnt->PlantLoop)) {
    2172            0 :                 bool errFlag = false;
    2173            0 :                 PlantUtilities::ScanPlantLoopsForObject(state, this->Name, this->Type, this->plantLoc, errFlag, _, _, _, _, _);
    2174            0 :                 if (errFlag) {
    2175            0 :                     ShowFatalError(state, "InitSolarCollector: Program terminated due to previous condition(s).");
    2176              :                 }
    2177            0 :                 this->SetLoopIndexFlag = false;
    2178              :             }
    2179              :         }
    2180            0 :     }
    2181            0 :     void CollectorData::oneTimeInit([[maybe_unused]] EnergyPlusData &state)
    2182              :     {
    2183            0 :     }
    2184              : 
    2185              : } // namespace SolarCollectors
    2186              : 
    2187              : } // namespace EnergyPlus
        

Generated by: LCOV version 2.0-1