LCOV - code coverage report
Current view: top level - EnergyPlus - GroundHeatExchangers.cc (source / functions) Hit Total Coverage
Test: lcov.output.filtered Lines: 1253 1526 82.1 %
Date: 2024-08-23 23:50:59 Functions: 72 79 91.1 %

          Line data    Source code
       1             : // EnergyPlus, Copyright (c) 1996-2024, The Board of Trustees of the University of Illinois,
       2             : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
       3             : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
       4             : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
       5             : // contributors. All rights reserved.
       6             : //
       7             : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
       8             : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
       9             : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
      10             : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
      11             : // derivative works, and perform publicly and display publicly, and to permit others to do so.
      12             : //
      13             : // Redistribution and use in source and binary forms, with or without modification, are permitted
      14             : // provided that the following conditions are met:
      15             : //
      16             : // (1) Redistributions of source code must retain the above copyright notice, this list of
      17             : //     conditions and the following disclaimer.
      18             : //
      19             : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
      20             : //     conditions and the following disclaimer in the documentation and/or other materials
      21             : //     provided with the distribution.
      22             : //
      23             : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
      24             : //     the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
      25             : //     used to endorse or promote products derived from this software without specific prior
      26             : //     written permission.
      27             : //
      28             : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
      29             : //     without changes from the version obtained under this License, or (ii) Licensee makes a
      30             : //     reference solely to the software portion of its product, Licensee must refer to the
      31             : //     software as "EnergyPlus version X" software, where "X" is the version number Licensee
      32             : //     obtained under this License and may not use a different name for the software. Except as
      33             : //     specifically required in this Section (4), Licensee shall not use in a company name, a
      34             : //     product name, in advertising, publicity, or other promotional activities any name, trade
      35             : //     name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
      36             : //     similar designation, without the U.S. Department of Energy's prior written consent.
      37             : //
      38             : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
      39             : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
      40             : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
      41             : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
      42             : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
      43             : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
      44             : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
      45             : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
      46             : // POSSIBILITY OF SUCH DAMAGE.
      47             : 
      48             : // C++ Headers
      49             : #include <cmath>
      50             : #include <vector>
      51             : 
      52             : // ObjexxFCL Headers
      53             : #include <ObjexxFCL/Array.functions.hh>
      54             : #include <ObjexxFCL/Fmath.hh>
      55             : 
      56             : // cpgfunction Headers
      57             : #include <cpgfunction/boreholes.h>
      58             : #include <cpgfunction/gfunction.h>
      59             : #include <cpgfunction/segments.h>
      60             : 
      61             : // JSON Headers
      62             : #include <nlohmann/json.hpp>
      63             : 
      64             : // EnergyPlus Headers
      65             : #include <EnergyPlus/BranchNodeConnections.hh>
      66             : #include <EnergyPlus/Data/EnergyPlusData.hh>
      67             : #include <EnergyPlus/DataEnvironment.hh>
      68             : #include <EnergyPlus/DataHVACGlobals.hh>
      69             : #include <EnergyPlus/DataLoopNode.hh>
      70             : #include <EnergyPlus/DataStringGlobals.hh>
      71             : #include <EnergyPlus/DataSystemVariables.hh>
      72             : #include <EnergyPlus/DisplayRoutines.hh>
      73             : #include <EnergyPlus/FileSystem.hh>
      74             : #include <EnergyPlus/FluidProperties.hh>
      75             : #include <EnergyPlus/General.hh>
      76             : #include <EnergyPlus/GroundHeatExchangers.hh>
      77             : #include <EnergyPlus/GroundTemperatureModeling/GroundTemperatureModelManager.hh>
      78             : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
      79             : #include <EnergyPlus/NodeInputManager.hh>
      80             : #include <EnergyPlus/OutputProcessor.hh>
      81             : #include <EnergyPlus/Plant/DataPlant.hh>
      82             : #include <EnergyPlus/PlantUtilities.hh>
      83             : #include <EnergyPlus/UtilityRoutines.hh>
      84             : 
      85             : namespace EnergyPlus::GroundHeatExchangers {
      86             : // MODULE INFORMATION:
      87             : //       AUTHOR         Arun Murugappan, Dan Fisher
      88             : //       DATE WRITTEN   September 2000
      89             : //       MODIFIED       B. Griffith, Sept 2010,plant upgrades
      90             : //                      Matt Mitchell, February 2015. Added Slinky GHX.
      91             : //                                                    Moved models to object-oriented design.
      92             : 
      93             : // PURPOSE OF THIS MODULE:
      94             : // The module contains the data structures and routines to simulate the
      95             : // operation of vertical closed-loop ground heat exchangers (GLHE) typically
      96             : // used in low temperature geothermal heat pump systems.
      97             : 
      98             : // METHODOLOGY EMPLOYED:
      99             : // The borehole and fluid temperatures are calculated from the response to
     100             : // the current heat transfer rate and the response to the history of past
     101             : // applied heat pulses. The response to each pulse is calculated from a non-
     102             : // dimensionalized response function, or G-function, that is specific to the
     103             : // given borehole field arrangement, depth and spacing. The data defining
     104             : // this function is read from input.
     105             : // The heat pulse histories need to be recorded over an extended period (months).
     106             : // To aid computational efficiency past pulses are continuously aggregated into
     107             : // equivalent heat pulses of longer duration, as each pulse becomes less recent.
     108             : 
     109             : // REFERENCES:
     110             : // Eskilson, P. 'Thermal Analysis of Heat Extraction Boreholes' Ph.D. Thesis:
     111             : //   Dept. of Mathematical Physics, University of Lund, Sweden, June 1987.
     112             : // Yavuzturk, C., J.D. Spitler. 1999. 'A Short Time Step Response Factor Model
     113             : //   for Vertical Ground Loop Heat Exchangers. ASHRAE Transactions. 105(2): 475-485.
     114             : // Xiong, Z., D.E. Fisher, J.D. Spitler. 2015. 'Development and Validation of a Slinky
     115             : //   Ground Heat Exchanger.' Applied Energy. Vol 114, 57-69.
     116             : 
     117             : // MODULE PARAMETER DEFINITIONS
     118             : constexpr Real64 hrsPerMonth = 730.0; // Number of hours in month
     119             : constexpr Real64 maxTSinHr = 60.0;    // Max number of time step in a hour
     120             : static constexpr std::array<std::string_view, 2> GFuncCalcMethodsStrs = {"UHFCALC", "UBHWTCALC"};
     121             : 
     122             : //******************************************************************************
     123             : 
     124           1 : GLHESlinky::GLHESlinky(EnergyPlusData &state, std::string const &objName, nlohmann::json const &j)
     125             : {
     126             :     // Check for duplicates
     127           1 :     for (auto &existingObj : state.dataGroundHeatExchanger->singleBoreholesVector) {
     128           0 :         if (objName == existingObj->name) {
     129           0 :             ShowFatalError(state, format("Invalid input for {} object: Duplicate name found: {}", this->moduleName, existingObj->name));
     130             :         }
     131           1 :     }
     132             : 
     133           1 :     bool errorsFound = false;
     134             : 
     135           1 :     this->name = objName;
     136             : 
     137           1 :     std::string inletNodeName = Util::makeUPPER(j["inlet_node_name"].get<std::string>());
     138           1 :     std::string outletNodeName = Util::makeUPPER(j["outlet_node_name"].get<std::string>());
     139             : 
     140             :     // get inlet node num
     141           1 :     this->inletNodeNum = NodeInputManager::GetOnlySingleNode(state,
     142             :                                                              inletNodeName,
     143             :                                                              errorsFound,
     144             :                                                              DataLoopNode::ConnectionObjectType::GroundHeatExchangerSlinky,
     145           1 :                                                              this->name,
     146             :                                                              DataLoopNode::NodeFluidType::Water,
     147             :                                                              DataLoopNode::ConnectionType::Inlet,
     148             :                                                              NodeInputManager::CompFluidStream::Primary,
     149             :                                                              DataLoopNode::ObjectIsNotParent);
     150             : 
     151             :     // get outlet node num
     152           1 :     this->outletNodeNum = NodeInputManager::GetOnlySingleNode(state,
     153             :                                                               outletNodeName,
     154             :                                                               errorsFound,
     155             :                                                               DataLoopNode::ConnectionObjectType::GroundHeatExchangerSlinky,
     156           1 :                                                               this->name,
     157             :                                                               DataLoopNode::NodeFluidType::Water,
     158             :                                                               DataLoopNode::ConnectionType::Outlet,
     159             :                                                               NodeInputManager::CompFluidStream::Primary,
     160             :                                                               DataLoopNode::ObjectIsNotParent);
     161             : 
     162           1 :     this->available = true;
     163           1 :     this->on = true;
     164             : 
     165           1 :     BranchNodeConnections::TestCompSet(state, this->moduleName, this->name, inletNodeName, outletNodeName, "Condenser Water Nodes");
     166             : 
     167             :     // load data
     168           1 :     this->designFlow = j["design_flow_rate"].get<Real64>();
     169           1 :     PlantUtilities::RegisterPlantCompDesignFlow(state, this->inletNodeNum, this->designFlow);
     170             : 
     171           1 :     this->soil.k = j["soil_thermal_conductivity"].get<Real64>();
     172           1 :     this->soil.rho = j["soil_density"].get<Real64>();
     173           1 :     this->soil.cp = j["soil_specific_heat"].get<Real64>();
     174           1 :     this->soil.rhoCp = this->soil.rho * this->soil.cp;
     175           1 :     this->pipe.k = j["pipe_thermal_conductivity"].get<Real64>();
     176           1 :     this->pipe.rho = j["pipe_density"].get<Real64>();
     177           1 :     this->pipe.cp = j["pipe_specific_heat"].get<Real64>();
     178           1 :     this->pipe.outDia = j["pipe_outer_diameter"].get<Real64>();
     179           1 :     this->pipe.outRadius = this->pipe.outDia / 2.0;
     180           1 :     this->pipe.thickness = j["pipe_thickness"].get<Real64>();
     181             : 
     182           1 :     std::string const hxConfig = Util::makeUPPER(j["heat_exchanger_configuration"].get<std::string>());
     183           1 :     if (Util::SameString(hxConfig, "VERTICAL")) {
     184           1 :         this->verticalConfig = true;
     185           0 :     } else if (Util::SameString(hxConfig, "HORIZONTAL")) {
     186           0 :         this->verticalConfig = false;
     187             :     }
     188             : 
     189           1 :     this->coilDiameter = j["coil_diameter"].get<Real64>();
     190           1 :     this->coilPitch = j["coil_pitch"].get<Real64>();
     191           1 :     this->trenchDepth = j["trench_depth"].get<Real64>();
     192           1 :     this->trenchLength = j["trench_length"].get<Real64>();
     193           1 :     this->numTrenches = j["number_of_trenches"].get<int>();
     194           1 :     this->trenchSpacing = j["horizontal_spacing_between_pipes"].get<Real64>();
     195           1 :     this->maxSimYears = j["maximum_length_of_simulation"].get<Real64>();
     196             : 
     197             :     // Need to add a response factor object for the slinky model
     198           1 :     std::shared_ptr<GLHEResponseFactors> thisRF(new GLHEResponseFactors);
     199           1 :     thisRF->name = "Response Factor Object Auto Generated No: " + fmt::to_string(state.dataGroundHeatExchanger->numAutoGeneratedResponseFactors + 1);
     200           1 :     this->myRespFactors = thisRF;
     201           1 :     state.dataGroundHeatExchanger->responseFactorsVector.push_back(thisRF);
     202             : 
     203             :     // Number of coils
     204           1 :     this->numCoils = static_cast<int>(this->trenchLength / this->coilPitch);
     205             : 
     206             :     // Total tube length
     207           1 :     this->totalTubeLength = Constant::Pi * this->coilDiameter * this->trenchLength * this->numTrenches / this->coilPitch;
     208             : 
     209             :     // Get g function data
     210           1 :     this->SubAGG = 15;
     211           1 :     this->AGG = 192;
     212             : 
     213             :     // Average coil depth
     214           1 :     if (this->verticalConfig) {
     215             :         // Vertical configuration
     216           1 :         if (this->trenchDepth - this->coilDiameter < 0.0) {
     217             :             // Error: part of the coil is above ground
     218           0 :             ShowSevereError(state, format("{}=\"{}\", invalid value in field.", this->moduleName, this->name));
     219           0 :             ShowContinueError(state, format("...{}=[{:.3R}].", "Trench Depth", this->trenchDepth));
     220           0 :             ShowContinueError(state, format("...{}=[{:.3R}].", "Coil Depth", this->coilDepth));
     221           0 :             ShowContinueError(state, "...Part of coil will be above ground.");
     222           0 :             errorsFound = true;
     223             : 
     224             :         } else {
     225             :             // Entire coil is below ground
     226           1 :             this->coilDepth = this->trenchDepth - (this->coilDiameter / 2.0);
     227             :         }
     228             : 
     229             :     } else {
     230             :         // Horizontal configuration
     231           0 :         this->coilDepth = this->trenchDepth;
     232             :     }
     233             : 
     234             :     // Thermal diffusivity of the ground
     235           1 :     this->soil.diffusivity = this->soil.k / this->soil.rhoCp;
     236             : 
     237           1 :     state.dataGroundHeatExchanger->prevTimeSteps.allocate(static_cast<int>((this->SubAGG + 1) * maxTSinHr + 1));
     238           1 :     state.dataGroundHeatExchanger->prevTimeSteps = 0.0;
     239             : 
     240           1 :     if (this->pipe.thickness >= this->pipe.outDia / 2.0) {
     241           0 :         ShowSevereError(state, format("{}=\"{}\", invalid value in field.", this->moduleName, this->name));
     242           0 :         ShowContinueError(state, format("...{}=[{:.3R}].", "Pipe Thickness", this->pipe.thickness));
     243           0 :         ShowContinueError(state, format("...{}=[{:.3R}].", "Pipe Outer Diameter", this->pipe.outDia));
     244           0 :         ShowContinueError(state, "...Radius will be <=0.");
     245           0 :         errorsFound = true;
     246             :     }
     247             : 
     248             :     // Initialize ground temperature model and get pointer reference
     249           1 :     std::string const gtmType = Util::makeUPPER(j["undisturbed_ground_temperature_model_type"].get<std::string>());
     250           1 :     std::string const gtmName = Util::makeUPPER(j["undisturbed_ground_temperature_model_name"].get<std::string>());
     251           1 :     this->groundTempModel = GetGroundTempModelAndInit(state, gtmType, gtmName);
     252             : 
     253             :     // Check for Errors
     254           1 :     if (errorsFound) {
     255           0 :         ShowFatalError(state, format("Errors found in processing input for {}", this->moduleName));
     256             :     }
     257           1 : }
     258             : 
     259             : //******************************************************************************
     260             : 
     261          23 : GLHEVert::GLHEVert(EnergyPlusData &state, std::string const &objName, nlohmann::json const &j)
     262             : {
     263             :     // Check for duplicates
     264          23 :     for (auto &existingObj : state.dataGroundHeatExchanger->singleBoreholesVector) {
     265           0 :         if (objName == existingObj->name) {
     266           0 :             ShowFatalError(state, format("Invalid input for {} object: Duplicate name found: {}", this->moduleName, existingObj->name));
     267             :         }
     268          23 :     }
     269             : 
     270          23 :     bool errorsFound = false;
     271             : 
     272          23 :     this->name = objName;
     273             : 
     274             :     // get inlet node num
     275          23 :     std::string const inletNodeName = Util::makeUPPER(j["inlet_node_name"].get<std::string>());
     276             : 
     277          23 :     this->inletNodeNum = NodeInputManager::GetOnlySingleNode(state,
     278             :                                                              inletNodeName,
     279             :                                                              errorsFound,
     280             :                                                              DataLoopNode::ConnectionObjectType::GroundHeatExchangerSystem,
     281             :                                                              objName,
     282             :                                                              DataLoopNode::NodeFluidType::Water,
     283             :                                                              DataLoopNode::ConnectionType::Inlet,
     284             :                                                              NodeInputManager::CompFluidStream::Primary,
     285             :                                                              DataLoopNode::ObjectIsNotParent);
     286             : 
     287             :     // get outlet node num
     288          23 :     std::string const outletNodeName = Util::makeUPPER(j["outlet_node_name"].get<std::string>());
     289             : 
     290          23 :     this->outletNodeNum = NodeInputManager::GetOnlySingleNode(state,
     291             :                                                               outletNodeName,
     292             :                                                               errorsFound,
     293             :                                                               DataLoopNode::ConnectionObjectType::GroundHeatExchangerSystem,
     294             :                                                               objName,
     295             :                                                               DataLoopNode::NodeFluidType::Water,
     296             :                                                               DataLoopNode::ConnectionType::Outlet,
     297             :                                                               NodeInputManager::CompFluidStream::Primary,
     298             :                                                               DataLoopNode::ObjectIsNotParent);
     299          23 :     this->available = true;
     300          23 :     this->on = true;
     301             : 
     302          23 :     BranchNodeConnections::TestCompSet(state, this->moduleName, objName, inletNodeName, outletNodeName, "Condenser Water Nodes");
     303             : 
     304          23 :     this->designFlow = j["design_flow_rate"].get<Real64>();
     305          23 :     PlantUtilities::RegisterPlantCompDesignFlow(state, this->inletNodeNum, this->designFlow);
     306             : 
     307          23 :     this->soil.k = j["ground_thermal_conductivity"].get<Real64>();
     308          23 :     this->soil.rhoCp = j["ground_thermal_heat_capacity"].get<Real64>();
     309             : 
     310          23 :     if (j.find("ghe_vertical_responsefactors_object_name") != j.end()) {
     311             :         // Response factors come from IDF object
     312          21 :         this->myRespFactors = GetResponseFactor(state, Util::makeUPPER(j["ghe_vertical_responsefactors_object_name"].get<std::string>()));
     313          21 :         this->gFunctionsExist = true;
     314             :     }
     315             : 
     316             :     // no g-functions in the input file, so they need to be calculated
     317          23 :     if (!this->gFunctionsExist) {
     318             : 
     319             :         // g-function calculation method
     320           2 :         if (j.find("g_function_calculation_method") != j.end()) {
     321           2 :             std::string gFunctionMethodStr = Util::makeUPPER(j["g_function_calculation_method"].get<std::string>());
     322           2 :             if (gFunctionMethodStr == "UHFCALC") {
     323           1 :                 this->gFuncCalcMethod = GFuncCalcMethod::UniformHeatFlux;
     324           1 :             } else if (gFunctionMethodStr == "UBHWTCALC") {
     325           1 :                 this->gFuncCalcMethod = GFuncCalcMethod::UniformBoreholeWallTemp;
     326             :             } else {
     327           0 :                 errorsFound = true;
     328           0 :                 ShowSevereError(state, fmt::format("g-Function Calculation Method: \"{}\" is invalid", gFunctionMethodStr));
     329             :             }
     330           2 :         }
     331             : 
     332             :         // get borehole data from array or individual borehole instance objects
     333           2 :         if (j.find("ghe_vertical_array_object_name") != j.end()) {
     334             :             // Response factors come from array object
     335           4 :             this->myRespFactors = BuildAndGetResponseFactorObjectFromArray(
     336           6 :                 state, GetVertArray(state, Util::makeUPPER(j["ghe_vertical_array_object_name"].get<std::string>())));
     337             :         } else {
     338           0 :             if (j.find("vertical_well_locations") == j.end()) {
     339             :                 // No ResponseFactors, GHEArray, or SingleBH object are referenced
     340           0 :                 ShowSevereError(state, "No GHE:ResponseFactors, GHE:Vertical:Array, or GHE:Vertical:Single objects found");
     341           0 :                 ShowFatalError(state, format("Check references to these objects for GHE:System object: {}", this->name));
     342             :             }
     343             : 
     344           0 :             auto const &vars = j.at("vertical_well_locations");
     345             : 
     346             :             // Calculate response factors from individual boreholes
     347           0 :             std::vector<std::shared_ptr<GLHEVertSingle>> tempVectOfBHObjects;
     348             : 
     349           0 :             for (auto const &var : vars) {
     350           0 :                 if (!var.at("ghe_vertical_single_object_name").empty()) {
     351             :                     std::shared_ptr<GLHEVertSingle> tempBHptr =
     352           0 :                         GetSingleBH(state, Util::makeUPPER(var.at("ghe_vertical_single_object_name").get<std::string>()));
     353           0 :                     tempVectOfBHObjects.push_back(tempBHptr);
     354           0 :                 } else {
     355           0 :                     break;
     356             :                 }
     357           0 :             }
     358             : 
     359           0 :             this->myRespFactors = BuildAndGetResponseFactorsObjectFromSingleBHs(state, tempVectOfBHObjects);
     360             : 
     361           0 :             if (!this->myRespFactors) {
     362           0 :                 errorsFound = true;
     363           0 :                 ShowSevereError(state, "GroundHeatExchanger:Vertical:Single objects not found.");
     364             :             }
     365           0 :         }
     366             :     }
     367             : 
     368          23 :     this->bhDiameter = this->myRespFactors->props->bhDiameter;
     369          23 :     this->bhRadius = this->bhDiameter / 2.0;
     370          23 :     this->bhLength = this->myRespFactors->props->bhLength;
     371          23 :     this->bhUTubeDist = this->myRespFactors->props->bhUTubeDist;
     372             : 
     373             :     // pull pipe and grout data up from response factor struct for simplicity
     374          23 :     this->pipe.outDia = this->myRespFactors->props->pipe.outDia;
     375          23 :     this->pipe.innerDia = this->myRespFactors->props->pipe.innerDia;
     376          23 :     this->pipe.outRadius = this->pipe.outDia / 2;
     377          23 :     this->pipe.innerRadius = this->pipe.innerDia / 2;
     378          23 :     this->pipe.thickness = this->myRespFactors->props->pipe.thickness;
     379          23 :     this->pipe.k = this->myRespFactors->props->pipe.k;
     380          23 :     this->pipe.rhoCp = this->myRespFactors->props->pipe.rhoCp;
     381             : 
     382          23 :     this->grout.k = this->myRespFactors->props->grout.k;
     383          23 :     this->grout.rhoCp = this->myRespFactors->props->grout.rhoCp;
     384             : 
     385          23 :     this->myRespFactors->gRefRatio = this->bhRadius / this->bhLength;
     386             : 
     387             :     // Number of simulation years from RunPeriod
     388          23 :     this->myRespFactors->maxSimYears = state.dataEnvrn->MaxNumberSimYears;
     389             : 
     390             :     // total tube length
     391          23 :     this->totalTubeLength = this->myRespFactors->numBoreholes * this->myRespFactors->props->bhLength;
     392             : 
     393             :     // ground thermal diffusivity
     394          23 :     this->soil.diffusivity = this->soil.k / this->soil.rhoCp;
     395             : 
     396             :     // multipole method constants
     397          23 :     this->theta_1 = this->bhUTubeDist / (2 * this->bhRadius);
     398          23 :     this->theta_2 = this->bhRadius / this->pipe.outRadius;
     399          23 :     this->theta_3 = 1 / (2 * this->theta_1 * this->theta_2);
     400          23 :     this->sigma = (this->grout.k - this->soil.k) / (this->grout.k + this->soil.k);
     401             : 
     402          23 :     this->SubAGG = 15;
     403          23 :     this->AGG = 192;
     404             : 
     405             :     // Allocation of all the dynamic arrays
     406          23 :     this->QnMonthlyAgg.dimension(static_cast<int>(this->myRespFactors->maxSimYears * 12), 0.0);
     407          23 :     this->QnHr.dimension(730 + this->AGG + this->SubAGG, 0.0);
     408          23 :     this->QnSubHr.dimension(static_cast<int>((this->SubAGG + 1) * maxTSinHr + 1), 0.0);
     409          23 :     this->LastHourN.dimension(this->SubAGG + 1, 0);
     410             : 
     411          23 :     state.dataGroundHeatExchanger->prevTimeSteps.allocate(static_cast<int>((this->SubAGG + 1) * maxTSinHr + 1));
     412          23 :     state.dataGroundHeatExchanger->prevTimeSteps = 0.0;
     413             : 
     414             :     // Initialize ground temperature model and get pointer reference
     415          46 :     this->groundTempModel = GetGroundTempModelAndInit(state,
     416          46 :                                                       Util::makeUPPER(j["undisturbed_ground_temperature_model_type"].get<std::string>()),
     417          69 :                                                       Util::makeUPPER(j["undisturbed_ground_temperature_model_name"].get<std::string>()));
     418             : 
     419             :     // Check for Errors
     420          23 :     if (errorsFound) {
     421           0 :         ShowFatalError(state, format("Errors found in processing input for {}", this->moduleName));
     422             :     }
     423          23 : }
     424             : 
     425             : //******************************************************************************
     426             : 
     427           0 : GLHEVertSingle::GLHEVertSingle(EnergyPlusData &state, std::string const &objName, nlohmann::json const &j)
     428             : {
     429             :     // Check for duplicates
     430           0 :     for (auto &existingObj : state.dataGroundHeatExchanger->singleBoreholesVector) {
     431           0 :         if (objName == existingObj->name) {
     432           0 :             ShowFatalError(state, format("Invalid input for {} object: Duplicate name found: {}", this->moduleName, existingObj->name));
     433             :         }
     434           0 :     }
     435             : 
     436           0 :     this->name = objName;
     437           0 :     this->props = GetVertProps(state, Util::makeUPPER(j["ghe_vertical_properties_object_name"].get<std::string>()));
     438           0 :     this->xLoc = j["x_location"].get<Real64>();
     439           0 :     this->yLoc = j["y_location"].get<Real64>();
     440           0 :     this->dl_i = 0.0;
     441           0 :     this->dl_ii = 0.0;
     442           0 :     this->dl_j = 0.0;
     443           0 : }
     444             : 
     445             : //******************************************************************************
     446             : 
     447           2 : GLHEVertArray::GLHEVertArray(EnergyPlusData &state, std::string const &objName, nlohmann::json const &j)
     448             : {
     449             :     // Check for duplicates
     450           2 :     for (auto &existingObj : state.dataGroundHeatExchanger->vertArraysVector) {
     451           0 :         if (objName == existingObj->name) {
     452           0 :             ShowFatalError(state, format("Invalid input for {} object: Duplicate name found: {}", this->moduleName, existingObj->name));
     453             :         }
     454           2 :     }
     455             : 
     456           2 :     this->name = objName;
     457           2 :     this->props = GetVertProps(state, Util::makeUPPER(j["ghe_vertical_properties_object_name"].get<std::string>()));
     458           2 :     this->numBHinXDirection = j["number_of_boreholes_in_x_direction"].get<int>();
     459           2 :     this->numBHinYDirection = j["number_of_boreholes_in_y_direction"].get<int>();
     460           2 :     this->bhSpacing = j["borehole_spacing"].get<Real64>();
     461           2 : }
     462             : 
     463             : //******************************************************************************
     464             : 
     465          21 : GLHEResponseFactors::GLHEResponseFactors(EnergyPlusData &state, std::string const &objName, nlohmann::json const &j)
     466             : {
     467             : 
     468             :     // Check for duplicates
     469          42 :     for (auto &existingObj : state.dataGroundHeatExchanger->vertPropsVector) {
     470          21 :         if (objName == existingObj->name) {
     471           0 :             ShowFatalError(state, format("Invalid input for {} object: Duplicate name found: {}", this->moduleName, existingObj->name));
     472             :         }
     473          21 :     }
     474             : 
     475          21 :     this->name = objName;
     476          21 :     this->props = GetVertProps(state, Util::makeUPPER(j["ghe_vertical_properties_object_name"].get<std::string>()));
     477          21 :     this->numBoreholes = j["number_of_boreholes"].get<int>();
     478          21 :     this->gRefRatio = j["g_function_reference_ratio"].get<Real64>();
     479          21 :     this->maxSimYears = state.dataEnvrn->MaxNumberSimYears;
     480             : 
     481          21 :     auto const &vars = j.at("g_functions");
     482          21 :     std::vector<Real64> tmpLntts;
     483          21 :     std::vector<Real64> tmpGvals;
     484         756 :     for (auto const &var : vars) {
     485         735 :         tmpLntts.push_back(var.at("g_function_ln_t_ts_value").get<Real64>());
     486         735 :         tmpGvals.push_back(var.at("g_function_g_value").get<Real64>());
     487          21 :     }
     488             : 
     489          21 :     this->numGFuncPairs = static_cast<int>(tmpLntts.size());
     490          21 :     this->LNTTS.dimension(this->numGFuncPairs, 0.0);
     491          21 :     this->GFNC.dimension(this->numGFuncPairs, 0.0);
     492             : 
     493         756 :     for (int i = 1; i <= (int)tmpLntts.size(); ++i) {
     494         735 :         this->LNTTS(i) = tmpLntts[i - 1];
     495         735 :         this->GFNC(i) = tmpGvals[i - 1];
     496             :     }
     497          21 : }
     498             : 
     499             : //******************************************************************************
     500             : 
     501          23 : GLHEVertProps::GLHEVertProps(EnergyPlusData &state, std::string const &objName, nlohmann::json const &j)
     502             : {
     503             : 
     504             :     // Check for duplicates
     505          23 :     for (auto &existingObj : state.dataGroundHeatExchanger->vertPropsVector) {
     506           0 :         if (objName == existingObj->name) {
     507           0 :             ShowFatalError(state, format("Invalid input for {} object: Duplicate name found: {}", this->moduleName, existingObj->name));
     508             :         }
     509          23 :     }
     510             : 
     511             :     // Load data from JSON
     512          23 :     this->name = objName;
     513          23 :     this->bhTopDepth = j["depth_of_top_of_borehole"].get<Real64>();
     514          23 :     this->bhLength = j["borehole_length"].get<Real64>();
     515          23 :     this->bhDiameter = j["borehole_diameter"].get<Real64>();
     516          23 :     this->grout.k = j["grout_thermal_conductivity"].get<Real64>();
     517          23 :     this->grout.rhoCp = j["grout_thermal_heat_capacity"].get<Real64>();
     518          23 :     this->pipe.k = j["pipe_thermal_conductivity"].get<Real64>();
     519          23 :     this->pipe.rhoCp = j["pipe_thermal_heat_capacity"].get<Real64>();
     520          23 :     this->pipe.outDia = j["pipe_outer_diameter"].get<Real64>();
     521          23 :     this->pipe.thickness = j["pipe_thickness"].get<Real64>();
     522          23 :     this->bhUTubeDist = j["u_tube_distance"].get<Real64>();
     523             : 
     524             :     // Verify u-tube spacing is valid
     525          23 :     if (this->bhUTubeDist < this->pipe.outDia) {
     526           1 :         ShowWarningError(state, "Borehole shank spacing is less than the pipe diameter. U-tube spacing is reference from the u-tube pipe center.");
     527           1 :         ShowWarningError(state, "Shank spacing is set to the outer pipe diameter.");
     528           1 :         this->bhUTubeDist = this->pipe.outDia;
     529             :     }
     530             : 
     531             :     // Set remaining data derived from previous inputs
     532          23 :     this->pipe.innerDia = this->pipe.outDia - 2 * this->pipe.thickness;
     533          23 :     this->pipe.outRadius = this->pipe.outDia / 2;
     534          23 :     this->pipe.innerRadius = this->pipe.innerDia / 2;
     535          23 : }
     536             : 
     537             : //******************************************************************************
     538             : 
     539          31 : std::shared_ptr<GLHEVertProps> GetVertProps(EnergyPlusData &state, std::string const &objectName)
     540             : {
     541             :     // Check if this instance of this model has already been retrieved
     542          62 :     auto thisObj = std::find_if(state.dataGroundHeatExchanger->vertPropsVector.begin(),
     543          31 :                                 state.dataGroundHeatExchanger->vertPropsVector.end(),
     544          93 :                                 [&objectName](const std::shared_ptr<GLHEVertProps> &myObj) { return myObj->name == objectName; });
     545          31 :     if (thisObj != state.dataGroundHeatExchanger->vertPropsVector.end()) return *thisObj;
     546             : 
     547           0 :     ShowSevereError(state, fmt::format("Object=GroundHeatExchanger:Vertical:Properties, Name={} - not found.", objectName));
     548           0 :     ShowFatalError(state, "Preceding errors cause program termination");
     549             : 
     550             :     // needed to silence compiler, but should never get here
     551           0 :     return nullptr;
     552          31 : }
     553             : 
     554             : //******************************************************************************
     555             : 
     556           0 : std::shared_ptr<GLHEVertSingle> GetSingleBH(EnergyPlusData &state, std::string const &objectName)
     557             : {
     558             :     // Check if this instance of this model has already been retrieved
     559           0 :     auto thisObj = std::find_if(state.dataGroundHeatExchanger->singleBoreholesVector.begin(),
     560           0 :                                 state.dataGroundHeatExchanger->singleBoreholesVector.end(),
     561           0 :                                 [&objectName](const std::shared_ptr<GLHEVertSingle> &myObj) { return myObj->name == objectName; });
     562           0 :     if (thisObj != state.dataGroundHeatExchanger->singleBoreholesVector.end()) return *thisObj;
     563             : 
     564           0 :     ShowSevereError(state, fmt::format("Object=GroundHeatExchanger:Vertical:Single, Name={} - not found.", objectName));
     565           0 :     ShowFatalError(state, "Preceding errors cause program termination");
     566             : 
     567             :     // needed to silence compiler, but should never get here
     568           0 :     return nullptr;
     569           0 : }
     570             : 
     571             : //******************************************************************************
     572             : 
     573           2 : std::shared_ptr<GLHEVertArray> GetVertArray(EnergyPlusData &state, std::string const &objectName)
     574             : {
     575             :     // Check if this instance of this model has already been retrieved
     576           4 :     auto thisObj = std::find_if(state.dataGroundHeatExchanger->vertArraysVector.begin(),
     577           2 :                                 state.dataGroundHeatExchanger->vertArraysVector.end(),
     578           6 :                                 [&objectName](const std::shared_ptr<GLHEVertArray> &myObj) { return myObj->name == objectName; });
     579           2 :     if (thisObj != state.dataGroundHeatExchanger->vertArraysVector.end()) return *thisObj;
     580             : 
     581           0 :     ShowSevereError(state, fmt::format("Object=GroundHeatExchanger:Vertical:Array, Name={} - not found.", objectName));
     582           0 :     ShowFatalError(state, "Preceding errors cause program termination");
     583             : 
     584             :     // needed to silence compiler, but should never get here
     585           0 :     return nullptr;
     586           2 : }
     587             : 
     588             : //******************************************************************************
     589             : 
     590          21 : std::shared_ptr<GLHEResponseFactors> GetResponseFactor(EnergyPlusData &state, std::string const &objectName)
     591             : {
     592             :     // Check if this instance of this model has already been retrieved
     593          42 :     auto thisObj = std::find_if(state.dataGroundHeatExchanger->responseFactorsVector.begin(),
     594          21 :                                 state.dataGroundHeatExchanger->responseFactorsVector.end(),
     595          63 :                                 [&objectName](const std::shared_ptr<GLHEResponseFactors> &myObj) { return myObj->name == objectName; });
     596          21 :     if (thisObj != state.dataGroundHeatExchanger->responseFactorsVector.end()) return *thisObj;
     597             : 
     598           0 :     ShowSevereError(state, fmt::format("Object=GroundHeatExchanger:ResponseFactors, Name={} - not found.", objectName));
     599           0 :     ShowFatalError(state, "Preceding errors cause program termination");
     600             : 
     601             :     // needed to silence compiler, but should never get here
     602           0 :     return nullptr;
     603          21 : }
     604             : 
     605             : //******************************************************************************
     606             : 
     607           2 : std::shared_ptr<GLHEResponseFactors> BuildAndGetResponseFactorObjectFromArray(EnergyPlusData &state,
     608             :                                                                               std::shared_ptr<GLHEVertArray> const &arrayObjectPtr)
     609             : {
     610             :     // Make new response factor object and store it for later use
     611           2 :     std::shared_ptr<GLHEResponseFactors> thisRF(new GLHEResponseFactors);
     612           2 :     thisRF->name = arrayObjectPtr->name;
     613           2 :     thisRF->props = arrayObjectPtr->props;
     614             : 
     615             :     // Build out new instances of the vertical BH objects which correspond to this object
     616           2 :     int xLoc = 0;
     617           2 :     int bhCounter = 0;
     618           6 :     for (int xBH = 1; xBH <= arrayObjectPtr->numBHinXDirection; ++xBH) {
     619           4 :         int yLoc = 0;
     620          12 :         for (int yBH = 1; yBH <= arrayObjectPtr->numBHinYDirection; ++yBH) {
     621           8 :             bhCounter += 1;
     622           8 :             std::shared_ptr<GLHEVertSingle> thisBH(new GLHEVertSingle);
     623           8 :             thisBH->name = format("{} BH {} loc: ({}, {})", thisRF->name, bhCounter, xLoc, yLoc);
     624           8 :             thisBH->props = GetVertProps(state, arrayObjectPtr->props->name);
     625           8 :             thisBH->xLoc = xLoc;
     626           8 :             thisBH->yLoc = yLoc;
     627           8 :             thisRF->myBorholes.push_back(thisBH);
     628           8 :             state.dataGroundHeatExchanger->singleBoreholesVector.push_back(thisBH);
     629           8 :             yLoc += arrayObjectPtr->bhSpacing;
     630           8 :             thisRF->numBoreholes += 1;
     631           8 :         }
     632           4 :         xLoc += arrayObjectPtr->bhSpacing;
     633             :     }
     634             : 
     635           2 :     SetupBHPointsForResponseFactorsObject(thisRF);
     636           2 :     state.dataGroundHeatExchanger->responseFactorsVector.push_back(thisRF);
     637           4 :     return thisRF;
     638           0 : }
     639             : 
     640             : //******************************************************************************
     641             : 
     642             : std::shared_ptr<GLHEResponseFactors>
     643           0 : BuildAndGetResponseFactorsObjectFromSingleBHs(EnergyPlusData &state, std::vector<std::shared_ptr<GLHEVertSingle>> const &singleBHsForRFVect)
     644             : {
     645             :     // Make new response factor object and store it for later use
     646           0 :     std::shared_ptr<GLHEResponseFactors> thisRF(new GLHEResponseFactors);
     647           0 :     thisRF->name = format("Response Factor Object Auto Generated No: {}", state.dataGroundHeatExchanger->numAutoGeneratedResponseFactors + 1);
     648             : 
     649             :     // Make new props object which has the mean values of the other props objects referenced by the individual BH objects
     650           0 :     std::shared_ptr<GLHEVertProps> thisProps(new GLHEVertProps);
     651           0 :     thisProps->name = format("Response Factor Auto Generated Mean Props No: {}", state.dataGroundHeatExchanger->numAutoGeneratedResponseFactors + 1);
     652           0 :     for (auto &thisBH : state.dataGroundHeatExchanger->singleBoreholesVector) {
     653           0 :         thisProps->bhDiameter += thisBH->props->bhDiameter;
     654           0 :         thisProps->bhLength += thisBH->props->bhLength;
     655           0 :         thisProps->bhTopDepth += thisBH->props->bhTopDepth;
     656           0 :         thisProps->bhUTubeDist += thisBH->props->bhUTubeDist;
     657             : 
     658           0 :         thisProps->grout.cp += thisBH->props->grout.cp;
     659           0 :         thisProps->grout.diffusivity += thisBH->props->grout.diffusivity;
     660           0 :         thisProps->grout.k += thisBH->props->grout.k;
     661           0 :         thisProps->grout.rho += thisBH->props->grout.rho;
     662           0 :         thisProps->grout.rhoCp += thisBH->props->grout.rhoCp;
     663             : 
     664           0 :         thisProps->pipe.cp += thisBH->props->pipe.cp;
     665           0 :         thisProps->pipe.diffusivity += thisBH->props->pipe.diffusivity;
     666           0 :         thisProps->pipe.k += thisBH->props->pipe.k;
     667           0 :         thisProps->pipe.rho += thisBH->props->pipe.rho;
     668           0 :         thisProps->pipe.rhoCp += thisBH->props->pipe.rhoCp;
     669             : 
     670           0 :         thisProps->pipe.outDia += thisBH->props->pipe.outDia;
     671           0 :         thisProps->pipe.thickness += thisBH->props->pipe.thickness;
     672             : 
     673           0 :         thisProps->pipe.innerDia += (thisBH->props->pipe.outDia - 2 * thisBH->props->pipe.thickness);
     674             : 
     675           0 :         thisRF->myBorholes.push_back(thisBH);
     676           0 :     }
     677             : 
     678           0 :     int numBH = (int)singleBHsForRFVect.size();
     679             : 
     680             :     // normalize by number of bh
     681           0 :     thisProps->bhDiameter /= numBH;
     682           0 :     thisProps->bhLength /= numBH;
     683           0 :     thisProps->bhTopDepth /= numBH;
     684           0 :     thisProps->bhUTubeDist /= numBH;
     685             : 
     686           0 :     thisProps->grout.cp /= numBH;
     687           0 :     thisProps->grout.diffusivity /= numBH;
     688           0 :     thisProps->grout.k /= numBH;
     689           0 :     thisProps->grout.rho /= numBH;
     690           0 :     thisProps->grout.rhoCp /= numBH;
     691             : 
     692           0 :     thisProps->pipe.cp /= numBH;
     693           0 :     thisProps->pipe.diffusivity /= numBH;
     694           0 :     thisProps->pipe.k /= numBH;
     695           0 :     thisProps->pipe.rho /= numBH;
     696           0 :     thisProps->pipe.rhoCp /= numBH;
     697             : 
     698           0 :     thisProps->pipe.outDia /= numBH;
     699           0 :     thisProps->pipe.thickness /= numBH;
     700             : 
     701           0 :     thisProps->pipe.innerDia /= numBH;
     702             : 
     703           0 :     thisRF->props = thisProps;
     704           0 :     thisRF->numBoreholes = thisRF->myBorholes.size();
     705           0 :     state.dataGroundHeatExchanger->vertPropsVector.push_back(thisProps);
     706             : 
     707           0 :     SetupBHPointsForResponseFactorsObject(thisRF);
     708             : 
     709           0 :     state.dataGroundHeatExchanger->responseFactorsVector.push_back(thisRF);
     710             : 
     711           0 :     state.dataGroundHeatExchanger->numAutoGeneratedResponseFactors += 1;
     712             : 
     713           0 :     return thisRF;
     714           0 : }
     715             : 
     716             : //******************************************************************************
     717             : 
     718           2 : void SetupBHPointsForResponseFactorsObject(const std::shared_ptr<GLHEResponseFactors> &thisRF)
     719             : {
     720          10 :     for (auto const &thisBH : thisRF->myBorholes) {
     721             : 
     722             :         // Using Simpson's rule the number of points (n+1) must be odd, therefore an even number of panels is required
     723             :         // Starting from i = 0 to i <= NumPanels produces an odd number of points
     724           8 :         constexpr int numPanels_i = 50;
     725           8 :         constexpr int numPanels_ii = 50;
     726           8 :         constexpr int numPanels_j = 560;
     727             : 
     728           8 :         thisBH->dl_i = thisBH->props->bhLength / numPanels_i;
     729         416 :         for (int i = 0; i <= numPanels_i; ++i) {
     730         408 :             MyCartesian newPoint;
     731         408 :             newPoint.x = thisBH->xLoc;
     732         408 :             newPoint.y = thisBH->yLoc;
     733         408 :             newPoint.z = thisBH->props->bhTopDepth + (i * thisBH->dl_i);
     734         408 :             thisBH->pointLocations_i.push_back(newPoint);
     735             :         }
     736             : 
     737           8 :         thisBH->dl_ii = thisBH->props->bhLength / numPanels_ii;
     738         416 :         for (int i = 0; i <= numPanels_ii; ++i) {
     739         408 :             MyCartesian newPoint;
     740             :             // For case when bh is being compared to itself, shift points by 1 radius in the horizontal plane
     741         408 :             newPoint.x = thisBH->xLoc + (thisBH->props->bhDiameter / 2.0) / sqrt(2.0);
     742         408 :             newPoint.y = thisBH->yLoc + (thisBH->props->bhDiameter / 2.0) / (-sqrt(2.0));
     743         408 :             newPoint.z = thisBH->props->bhTopDepth + (i * thisBH->dl_ii);
     744         408 :             thisBH->pointLocations_ii.push_back(newPoint);
     745             :         }
     746             : 
     747           8 :         thisBH->dl_j = thisBH->props->bhLength / numPanels_j;
     748        4496 :         for (int i = 0; i <= numPanels_j; ++i) {
     749        4488 :             MyCartesian newPoint;
     750        4488 :             newPoint.x = thisBH->xLoc;
     751        4488 :             newPoint.y = thisBH->yLoc;
     752        4488 :             newPoint.z = thisBH->props->bhTopDepth + (i * thisBH->dl_j);
     753        4488 :             thisBH->pointLocations_j.push_back(newPoint);
     754             :         }
     755           2 :     }
     756           2 : }
     757             : 
     758             : //******************************************************************************
     759             : 
     760         120 : void GLHEBase::onInitLoopEquip(EnergyPlusData &state, [[maybe_unused]] const PlantLocation &calledFromLocation)
     761             : {
     762         120 :     this->initGLHESimVars(state);
     763         120 : }
     764             : 
     765             : //******************************************************************************
     766             : 
     767      375885 : void GLHEBase::simulate(EnergyPlusData &state,
     768             :                         [[maybe_unused]] const PlantLocation &calledFromLocation,
     769             :                         [[maybe_unused]] bool const FirstHVACIteration,
     770             :                         [[maybe_unused]] Real64 &CurLoad,
     771             :                         [[maybe_unused]] bool const RunFlag)
     772             : {
     773             : 
     774      375885 :     if (this->needToSetupOutputVars) {
     775          24 :         this->setupOutput(state);
     776          24 :         this->needToSetupOutputVars = false;
     777             :     }
     778             : 
     779      375885 :     if (state.dataGlobal->KickOffSimulation) {
     780        2125 :         this->initGLHESimVars(state);
     781             :     } else {
     782      373760 :         this->initGLHESimVars(state);
     783      373760 :         this->calcGroundHeatExchanger(state);
     784      373760 :         this->updateGHX(state);
     785             :     }
     786      375885 : }
     787             : 
     788             : //******************************************************************************
     789             : 
     790          24 : GLHEBase *GLHEBase::factory(EnergyPlusData &state, DataPlant::PlantEquipmentType objectType, std::string const &objectName)
     791             : {
     792          24 :     if (state.dataGroundHeatExchanger->GetInput) {
     793          24 :         GetGroundHeatExchangerInput(state);
     794          24 :         state.dataGroundHeatExchanger->GetInput = false;
     795             :     }
     796          24 :     if (objectType == DataPlant::PlantEquipmentType::GrndHtExchgSystem) {
     797          46 :         auto thisObj = std::find_if(state.dataGroundHeatExchanger->verticalGLHE.begin(),
     798          23 :                                     state.dataGroundHeatExchanger->verticalGLHE.end(),
     799          69 :                                     [&objectName](const GLHEBase &myObj) { return myObj.name == objectName; });
     800          23 :         if (thisObj != state.dataGroundHeatExchanger->verticalGLHE.end()) return &(*thisObj);
     801          24 :     } else if (objectType == DataPlant::PlantEquipmentType::GrndHtExchgSlinky) {
     802           2 :         auto thisObj = std::find_if(state.dataGroundHeatExchanger->slinkyGLHE.begin(),
     803           1 :                                     state.dataGroundHeatExchanger->slinkyGLHE.end(),
     804           3 :                                     [&objectName](const GLHEBase &myObj) { return myObj.name == objectName; });
     805           1 :         if (thisObj != state.dataGroundHeatExchanger->slinkyGLHE.end()) return &(*thisObj);
     806           1 :     }
     807             : 
     808             :     // If we didn't find it, fatal
     809           0 :     ShowFatalError(state, format("Ground Heat Exchanger Factory: Error getting inputs for GHX named: {}", objectName));
     810             :     // Shut up the compiler
     811           0 :     return nullptr;
     812             : }
     813             : 
     814             : //******************************************************************************
     815             : 
     816     5035536 : std::vector<Real64> GLHEVert::distances(MyCartesian const &point_i, MyCartesian const &point_j)
     817             : {
     818     5035536 :     std::vector<Real64> sumVals;
     819             : 
     820             :     // Calculate the distance between points
     821     5035536 :     sumVals.push_back(pow_2(point_i.x - point_j.x));
     822     5035536 :     sumVals.push_back(pow_2(point_i.y - point_j.y));
     823     5035536 :     sumVals.push_back(pow_2(point_i.z - point_j.z));
     824             : 
     825     5035536 :     Real64 sumTot = 0.0;
     826     5035536 :     std::vector<Real64> retVals;
     827     5035536 :     std::for_each(sumVals.begin(), sumVals.end(), [&](Real64 n) { sumTot += n; });
     828     5035536 :     retVals.push_back(std::sqrt(sumTot));
     829             : 
     830             :     // Calculate distance to mirror point
     831     5035536 :     sumVals.pop_back();
     832     5035536 :     sumVals.push_back(pow_2(point_i.z - (-point_j.z)));
     833             : 
     834     5035536 :     sumTot = 0.0;
     835     5035536 :     std::for_each(sumVals.begin(), sumVals.end(), [&](Real64 n) { sumTot += n; });
     836     5035536 :     retVals.push_back(std::sqrt(sumTot));
     837             : 
     838    10071072 :     return retVals;
     839     5035536 : }
     840             : 
     841             : //******************************************************************************
     842             : 
     843     5035536 : Real64 GLHEVert::calcResponse(std::vector<Real64> const &dists, Real64 const currTime)
     844             : {
     845     5035536 :     Real64 pointToPointResponse = erfc(dists[0] / (2 * sqrt(this->soil.diffusivity * currTime))) / dists[0];
     846     5035536 :     Real64 pointToReflectedResponse = erfc(dists[1] / (2 * sqrt(this->soil.diffusivity * currTime))) / dists[1];
     847             : 
     848     5035536 :     return pointToPointResponse - pointToReflectedResponse;
     849             : }
     850             : 
     851             : //******************************************************************************
     852             : 
     853        8976 : Real64 GLHEVert::integral(MyCartesian const &point_i, std::shared_ptr<GLHEVertSingle> const &bh_j, Real64 const currTime)
     854             : {
     855             : 
     856             :     // This code could be optimized in a number of ways.
     857             :     // The first, most simple way would be to precompute the distances from point i to point j, then store them for reuse.
     858             :     // The second, more intensive method would be to break the calcResponse calls out into four different parts.
     859             :     // The first point, last point, odd points, and even points. Then multiply the odd/even points by their respective coefficient for the
     860             :     // Simpson's method. After that, all points are summed together and divided by 3.
     861             : 
     862        8976 :     Real64 sum_f = 0.0;
     863        8976 :     int i = 0;
     864        8976 :     int const lastIndex_j = static_cast<int>(bh_j->pointLocations_j.size() - 1u);
     865     5044512 :     for (auto const &point_j : bh_j->pointLocations_j) {
     866     5035536 :         std::vector<Real64> dists = distances(point_i, point_j);
     867     5035536 :         Real64 const f = calcResponse(dists, currTime);
     868             : 
     869             :         // Integrate using Simpson's
     870     5035536 :         if (i == 0 || i == lastIndex_j) {
     871       17952 :             sum_f += f;
     872     5017584 :         } else if (isEven(i)) {
     873     2504304 :             sum_f += 2 * f;
     874             :         } else {
     875     2513280 :             sum_f += 4 * f;
     876             :         }
     877             : 
     878     5035536 :         ++i;
     879     5044512 :     }
     880             : 
     881        8976 :     return (bh_j->dl_j / 3.0) * sum_f;
     882             : }
     883             : 
     884             : //******************************************************************************
     885             : 
     886         176 : Real64 GLHEVert::doubleIntegral(std::shared_ptr<GLHEVertSingle> const &bh_i, std::shared_ptr<GLHEVertSingle> const &bh_j, Real64 const currTime)
     887             : {
     888             : 
     889             :     // Similar optimizations as discussed above could happen here
     890             : 
     891         176 :     if (bh_i == bh_j) {
     892             : 
     893          44 :         Real64 sum_f = 0;
     894          44 :         int i = 0;
     895          44 :         int const lastIndex = static_cast<int>(bh_i->pointLocations_ii.size() - 1u);
     896        2288 :         for (auto &thisPoint : bh_i->pointLocations_ii) {
     897             : 
     898        2244 :             Real64 f = integral(thisPoint, bh_j, currTime);
     899             : 
     900             :             // Integrate using Simpson's
     901        2244 :             if (i == 0 || i == lastIndex) {
     902          88 :                 sum_f += f;
     903        2156 :             } else if (isEven(i)) {
     904        1056 :                 sum_f += 2 * f;
     905             :             } else {
     906        1100 :                 sum_f += 4 * f;
     907             :             }
     908             : 
     909        2244 :             ++i;
     910          44 :         }
     911             : 
     912          44 :         return (bh_i->dl_ii / 3.0) * sum_f;
     913             : 
     914             :     } else {
     915             : 
     916         132 :         Real64 sum_f = 0;
     917         132 :         int i = 0;
     918         132 :         int const lastIndex = static_cast<int>(bh_i->pointLocations_i.size() - 1u);
     919        6864 :         for (auto const &thisPoint : bh_i->pointLocations_i) {
     920             : 
     921        6732 :             Real64 f = integral(thisPoint, bh_j, currTime);
     922             : 
     923             :             // Integrate using Simpson's
     924        6732 :             if (i == 0 || i == lastIndex) {
     925         264 :                 sum_f += f;
     926        6468 :             } else if (isEven(i)) {
     927        3168 :                 sum_f += 2 * f;
     928             :             } else {
     929        3300 :                 sum_f += 4 * f;
     930             :             }
     931             : 
     932        6732 :             ++i;
     933         132 :         }
     934             : 
     935         132 :         return (bh_i->dl_i / 3.0) * sum_f;
     936             :     }
     937             : }
     938             : 
     939             : //******************************************************************************
     940             : 
     941           2 : void GLHEVert::calcLongTimestepGFunctions(EnergyPlusData &state)
     942             : {
     943           2 :     switch (this->gFuncCalcMethod) {
     944           1 :     case GFuncCalcMethod::UniformHeatFlux:
     945           1 :         this->calcUniformHeatFluxGFunctions(state);
     946           1 :         break;
     947           1 :     case GFuncCalcMethod::UniformBoreholeWallTemp:
     948           1 :         this->calcUniformBHWallTempGFunctions(state);
     949           1 :         break;
     950           0 :     default:
     951           0 :         assert(false);
     952             :     }
     953           2 : }
     954             : 
     955             : //******************************************************************************
     956             : 
     957           1 : void GLHEVert::calcUniformBHWallTempGFunctions(EnergyPlusData &state)
     958             : {
     959             :     // construct boreholes vector
     960           1 :     std::vector<gt::boreholes::Borehole> boreholes;
     961           5 :     for (auto &bh : this->myRespFactors->myBorholes) {
     962           4 :         boreholes.emplace_back(bh->props->bhLength, bh->props->bhTopDepth, bh->props->bhDiameter / 2.0, bh->xLoc, bh->yLoc);
     963           1 :     }
     964             : 
     965             :     // convert time to a std::vector from an Array1D
     966           1 :     std::vector<double> time;
     967          12 :     for (auto &v : this->myRespFactors->time) {
     968          11 :         time.push_back(v);
     969             :     }
     970             : 
     971             :     // Obtain number of segments by adaptive discretization
     972           1 :     gt::segments::adaptive adptDisc;
     973           1 :     int nSegments = adptDisc.discretize(this->bhLength, this->totalTubeLength);
     974             : 
     975           1 :     this->myRespFactors->GFNC =
     976           2 :         gt::gfunction::uniform_borehole_wall_temperature(boreholes, time, this->soil.diffusivity, nSegments, true, state.dataGlobal->numThread);
     977           1 : }
     978             : 
     979             : //******************************************************************************
     980             : 
     981           2 : void GLHEVert::calcGFunctions(EnergyPlusData &state)
     982             : {
     983             : 
     984             :     // No other choice than to calculate the g-functions here
     985           2 :     this->setupTimeVectors();
     986           2 :     this->calcShortTimestepGFunctions(state);
     987           2 :     this->calcLongTimestepGFunctions(state);
     988           2 :     this->combineShortAndLongTimestepGFunctions();
     989             : 
     990             :     // save data for later
     991           2 :     if (state.files.outputControl.glhe && !state.dataSysVars->DisableGLHECaching) {
     992           2 :         myCacheData["Response Factors"]["time"] = std::vector<Real64>(this->myRespFactors->time.begin(), this->myRespFactors->time.end());
     993           2 :         myCacheData["Response Factors"]["LNTTS"] = std::vector<Real64>(this->myRespFactors->LNTTS.begin(), this->myRespFactors->LNTTS.end());
     994           2 :         myCacheData["Response Factors"]["GFNC"] = std::vector<Real64>(this->myRespFactors->GFNC.begin(), this->myRespFactors->GFNC.end());
     995           2 :         writeGLHECacheToFile(state);
     996             :     }
     997           2 : }
     998             : 
     999             : //******************************************************************************
    1000             : 
    1001           2 : void GLHEVert::setupTimeVectors()
    1002             : {
    1003             : 
    1004           2 :     constexpr int numDaysInYear = 365;
    1005           2 :     constexpr Real64 lnttsStepSize = 0.5;
    1006             : 
    1007             :     // Minimum simulation time for which finite line source method is applicable
    1008           2 :     constexpr Real64 lntts_min_for_long_timestep = -8.5;
    1009             : 
    1010             :     // Time scale constant
    1011           2 :     Real64 const t_s = pow_2(this->bhLength) / (9 * this->soil.diffusivity);
    1012             : 
    1013             :     // Temporary vector for holding the LNTTS vals
    1014           2 :     std::vector<Real64> tempLNTTS;
    1015             : 
    1016           2 :     tempLNTTS.push_back(lntts_min_for_long_timestep);
    1017             : 
    1018             :     // Determine how many g-function pairs to generate based on user defined maximum simulation time
    1019             :     while (true) {
    1020          22 :         Real64 maxPossibleSimTime = exp(tempLNTTS.back()) * t_s;
    1021          22 :         if (maxPossibleSimTime < this->myRespFactors->maxSimYears * numDaysInYear * Constant::HoursInDay * Constant::SecInHour) {
    1022          20 :             tempLNTTS.push_back(tempLNTTS.back() + lnttsStepSize);
    1023             :         } else {
    1024           2 :             break;
    1025             :         }
    1026          20 :     }
    1027             : 
    1028             :     // Setup the arrays
    1029           2 :     this->myRespFactors->time.dimension(tempLNTTS.size(), 0.0);
    1030           2 :     this->myRespFactors->LNTTS.dimension(tempLNTTS.size(), 0.0);
    1031           2 :     this->myRespFactors->GFNC.dimension(tempLNTTS.size(), 0.0);
    1032             : 
    1033           2 :     int index = 1;
    1034          24 :     for (auto const &thisLNTTS : tempLNTTS) {
    1035          22 :         this->myRespFactors->time(index) = exp(thisLNTTS) * t_s;
    1036          22 :         this->myRespFactors->LNTTS(index) = thisLNTTS;
    1037          22 :         ++index;
    1038           2 :     }
    1039           2 : }
    1040             : 
    1041             : //******************************************************************************
    1042             : 
    1043           1 : void GLHEVert::calcUniformHeatFluxGFunctions(EnergyPlusData &state)
    1044             : {
    1045           1 :     DisplayString(state, "Initializing GroundHeatExchanger:System: " + this->name);
    1046             : 
    1047             :     // Calculate the g-functions
    1048          12 :     for (size_t lntts_index = 1; lntts_index <= this->myRespFactors->LNTTS.size(); ++lntts_index) {
    1049          55 :         for (auto const &bh_i : this->myRespFactors->myBorholes) {
    1050          44 :             Real64 sum_T_ji = 0;
    1051         220 :             for (auto const &bh_j : this->myRespFactors->myBorholes) {
    1052         176 :                 sum_T_ji += doubleIntegral(bh_i, bh_j, this->myRespFactors->time(lntts_index));
    1053          44 :             }
    1054          44 :             this->myRespFactors->GFNC(lntts_index) += sum_T_ji;
    1055          11 :         }
    1056          11 :         this->myRespFactors->GFNC(lntts_index) /= (2 * this->totalTubeLength);
    1057             : 
    1058          11 :         std::stringstream ss;
    1059          11 :         ss << std::fixed << std::setprecision(1) << float(lntts_index) / this->myRespFactors->LNTTS.size() * 100;
    1060             : 
    1061          11 :         DisplayString(state, "...progress: " + ss.str() + "%");
    1062          11 :     }
    1063           1 : }
    1064             : 
    1065             : //******************************************************************************
    1066             : 
    1067           2 : void GLHEVert::calcShortTimestepGFunctions(EnergyPlusData &state)
    1068             : {
    1069             :     // SUBROUTINE PARAMETER DEFINITIONS:
    1070           2 :     std::string_view const RoutineName = "calcShortTimestepGFunctions";
    1071             : 
    1072             :     enum class CellType
    1073             :     {
    1074             :         Invalid = -1,
    1075             :         FLUID,
    1076             :         CONVECTION,
    1077             :         PIPE,
    1078             :         GROUT,
    1079             :         SOIL,
    1080             :         Num
    1081             :     };
    1082             : 
    1083             :     struct Cell
    1084             :     {
    1085             : 
    1086             :         ~Cell() = default;
    1087             : 
    1088             :         CellType type;
    1089             :         Real64 radius_center;
    1090             :         Real64 radius_outer;
    1091             :         Real64 radius_inner;
    1092             :         Real64 thickness;
    1093             :         Real64 vol;
    1094             :         Real64 conductivity;
    1095             :         Real64 rhoCp;
    1096             :         Real64 temperature;
    1097             :         Real64 temperature_prev_ts;
    1098             : 
    1099        1070 :         Cell()
    1100        1070 :             : type(), radius_center(0.0), radius_outer(0.0), radius_inner(0.0), thickness(0.0), vol(0.0), conductivity(0.0), rhoCp(0.0),
    1101        1070 :               temperature(0.0), temperature_prev_ts(0.0)
    1102             :         {
    1103        1070 :         }
    1104             :     };
    1105             : 
    1106             :     // vector to hold 1-D cells
    1107           2 :     std::vector<Cell> Cells;
    1108             : 
    1109             :     // setup pipe, convection, and fluid layer geometries
    1110           2 :     constexpr int num_pipe_cells = 4;
    1111           2 :     constexpr int num_conv_cells = 1;
    1112           2 :     constexpr int num_fluid_cells = 3;
    1113           2 :     Real64 const pipe_thickness = this->pipe.thickness;
    1114           2 :     Real64 const pcf_cell_thickness = pipe_thickness / num_pipe_cells;
    1115           2 :     Real64 const radius_pipe_out = std::sqrt(2) * this->pipe.outRadius;
    1116           2 :     Real64 const radius_pipe_in = radius_pipe_out - pipe_thickness;
    1117           2 :     Real64 const radius_conv = radius_pipe_in - num_conv_cells * pcf_cell_thickness;
    1118           2 :     Real64 const radius_fluid = radius_conv - (num_fluid_cells - 0.5) * pcf_cell_thickness; // accounts for half thickness of boundary cell
    1119             : 
    1120             :     // setup grout layer geometry
    1121           2 :     constexpr int num_grout_cells = 27;
    1122           2 :     Real64 const radius_grout = this->bhRadius;
    1123           2 :     Real64 const grout_cell_thickness = (radius_grout - radius_pipe_out) / num_grout_cells;
    1124             : 
    1125             :     // setup soil layer geometry
    1126           2 :     constexpr int num_soil_cells = 500;
    1127           2 :     constexpr Real64 radius_soil = 10.0;
    1128           2 :     Real64 const soil_cell_thickness = (radius_soil - radius_grout) / num_soil_cells;
    1129             : 
    1130             :     // use design flow rate
    1131           2 :     this->massFlowRate = this->designMassFlow;
    1132             : 
    1133             :     // calculate equivalent thermal resistance between borehole wall and fluid
    1134           2 :     Real64 bhResistance = calcBHAverageResistance(state);
    1135           2 :     Real64 bhConvectionResistance = calcPipeConvectionResistance(state);
    1136           2 :     Real64 bh_equivalent_resistance_tube_grout = bhResistance - bhConvectionResistance / 2.0;
    1137           2 :     Real64 bh_equivalent_resistance_convection = bhResistance - bh_equivalent_resistance_tube_grout;
    1138             : 
    1139           2 :     Real64 initial_temperature = this->inletTemp;
    1140           2 :     Real64 cpFluid_init = FluidProperties::GetSpecificHeatGlycol(state,
    1141           2 :                                                                  state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidName,
    1142             :                                                                  initial_temperature,
    1143           2 :                                                                  state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidIndex,
    1144             :                                                                  RoutineName);
    1145           2 :     Real64 fluidDensity_init = FluidProperties::GetDensityGlycol(state,
    1146           2 :                                                                  state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidName,
    1147             :                                                                  initial_temperature,
    1148           2 :                                                                  state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidIndex,
    1149             :                                                                  RoutineName);
    1150             : 
    1151             :     // initialize the fluid cells
    1152           8 :     for (int i = 0; i < num_fluid_cells; ++i) {
    1153           6 :         Cell thisCell;
    1154           6 :         thisCell.type = CellType::FLUID;
    1155           6 :         thisCell.thickness = pcf_cell_thickness;
    1156           6 :         thisCell.radius_center = radius_fluid + i * thisCell.thickness;
    1157             : 
    1158             :         // boundary cell is only half thickness
    1159           6 :         if (i == 0) {
    1160           2 :             thisCell.radius_inner = thisCell.radius_center;
    1161             :         } else {
    1162           4 :             thisCell.radius_inner = thisCell.radius_center - thisCell.thickness / 2.0;
    1163             :         }
    1164             : 
    1165           6 :         thisCell.radius_outer = thisCell.radius_center + thisCell.thickness / 2.0;
    1166           6 :         thisCell.conductivity = 200;
    1167           6 :         thisCell.rhoCp = 2.0 * cpFluid_init * fluidDensity_init * pow_2(this->pipe.innerRadius) / (pow_2(radius_conv) - pow_2(radius_fluid));
    1168           6 :         Cells.push_back(thisCell);
    1169             :     }
    1170             : 
    1171             :     // initialize the convection cells
    1172           4 :     for (int i = 0; i < num_conv_cells; ++i) {
    1173           2 :         Cell thisCell;
    1174           2 :         thisCell.thickness = pcf_cell_thickness;
    1175           2 :         thisCell.radius_inner = radius_conv + i * thisCell.thickness;
    1176           2 :         thisCell.radius_center = thisCell.radius_inner + thisCell.thickness / 2.0;
    1177           2 :         thisCell.radius_outer = thisCell.radius_inner + thisCell.thickness;
    1178           2 :         thisCell.conductivity = log(radius_pipe_in / radius_conv) / (2 * Constant::Pi * bh_equivalent_resistance_convection);
    1179           2 :         thisCell.rhoCp = 1;
    1180           2 :         Cells.push_back(thisCell);
    1181             :     }
    1182             : 
    1183             :     // initialize pipe cells
    1184          10 :     for (int i = 0; i < num_pipe_cells; ++i) {
    1185           8 :         Cell thisCell;
    1186           8 :         thisCell.type = CellType::PIPE;
    1187           8 :         thisCell.thickness = pcf_cell_thickness;
    1188           8 :         thisCell.radius_inner = radius_pipe_in + i * thisCell.thickness;
    1189           8 :         thisCell.radius_center = thisCell.radius_inner + thisCell.thickness / 2.0;
    1190           8 :         thisCell.radius_outer = thisCell.radius_inner + thisCell.thickness;
    1191           8 :         thisCell.conductivity = log(radius_grout / radius_pipe_in) / (2 * Constant::Pi * bh_equivalent_resistance_tube_grout);
    1192           8 :         thisCell.rhoCp = this->pipe.rhoCp;
    1193           8 :         Cells.push_back(thisCell);
    1194             :     }
    1195             : 
    1196             :     // initialize grout cells
    1197          56 :     for (int i = 0; i < num_grout_cells; ++i) {
    1198          54 :         Cell thisCell;
    1199          54 :         thisCell.type = CellType::GROUT;
    1200          54 :         thisCell.thickness = grout_cell_thickness;
    1201          54 :         thisCell.radius_inner = radius_pipe_out + i * thisCell.thickness;
    1202          54 :         thisCell.radius_center = thisCell.radius_inner + thisCell.thickness / 2.0;
    1203          54 :         thisCell.radius_outer = thisCell.radius_inner + thisCell.thickness;
    1204          54 :         thisCell.conductivity = log(radius_grout / radius_pipe_in) / (2 * Constant::Pi * bh_equivalent_resistance_tube_grout);
    1205          54 :         thisCell.rhoCp = grout.rhoCp;
    1206          54 :         Cells.push_back(thisCell);
    1207             :     }
    1208             : 
    1209             :     // initialize soil cells
    1210        1002 :     for (int i = 0; i < num_soil_cells; ++i) {
    1211        1000 :         Cell thisCell;
    1212        1000 :         thisCell.type = CellType::SOIL;
    1213        1000 :         thisCell.thickness = soil_cell_thickness;
    1214        1000 :         thisCell.radius_inner = radius_grout + i * thisCell.thickness;
    1215        1000 :         thisCell.radius_center = thisCell.radius_inner + thisCell.thickness / 2.0;
    1216        1000 :         thisCell.radius_outer = thisCell.radius_inner + thisCell.thickness;
    1217        1000 :         thisCell.conductivity = this->soil.k;
    1218        1000 :         thisCell.rhoCp = this->soil.rhoCp;
    1219        1000 :         Cells.push_back(thisCell);
    1220             :     }
    1221             : 
    1222             :     // other non-geometric specific setup
    1223        1072 :     for (auto &thisCell : Cells) {
    1224        1070 :         thisCell.vol = Constant::Pi * (pow_2(thisCell.radius_outer) - pow_2(thisCell.radius_inner));
    1225        1070 :         thisCell.temperature = initial_temperature;
    1226           2 :     }
    1227             : 
    1228             :     // set upper limit of time for the short time-step g-function calcs so there is some overlap
    1229           2 :     Real64 constexpr lntts_max_for_short_timestep = -9.0;
    1230           2 :     Real64 const t_s = pow_2(this->bhLength) / (9.0 * this->soil.diffusivity);
    1231             : 
    1232           2 :     Real64 constexpr time_step = 500.0;
    1233           2 :     Real64 const time_max_for_short_timestep = exp(lntts_max_for_short_timestep) * t_s;
    1234           2 :     Real64 total_time = 0.0;
    1235             : 
    1236           2 :     Real64 constexpr heat_flux = 40.4;
    1237             : 
    1238             :     // time step loop
    1239         552 :     while (total_time < time_max_for_short_timestep) {
    1240             : 
    1241      294800 :         for (auto &thisCell : Cells) {
    1242      294250 :             thisCell.temperature_prev_ts = thisCell.temperature;
    1243         550 :         }
    1244             : 
    1245         550 :         std::vector<Real64> a;
    1246         550 :         std::vector<Real64> b;
    1247         550 :         std::vector<Real64> c;
    1248         550 :         std::vector<Real64> d;
    1249             : 
    1250             :         // setup tdma matrices
    1251         550 :         int num_cells = Cells.size();
    1252      294800 :         for (int cell_index = 0; cell_index < num_cells; ++cell_index) {
    1253      294250 :             if (cell_index == 0) {
    1254             :                 // heat flux BC
    1255             : 
    1256         550 :                 auto const &thisCell = Cells[cell_index];
    1257         550 :                 auto const &eastCell = Cells[cell_index + 1];
    1258             : 
    1259         550 :                 Real64 FE1 = log(thisCell.radius_outer / thisCell.radius_center) / (2 * Constant::Pi * thisCell.conductivity);
    1260         550 :                 Real64 FE2 = log(eastCell.radius_center / eastCell.radius_inner) / (2 * Constant::Pi * eastCell.conductivity);
    1261         550 :                 Real64 AE = 1 / (FE1 + FE2);
    1262             : 
    1263         550 :                 Real64 AD = thisCell.rhoCp * thisCell.vol / time_step;
    1264             : 
    1265         550 :                 a.push_back(0);
    1266         550 :                 b.push_back(-AE / AD - 1);
    1267         550 :                 c.push_back(AE / AD);
    1268         550 :                 d.push_back(-thisCell.temperature_prev_ts - heat_flux / AD);
    1269             : 
    1270      293700 :             } else if (cell_index == num_cells - 1) {
    1271             :                 // const ground temp bc
    1272             : 
    1273         550 :                 auto &thisCell = Cells[cell_index];
    1274             : 
    1275         550 :                 a.push_back(0);
    1276         550 :                 b.push_back(1);
    1277         550 :                 c.push_back(0);
    1278         550 :                 d.push_back(thisCell.temperature_prev_ts);
    1279             : 
    1280             :             } else {
    1281             :                 // all other cells
    1282             : 
    1283      293150 :                 auto const &westCell = Cells[cell_index - 1];
    1284      293150 :                 auto const &eastCell = Cells[cell_index + 1];
    1285      293150 :                 auto const &thisCell = Cells[cell_index];
    1286             : 
    1287      293150 :                 Real64 FE1 = log(thisCell.radius_outer / thisCell.radius_center) / (2 * Constant::Pi * thisCell.conductivity);
    1288      293150 :                 Real64 FE2 = log(eastCell.radius_center / eastCell.radius_inner) / (2 * Constant::Pi * eastCell.conductivity);
    1289      293150 :                 Real64 AE = 1 / (FE1 + FE2);
    1290             : 
    1291      293150 :                 Real64 FW1 = log(westCell.radius_outer / westCell.radius_center) / (2 * Constant::Pi * westCell.conductivity);
    1292      293150 :                 Real64 FW2 = log(thisCell.radius_center / thisCell.radius_inner) / (2 * Constant::Pi * thisCell.conductivity);
    1293      293150 :                 Real64 AW = -1 / (FW1 + FW2);
    1294             : 
    1295      293150 :                 Real64 AD = thisCell.rhoCp * thisCell.vol / time_step;
    1296             : 
    1297      293150 :                 a.push_back(-AW / AD);
    1298      293150 :                 b.push_back(AW / AD - AE / AD - 1);
    1299      293150 :                 c.push_back(AE / AD);
    1300      293150 :                 d.push_back(-thisCell.temperature_prev_ts);
    1301             :             }
    1302             :         } // end tdma setup
    1303             : 
    1304             :         // solve for new temperatures
    1305        1100 :         std::vector<Real64> new_temps = TDMA(a, b, c, d);
    1306             : 
    1307      294800 :         for (int cell_index = 0; cell_index < num_cells; ++cell_index) {
    1308      294250 :             Cells[cell_index].temperature = new_temps[cell_index];
    1309             :         }
    1310             : 
    1311             :         // calculate bh wall temp
    1312         550 :         Real64 T_bhWall = 0.0;
    1313       19250 :         for (int cell_index = 0; cell_index < num_cells; ++cell_index) {
    1314       19250 :             auto const &leftCell = Cells[cell_index];
    1315       19250 :             auto const &rightCell = Cells[cell_index + 1];
    1316             : 
    1317       19250 :             if (leftCell.type == CellType::GROUT && rightCell.type == CellType::SOIL) {
    1318             : 
    1319         550 :                 Real64 left_conductance = 2 * Constant::Pi * leftCell.conductivity / log(leftCell.radius_outer / leftCell.radius_inner);
    1320         550 :                 Real64 right_conductance = 2 * Constant::Pi * rightCell.conductivity / log(rightCell.radius_center / leftCell.radius_inner);
    1321             : 
    1322         550 :                 T_bhWall =
    1323         550 :                     (left_conductance * leftCell.temperature + right_conductance * rightCell.temperature) / (left_conductance + right_conductance);
    1324         550 :                 break;
    1325             :             }
    1326             :         }
    1327             : 
    1328         550 :         total_time += time_step;
    1329             : 
    1330         550 :         GFNC_shortTimestep.push_back(2 * Constant::Pi * this->soil.k * ((Cells[0].temperature - initial_temperature) / heat_flux - bhResistance));
    1331         550 :         LNTTS_shortTimestep.push_back(log(total_time / t_s));
    1332             : 
    1333         550 :     } // end timestep loop
    1334           2 : }
    1335             : 
    1336             : //******************************************************************************
    1337             : 
    1338         550 : std::vector<Real64> TDMA(std::vector<Real64> a, std::vector<Real64> b, std::vector<Real64> c, std::vector<Real64> d)
    1339             : {
    1340             :     // from: https://en.wikibooks.org/wiki/Algorithm_Implementation/Linear_Algebra/Tridiagonal_matrix_algorithm#C.2B.2B
    1341             : 
    1342         550 :     int n = static_cast<int>(d.size() - 1u);
    1343             : 
    1344         550 :     c[0] /= b[0];
    1345         550 :     d[0] /= b[0];
    1346             : 
    1347      293700 :     for (int i = 1; i < n; ++i) {
    1348      293150 :         c[i] /= b[i] - a[i] * c[i - 1];
    1349      293150 :         d[i] = (d[i] - a[i] * d[i - 1]) / (b[i] - a[i] * c[i - 1]);
    1350             :     }
    1351             : 
    1352         550 :     d[n] = (d[n] - a[n] * d[n - 1]) / (b[n] - a[n] * c[n - 1]);
    1353             : 
    1354      294250 :     for (int i = n; i-- > 0;) {
    1355      293700 :         d[i] -= c[i] * d[i + 1];
    1356             :     }
    1357             : 
    1358         550 :     return d;
    1359             : }
    1360             : 
    1361             : //******************************************************************************
    1362             : 
    1363           2 : void GLHEVert::combineShortAndLongTimestepGFunctions()
    1364             : {
    1365           2 :     std::vector<Real64> GFNC_combined;
    1366           2 :     std::vector<Real64> LNTTS_combined;
    1367             : 
    1368           2 :     Real64 const t_s = pow_2(this->bhLength) / (9.0 * this->soil.diffusivity);
    1369             : 
    1370             :     // Nothing to do. Just put the short time step g-functions on the combined vector
    1371           2 :     int num_shortTimestepGFunctions = GFNC_shortTimestep.size();
    1372         552 :     for (int index_shortTS = 0; index_shortTS < num_shortTimestepGFunctions; ++index_shortTS) {
    1373         550 :         GFNC_combined.push_back(GFNC_shortTimestep[index_shortTS]);
    1374         550 :         LNTTS_combined.push_back(LNTTS_shortTimestep[index_shortTS]);
    1375             :     }
    1376             : 
    1377             :     // Add the rest of the long time-step g-functions to the combined curve
    1378          24 :     for (int index_longTS = this->myRespFactors->GFNC.l(); index_longTS <= this->myRespFactors->GFNC.u(); ++index_longTS) {
    1379          22 :         GFNC_combined.push_back(this->myRespFactors->GFNC(index_longTS));
    1380          22 :         LNTTS_combined.push_back(this->myRespFactors->LNTTS(index_longTS));
    1381             :     }
    1382             : 
    1383             :     // Move combined values into right data struct
    1384           2 :     this->myRespFactors->time.deallocate();
    1385           2 :     this->myRespFactors->LNTTS.deallocate();
    1386           2 :     this->myRespFactors->GFNC.deallocate();
    1387             : 
    1388           2 :     this->myRespFactors->time.dimension(GFNC_combined.size(), 0.0);
    1389           2 :     this->myRespFactors->LNTTS.dimension(GFNC_combined.size(), 0.0);
    1390           2 :     this->myRespFactors->GFNC.dimension(GFNC_combined.size(), 0.0);
    1391             : 
    1392         574 :     for (unsigned int index = 0; index < GFNC_combined.size(); ++index) {
    1393         572 :         this->myRespFactors->time[index] = exp(LNTTS_combined[index]) * t_s;
    1394         572 :         this->myRespFactors->LNTTS[index] = LNTTS_combined[index];
    1395         572 :         this->myRespFactors->GFNC[index] = GFNC_combined[index];
    1396             :     }
    1397           2 : }
    1398             : 
    1399           3 : void GLHEBase::makeThisGLHECacheAndCompareWithFileCache(EnergyPlusData &state)
    1400             : {
    1401           3 :     if (state.files.outputControl.glhe && !state.dataSysVars->DisableGLHECaching) {
    1402           3 :         makeThisGLHECacheStruct();
    1403           3 :         readCacheFileAndCompareWithThisGLHECache(state);
    1404             :     }
    1405           3 : }
    1406             : 
    1407             : //******************************************************************************
    1408             : 
    1409           2 : void GLHEVert::makeThisGLHECacheStruct()
    1410             : {
    1411             :     // For convenience
    1412           2 :     auto &d = myCacheData["Phys Data"];
    1413             : 
    1414           2 :     d["Flow Rate"] = this->designFlow;
    1415           2 :     d["Soil k"] = this->soil.k;
    1416           2 :     d["Soil rhoCp"] = this->soil.rhoCp;
    1417           2 :     d["BH Top Depth"] = this->myRespFactors->props->bhTopDepth;
    1418           2 :     d["BH Length"] = this->myRespFactors->props->bhLength;
    1419           2 :     d["BH Diameter"] = this->myRespFactors->props->bhDiameter;
    1420           2 :     d["Grout k"] = this->myRespFactors->props->grout.k;
    1421           2 :     d["Grout rhoCp"] = this->myRespFactors->props->grout.rhoCp;
    1422           2 :     d["Pipe k"] = this->myRespFactors->props->pipe.k;
    1423           2 :     d["Pipe rhoCP"] = this->myRespFactors->props->pipe.rhoCp;
    1424           2 :     d["Pipe Diameter"] = this->myRespFactors->props->pipe.outDia;
    1425           2 :     d["Pipe Thickness"] = this->myRespFactors->props->pipe.thickness;
    1426           2 :     d["U-tube Dist"] = this->myRespFactors->props->bhUTubeDist;
    1427           2 :     d["Max Simulation Years"] = this->myRespFactors->maxSimYears;
    1428           2 :     d["g-Function Calc Method"] = GroundHeatExchangers::GFuncCalcMethodsStrs[int(this->gFuncCalcMethod)];
    1429             : 
    1430           2 :     auto &d_bh_data = d["BH Data"];
    1431           2 :     int i = 0;
    1432          10 :     for (auto const &thisBH : this->myRespFactors->myBorholes) {
    1433           8 :         ++i;
    1434           8 :         auto &d_bh = d_bh_data[fmt::format("BH {}", i)];
    1435           8 :         d_bh["X-Location"] = thisBH->xLoc;
    1436           8 :         d_bh["Y-Location"] = thisBH->yLoc;
    1437           2 :     }
    1438           2 : }
    1439             : 
    1440             : //******************************************************************************
    1441             : 
    1442           2 : void GLHEVert::readCacheFileAndCompareWithThisGLHECache(EnergyPlusData &state)
    1443             : {
    1444             : 
    1445           2 :     if (!(state.files.outputControl.glhe && FileSystem::fileExists(state.dataStrGlobals->outputGLHEFilePath))) {
    1446             :         // if the file doesn't exist, there are no data to read
    1447           2 :         return;
    1448             :     }
    1449             :     // file exists -- read data and load if possible
    1450             : 
    1451           0 :     nlohmann::json const cached_json = FileSystem::readJSON(state.dataStrGlobals->outputGLHEFilePath);
    1452             : 
    1453           0 :     for (auto const &existing_data : cached_json) {
    1454           0 :         if (myCacheData["Phys Data"] == existing_data["Phys Data"]) {
    1455           0 :             myCacheData["Response Factors"] = existing_data["Response Factors"];
    1456           0 :             gFunctionsExist = true;
    1457           0 :             break;
    1458             :         }
    1459           0 :     }
    1460             : 
    1461           0 :     if (gFunctionsExist) {
    1462             :         // Populate the time array
    1463           0 :         this->myRespFactors->time = Array1D<Real64>(myCacheData["Response Factors"]["time"].get<std::vector<Real64>>());
    1464             : 
    1465             :         // Populate the lntts array
    1466           0 :         this->myRespFactors->LNTTS = Array1D<Real64>(myCacheData["Response Factors"]["LNTTS"].get<std::vector<Real64>>());
    1467             : 
    1468             :         // Populate the g-function array
    1469           0 :         this->myRespFactors->GFNC = Array1D<Real64>(myCacheData["Response Factors"]["GFNC"].get<std::vector<Real64>>());
    1470             :     }
    1471           0 : }
    1472             : 
    1473             : //******************************************************************************
    1474             : 
    1475           2 : void GLHEVert::writeGLHECacheToFile(EnergyPlusData &state) const
    1476             : {
    1477             : 
    1478           2 :     nlohmann::json cached_json;
    1479           2 :     if (FileSystem::fileExists(state.dataStrGlobals->outputGLHEFilePath)) {
    1480             :         // file exists -- add data
    1481             :         // open file
    1482           0 :         cached_json = FileSystem::readJSON(state.dataStrGlobals->outputGLHEFilePath);
    1483             : 
    1484             :         // add current data
    1485           0 :         cached_json.emplace(fmt::format("GHLE {}", cached_json.size() + 1), myCacheData);
    1486             :     } else {
    1487             :         // file doesn't exist -- add data
    1488             :         // add current data
    1489           2 :         cached_json.emplace("GHLE 1", myCacheData);
    1490             :     }
    1491           2 :     FileSystem::writeFile<FileSystem::FileTypes::GLHE>(state.dataStrGlobals->outputGLHEFilePath, cached_json, 2);
    1492           2 : }
    1493             : 
    1494             : //******************************************************************************
    1495             : 
    1496           1 : void GLHESlinky::calcGFunctions(EnergyPlusData &state)
    1497             : {
    1498             :     // SUBROUTINE INFORMATION:
    1499             :     //       AUTHOR:          Matt Mitchell
    1500             :     //       DATE WRITTEN:    February, 2015
    1501             : 
    1502             :     // PURPOSE OF THIS SUBROUTINE:
    1503             :     // calculates g-functions for the slinky ground heat exchanger model
    1504             : 
    1505             :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    1506           1 :     constexpr Real64 tLg_min = -2.0;
    1507           1 :     constexpr Real64 tLg_grid = 0.25;
    1508           1 :     constexpr Real64 ts = 3600.0;
    1509           1 :     constexpr Real64 convertYearsToSeconds = 356.0 * 24.0 * 60.0 * 60.0;
    1510             :     Real64 fraction;
    1511           2 :     Array2D<Real64> valStored({0, this->numTrenches}, {0, this->numCoils}, -1.0);
    1512             :     int I0;
    1513             :     int J0;
    1514             : 
    1515           1 :     DisplayString(state, "Initializing GroundHeatExchanger:Slinky: " + this->name);
    1516             : 
    1517           1 :     this->X0.allocate(this->numCoils);
    1518           1 :     this->Y0.allocate(this->numTrenches);
    1519             : 
    1520             :     // Calculate the number of g-functions required
    1521           1 :     Real64 tLg_max = std::log10(this->maxSimYears * convertYearsToSeconds / ts);
    1522           1 :     int NPairs = static_cast<int>((tLg_max - tLg_min) / (tLg_grid) + 1);
    1523             : 
    1524             :     // Allocate and setup g-function arrays
    1525           1 :     this->myRespFactors->GFNC.allocate(NPairs);
    1526           1 :     this->myRespFactors->LNTTS.allocate(NPairs);
    1527           1 :     this->QnMonthlyAgg.allocate(static_cast<int>(this->maxSimYears * 12));
    1528           1 :     this->QnHr.allocate(730 + this->AGG + this->SubAGG);
    1529           1 :     this->QnSubHr.allocate(static_cast<int>((this->SubAGG + 1) * maxTSinHr + 1));
    1530           1 :     this->LastHourN.allocate(this->SubAGG + 1);
    1531             : 
    1532          29 :     for (int i = 1; i <= NPairs; ++i) {
    1533          28 :         this->myRespFactors->GFNC(i) = 0.0;
    1534          28 :         this->myRespFactors->LNTTS(i) = 0.0;
    1535             :     }
    1536             : 
    1537             :     // Calculate the number of loops (per trench) and number of trenches to be involved
    1538             :     // Due to the symmetry of a slinky GHX field, we need only calculate about
    1539             :     // on quarter of the rings' tube wall temperature perturbation to get the
    1540             :     // mean wall temperature perturbation of the entire slinky GHX field.
    1541           1 :     int numLC = std::ceil(this->numCoils / 2.0);
    1542           1 :     int numRC = std::ceil(this->numTrenches / 2.0);
    1543             : 
    1544             :     // Calculate coordinates (X0, Y0, Z0) of a ring's center
    1545         201 :     for (int coil = 1; coil <= this->numCoils; ++coil) {
    1546         200 :         this->X0(coil) = coilPitch * (coil - 1);
    1547             :     }
    1548          16 :     for (int trench = 1; trench <= this->numTrenches; ++trench) {
    1549          15 :         this->Y0(trench) = (trench - 1) * this->trenchSpacing;
    1550             :     }
    1551           1 :     this->Z0 = this->coilDepth;
    1552             : 
    1553             :     // If number of trenches is greater than 1, one quarter of the rings are involved.
    1554             :     // If number of trenches is 1, one half of the rings are involved.
    1555           1 :     if (this->numTrenches > 1) {
    1556           1 :         fraction = 0.25;
    1557             :     } else {
    1558           0 :         fraction = 0.5;
    1559             :     }
    1560             : 
    1561             :     // Calculate the corresponding time of each temperature response factor
    1562          29 :     for (int NT = 1; NT <= NPairs; ++NT) {
    1563          28 :         Real64 tLg = tLg_min + tLg_grid * (NT - 1);
    1564          28 :         Real64 t = std::pow(10, tLg) * ts;
    1565             : 
    1566             :         // Set the average temperature response of the whole field to zero
    1567          28 :         Real64 gFunc = 0;
    1568             : 
    1569          28 :         valStored = -1.0;
    1570             : 
    1571         252 :         for (int m1 = 1; m1 <= numRC; ++m1) {
    1572       22624 :             for (int n1 = 1; n1 <= numLC; ++n1) {
    1573      358400 :                 for (int m = 1; m <= this->numTrenches; ++m) {
    1574    67536000 :                     for (int n = 1; n <= this->numCoils; ++n) {
    1575             : 
    1576             :                         // Zero out val after each iteration
    1577    67200000 :                         Real64 doubleIntegralVal = 0.0;
    1578    67200000 :                         Real64 midFieldVal = 0.0;
    1579             : 
    1580             :                         // Calculate the distance between ring centers
    1581    67200000 :                         Real64 disRing = distToCenter(m, n, m1, n1);
    1582             : 
    1583             :                         // Save mm1 and nn1
    1584    67200000 :                         int mm1 = std::abs(m - m1);
    1585    67200000 :                         int nn1 = std::abs(n - n1);
    1586             : 
    1587             :                         // If we're calculating a ring's temperature response to itself as a ring source,
    1588             :                         // then we need some extra effort in calculating the double integral
    1589    67200000 :                         if (m1 == m && n1 == n) {
    1590       22400 :                             I0 = 33;
    1591       22400 :                             J0 = 1089;
    1592             :                         } else {
    1593    67177600 :                             I0 = 33;
    1594    67177600 :                             J0 = 561;
    1595             :                         }
    1596             : 
    1597             :                         Real64 gFuncin;
    1598             : 
    1599             :                         // if the ring(n1, m1) is the near-field ring of the ring(n,m)
    1600    67200000 :                         if (disRing <= 2.5 + this->coilDiameter) {
    1601             :                             // if no calculated value has been stored
    1602     1923628 :                             if (valStored(mm1, nn1) < 0) {
    1603         924 :                                 doubleIntegralVal = doubleIntegral(m, n, m1, n1, t, I0, J0);
    1604         924 :                                 valStored(mm1, nn1) = doubleIntegralVal;
    1605             :                                 // else: if a stored value is found for the combination of (m, n, m1, n1)
    1606             :                             } else {
    1607     1922704 :                                 doubleIntegralVal = valStored(mm1, nn1);
    1608             :                             }
    1609             : 
    1610             :                             // due to symmetry, the temperature response of ring(n1, m1) should be 0.25, 0.5, or 1 times its calculated value
    1611     1923628 :                             if (!isEven(this->numTrenches) && !isEven(this->numCoils) && m1 == numRC && n1 == numLC && this->numTrenches > 1.5) {
    1612           0 :                                 gFuncin = 0.25 * doubleIntegralVal;
    1613     1923628 :                             } else if (!isEven(this->numTrenches) && m1 == numRC && this->numTrenches > 1.5) {
    1614      250236 :                                 gFuncin = 0.5 * doubleIntegralVal;
    1615     1673392 :                             } else if (!isEven(this->numCoils) && n1 == numLC) {
    1616           0 :                                 gFuncin = 0.5 * doubleIntegralVal;
    1617             :                             } else {
    1618     1673392 :                                 gFuncin = doubleIntegralVal;
    1619             :                             }
    1620             : 
    1621             :                             // if the ring(n1, m1) is in the far-field or the ring(n,m)
    1622    65276372 :                         } else if (disRing > (10 + this->coilDiameter)) {
    1623    51011604 :                             gFuncin = 0;
    1624             : 
    1625             :                             // else the ring(n1, m1) is in the middle-field of the ring(n,m)
    1626             :                         } else {
    1627             :                             // if no calculated value have been stored
    1628    14264768 :                             if (valStored(mm1, nn1) < 0.0) {
    1629        6664 :                                 midFieldVal = midFieldResponseFunction(m, n, m1, n1, t);
    1630        6664 :                                 valStored(mm1, nn1) = midFieldVal;
    1631             :                                 // if a stored value is found for the combination of (m, n, m1, n1), then
    1632             :                             } else {
    1633    14258104 :                                 midFieldVal = valStored(mm1, nn1);
    1634             :                             }
    1635             : 
    1636             :                             // due to symmetry, the temperature response of ring(n1, m1) should be 0.25, 0.5, or 1 times its calculated value
    1637    14264768 :                             if (!isEven(this->numTrenches) && !isEven(this->numCoils) && m1 == numRC && n1 == numLC && this->numTrenches > 1.5) {
    1638           0 :                                 gFuncin = 0.25 * midFieldVal;
    1639    14264768 :                             } else if (!isEven(this->numTrenches) && m1 == numRC && this->numTrenches > 1.5) {
    1640     2124864 :                                 gFuncin = 0.5 * midFieldVal;
    1641    12139904 :                             } else if (!isEven(this->numCoils) && n1 == numLC) {
    1642           0 :                                 gFuncin = 0.5 * midFieldVal;
    1643             :                             } else {
    1644    12139904 :                                 gFuncin = midFieldVal;
    1645             :                             }
    1646             :                         }
    1647             : 
    1648    67200000 :                         gFunc += gFuncin;
    1649             : 
    1650             :                     } // n
    1651             :                 }     // m
    1652             :             }         // n1
    1653             :         }             // m1
    1654             : 
    1655          28 :         this->myRespFactors->GFNC(NT) = (gFunc * (this->coilDiameter / 2.0)) / (4 * Constant::Pi * fraction * this->numTrenches * this->numCoils);
    1656          28 :         this->myRespFactors->LNTTS(NT) = tLg;
    1657             : 
    1658             :     } // NT time
    1659           1 : }
    1660             : 
    1661             : //******************************************************************************
    1662             : 
    1663           1 : void GLHESlinky::makeThisGLHECacheStruct()
    1664             : {
    1665           1 : }
    1666             : 
    1667             : //******************************************************************************
    1668             : 
    1669           1 : void GLHESlinky::readCacheFileAndCompareWithThisGLHECache([[maybe_unused]] EnergyPlusData &state)
    1670             : {
    1671           1 : }
    1672             : 
    1673             : //******************************************************************************
    1674             : 
    1675             : Real64
    1676    17593884 : GLHESlinky::nearFieldResponseFunction(int const m, int const n, int const m1, int const n1, Real64 const eta, Real64 const theta, Real64 const t)
    1677             : {
    1678             :     // SUBROUTINE INFORMATION:
    1679             :     //       AUTHOR:          Matt Mitchell
    1680             :     //       DATE WRITTEN:    February, 2015
    1681             : 
    1682             :     // PURPOSE OF THIS SUBROUTINE:
    1683             :     // Calculates the temperature response of from one near-field point to another
    1684             : 
    1685             :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    1686    17593884 :     Real64 distance1 = distance(m, n, m1, n1, eta, theta);
    1687    17593884 :     Real64 sqrtAlphaT = std::sqrt(this->soil.diffusivity * t);
    1688             : 
    1689    17593884 :     if (!verticalConfig) {
    1690             : 
    1691           0 :         Real64 sqrtDistDepth = std::sqrt(pow_2(distance1) + 4 * pow_2(this->coilDepth));
    1692           0 :         Real64 errFunc1 = std::erfc(0.5 * distance1 / sqrtAlphaT);
    1693           0 :         Real64 errFunc2 = std::erfc(0.5 * sqrtDistDepth / sqrtAlphaT);
    1694             : 
    1695           0 :         return errFunc1 / distance1 - errFunc2 / sqrtDistDepth;
    1696             : 
    1697             :     } else {
    1698             : 
    1699    17593884 :         Real64 distance2 = distanceToFictRing(m, n, m1, n1, eta, theta);
    1700    17593884 :         Real64 errFunc1 = std::erfc(0.5 * distance1 / sqrtAlphaT);
    1701    17593884 :         Real64 errFunc2 = std::erfc(0.5 * distance2 / sqrtAlphaT);
    1702             : 
    1703    17593884 :         return errFunc1 / distance1 - errFunc2 / distance2;
    1704             :     }
    1705             : }
    1706             : 
    1707             : //******************************************************************************
    1708             : 
    1709        6664 : Real64 GLHESlinky::midFieldResponseFunction(int const m, int const n, int const m1, int const n1, Real64 const t)
    1710             : {
    1711             :     // SUBROUTINE INFORMATION:
    1712             :     //       AUTHOR:          Matt Mitchell
    1713             :     //       DATE WRITTEN:    February, 2015
    1714             : 
    1715             :     // PURPOSE OF THIS SUBROUTINE:
    1716             :     // Calculates the temperature response of from one mid-field point to another
    1717             : 
    1718             :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    1719        6664 :     Real64 sqrtAlphaT = std::sqrt(this->soil.diffusivity * t);
    1720             : 
    1721        6664 :     Real64 distance = distToCenter(m, n, m1, n1);
    1722        6664 :     Real64 sqrtDistDepth = std::sqrt(pow_2(distance) + 4 * pow_2(this->coilDepth));
    1723             : 
    1724        6664 :     Real64 errFunc1 = std::erfc(0.5 * distance / sqrtAlphaT);
    1725        6664 :     Real64 errFunc2 = std::erfc(0.5 * sqrtDistDepth / sqrtAlphaT);
    1726             : 
    1727        6664 :     return 4 * pow_2(Constant::Pi) * (errFunc1 / distance - errFunc2 / sqrtDistDepth);
    1728             : }
    1729             : 
    1730             : //******************************************************************************
    1731             : 
    1732    17593884 : Real64 GLHESlinky::distance(int const m, int const n, int const m1, int const n1, Real64 const eta, Real64 const theta)
    1733             : {
    1734             :     // SUBROUTINE INFORMATION:
    1735             :     //       AUTHOR:          Matt Mitchell
    1736             :     //       DATE WRITTEN:    February, 2015
    1737             : 
    1738             :     // PURPOSE OF THIS SUBROUTINE:
    1739             :     // Calculates the distance between any two points on any two loops
    1740             : 
    1741    17593884 :     Real64 const cos_theta = std::cos(theta);
    1742    17593884 :     Real64 const sin_theta = std::sin(theta);
    1743    17593884 :     Real64 const cos_eta = std::cos(eta);
    1744    17593884 :     Real64 const sin_eta = std::sin(eta);
    1745             : 
    1746    17593884 :     Real64 x = this->X0(n) + cos_theta * (this->coilDiameter / 2.0);
    1747    17593884 :     Real64 y = this->Y0(m) + sin_theta * (this->coilDiameter / 2.0);
    1748             : 
    1749    17593884 :     Real64 xIn = this->X0(n1) + cos_eta * (this->coilDiameter / 2.0 - this->pipe.outRadius);
    1750    17593884 :     Real64 yIn = this->Y0(m1) + sin_eta * (this->coilDiameter / 2.0 - this->pipe.outRadius);
    1751             : 
    1752    17593884 :     Real64 xOut = this->X0(n1) + cos_eta * (this->coilDiameter / 2.0 + this->pipe.outRadius);
    1753    17593884 :     Real64 yOut = this->Y0(m1) + sin_eta * (this->coilDiameter / 2.0 + this->pipe.outRadius);
    1754             : 
    1755    17593884 :     if (!verticalConfig) {
    1756             : 
    1757           0 :         return 0.5 * std::sqrt(pow_2(x - xIn) + pow_2(y - yIn)) + 0.5 * std::sqrt(pow_2(x - xOut) + pow_2(y - yOut));
    1758             : 
    1759             :     } else {
    1760             : 
    1761    17593884 :         Real64 z = this->Z0 + sin_theta * (this->coilDiameter / 2.0);
    1762             : 
    1763    17593884 :         Real64 zIn = this->Z0 + sin_eta * (this->coilDiameter / 2.0 - this->pipe.outRadius);
    1764    17593884 :         Real64 zOut = this->Z0 + sin_eta * (this->coilDiameter / 2.0 + this->pipe.outRadius);
    1765             : 
    1766    17593884 :         return 0.5 * std::sqrt(pow_2(x - xIn) + pow_2(this->Y0(m1) - this->Y0(m)) + pow_2(z - zIn)) +
    1767    17593884 :                0.5 * std::sqrt(pow_2(x - xOut) + pow_2(this->Y0(m1) - this->Y0(m)) + pow_2(z - zOut));
    1768             :     }
    1769             : }
    1770             : 
    1771             : //******************************************************************************
    1772             : 
    1773    17593884 : Real64 GLHESlinky::distanceToFictRing(int const m, int const n, int const m1, int const n1, Real64 const eta, Real64 const theta)
    1774             : {
    1775             :     // SUBROUTINE INFORMATION:
    1776             :     //       AUTHOR:          Matt Mitchell
    1777             :     //       DATE WRITTEN:    February, 2015
    1778             : 
    1779             :     // PURPOSE OF THIS SUBROUTINE:
    1780             :     // Calculates the distance between any two points between real and fictitious rings
    1781             : 
    1782    17593884 :     Real64 const sin_theta = std::sin(theta);
    1783    17593884 :     Real64 const cos_theta = std::cos(theta);
    1784    17593884 :     Real64 const sin_eta = std::sin(eta);
    1785    17593884 :     Real64 const cos_eta = std::cos(eta);
    1786             : 
    1787    17593884 :     Real64 x = this->X0(n) + cos_theta * (this->coilDiameter / 2.0);
    1788             :     // Real64 y = Y0( m ) + sin_theta * (coilDiameter / 2.0);
    1789    17593884 :     Real64 z = this->Z0 + sin_theta * (this->coilDiameter / 2.0) + 2 * this->coilDepth;
    1790             : 
    1791    17593884 :     Real64 xIn = this->X0(n1) + cos_eta * (this->coilDiameter / 2.0 - this->pipe.outRadius);
    1792             :     // Real64 yIn = Y0( m1 ) + sin_eta * (coilDiameter / 2.0 - pipe.outRadius);
    1793    17593884 :     Real64 zIn = this->Z0 + sin_eta * (this->coilDiameter / 2.0 - this->pipe.outRadius);
    1794             : 
    1795    17593884 :     Real64 xOut = this->X0(n1) + cos_eta * (this->coilDiameter / 2.0 + this->pipe.outRadius);
    1796             :     // Real64 yOut = Y0( m1 ) + sin_eta * (coilDiameter / 2.0 + outRadius);
    1797    17593884 :     Real64 zOut = this->Z0 + sin_eta * (this->coilDiameter / 2.0 + this->pipe.outRadius);
    1798             : 
    1799    17593884 :     return 0.5 * std::sqrt(pow_2(x - xIn) + pow_2(this->Y0(m1) - this->Y0(m)) + pow_2(z - zIn)) +
    1800    17593884 :            0.5 * std::sqrt(pow_2(x - xOut) + pow_2(this->Y0(m1) - this->Y0(m)) + pow_2(z - zOut));
    1801             : }
    1802             : 
    1803             : //******************************************************************************
    1804             : 
    1805    67206664 : Real64 GLHESlinky::distToCenter(int const m, int const n, int const m1, int const n1)
    1806             : {
    1807             :     // SUBROUTINE INFORMATION:
    1808             :     //       AUTHOR:          Matt Mitchell
    1809             :     //       DATE WRITTEN:    February, 2015
    1810             : 
    1811             :     // PURPOSE OF THIS SUBROUTINE:
    1812             :     // Calculates the center-to-center distance between rings
    1813             : 
    1814    67206664 :     return std::sqrt(pow_2(this->X0(n) - this->X0(n1)) + pow_2(this->Y0(m) - this->Y0(m1)));
    1815             : }
    1816             : 
    1817             : //******************************************************************************
    1818             : 
    1819    84966236 : inline bool GLHEBase::isEven(int const val)
    1820             : {
    1821             :     // SUBROUTINE INFORMATION:
    1822             :     //       AUTHOR:          Matt Mitchell
    1823             :     //       DATE WRITTEN:    February, 2015
    1824             : 
    1825             :     // PURPOSE OF THIS SUBROUTINE:
    1826             :     // Determines if an integer is even
    1827             : 
    1828    84966236 :     if (val % 2 == 0) {
    1829    41306700 :         return true;
    1830             :     } else {
    1831    43659536 :         return false;
    1832             :     }
    1833             : }
    1834             : 
    1835             : //******************************************************************************
    1836             : 
    1837       30492 : Real64 GLHESlinky::integral(int const m, int const n, int const m1, int const n1, Real64 const t, Real64 const eta, Real64 const J0)
    1838             : {
    1839             :     // SUBROUTINE INFORMATION:
    1840             :     //       AUTHOR:          Matt Mitchell
    1841             :     //       DATE WRITTEN:    February, 2015
    1842             : 
    1843             :     // PURPOSE OF THIS SUBROUTINE:
    1844             :     // Integrates the temperature response at one point based on
    1845             :     // input from other points
    1846             : 
    1847             :     // METHODOLOGY EMPLOYED:
    1848             :     // Simpson's 1/3 rule of integration
    1849             : 
    1850             :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    1851       30492 :     Real64 sumIntF = 0.0;
    1852       30492 :     Real64 theta = 0.0;
    1853       30492 :     constexpr Real64 theta1 = 0.0;
    1854       30492 :     constexpr Real64 theta2 = 2 * Constant::Pi;
    1855       30492 :     Array1D<Real64> f(J0, 0.0);
    1856             : 
    1857       30492 :     Real64 h = (theta2 - theta1) / (J0 - 1);
    1858             : 
    1859             :     // Calculate the function at various equally spaced x values
    1860    17624376 :     for (int j = 1; j <= J0; ++j) {
    1861             : 
    1862    17593884 :         theta = theta1 + (j - 1) * h;
    1863             : 
    1864    17593884 :         f(j) = nearFieldResponseFunction(m, n, m1, n1, eta, theta, t);
    1865             : 
    1866    17593884 :         if (j == 1 || j == J0) {
    1867       60984 :             f(j) = f(j);
    1868    17532900 :         } else if (isEven(j)) {
    1869     8781696 :             f(j) = 4 * f(j);
    1870             :         } else {
    1871     8751204 :             f(j) = 2 * f(j);
    1872             :         }
    1873             : 
    1874    17593884 :         sumIntF += f(j);
    1875             :     }
    1876             : 
    1877       30492 :     return (h / 3) * sumIntF;
    1878       30492 : }
    1879             : 
    1880             : //******************************************************************************
    1881             : 
    1882         924 : Real64 GLHESlinky::doubleIntegral(int const m, int const n, int const m1, int const n1, Real64 const t, int const I0, int const J0)
    1883             : {
    1884             :     // SUBROUTINE INFORMATION:
    1885             :     //       AUTHOR:          Matt Mitchell
    1886             :     //       DATE WRITTEN:    February, 2015
    1887             : 
    1888             :     // PURPOSE OF THIS SUBROUTINE:
    1889             :     // Integrates the temperature response at one point based on
    1890             :     // input from other points
    1891             : 
    1892             :     // METHODOLOGY EMPLOYED:
    1893             :     // Simpson's 1/3 rule of integration
    1894             : 
    1895             :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    1896         924 :     constexpr Real64 eta1 = 0.0;
    1897         924 :     constexpr Real64 eta2 = 2 * Constant::Pi;
    1898             : 
    1899         924 :     Real64 sumIntF = 0.0;
    1900         924 :     Array1D<Real64> g(I0, 0.0);
    1901             : 
    1902         924 :     Real64 h = (eta2 - eta1) / (I0 - 1);
    1903             : 
    1904             :     // Calculates the value of the function at various equally spaced values
    1905       31416 :     for (int i = 1; i <= I0; ++i) {
    1906             : 
    1907       30492 :         Real64 eta = eta1 + (i - 1) * h;
    1908       30492 :         g(i) = integral(m, n, m1, n1, t, eta, J0);
    1909             : 
    1910       30492 :         if (i == 1 || i == I0) {
    1911        1848 :             g(i) = g(i);
    1912       28644 :         } else if (isEven(i)) {
    1913       14784 :             g(i) = 4 * g(i);
    1914             :         } else {
    1915       13860 :             g(i) = 2 * g(i);
    1916             :         }
    1917             : 
    1918       30492 :         sumIntF += g(i);
    1919             :     }
    1920             : 
    1921         924 :     return (h / 3) * sumIntF;
    1922         924 : }
    1923             : 
    1924             : //******************************************************************************
    1925             : 
    1926      359312 : void GLHEVert::getAnnualTimeConstant()
    1927             : {
    1928             :     // SUBROUTINE INFORMATION:
    1929             :     //       AUTHOR:          Matt Mitchell
    1930             :     //       DATE WRITTEN:    February, 2015
    1931             : 
    1932             :     // PURPOSE OF THIS SUBROUTINE:
    1933             :     // calculate annual time constant for ground conduction
    1934             : 
    1935      359312 :     constexpr Real64 hrInYear = 8760;
    1936             : 
    1937      359312 :     this->timeSS = (pow_2(this->bhLength) / (9.0 * this->soil.diffusivity)) / Constant::SecInHour / hrInYear;
    1938      359312 :     this->timeSSFactor = this->timeSS * 8760.0;
    1939      359312 : }
    1940             : 
    1941             : //******************************************************************************
    1942             : 
    1943       14448 : void GLHESlinky::getAnnualTimeConstant()
    1944             : {
    1945             :     // SUBROUTINE INFORMATION:
    1946             :     //       AUTHOR:          Matt Mitchell
    1947             :     //       DATE WRITTEN:    February, 2015
    1948             : 
    1949             :     // PURPOSE OF THIS SUBROUTINE:
    1950             :     // calculate annual time constant for ground conduction
    1951             : 
    1952       14448 :     this->timeSSFactor = 1.0;
    1953       14448 : }
    1954             : 
    1955             : //******************************************************************************
    1956             : 
    1957      373760 : void GLHEBase::calcGroundHeatExchanger(EnergyPlusData &state)
    1958             : {
    1959             :     // SUBROUTINE INFORMATION:
    1960             :     //       AUTHOR:          Dan Fisher
    1961             :     //       DATE WRITTEN:    August, 2000
    1962             :     //       MODIFIED         Arun Murugappan
    1963             : 
    1964             :     // PURPOSE OF THIS SUBROUTINE:
    1965             :     // This is the main routine to simulate the operation of vertical
    1966             :     // closed-loop ground heat exchangers (GLHE).
    1967             : 
    1968             :     // METHODOLOGY EMPLOYED:
    1969             :     // The borehole and fluid temperatures are calculated from the response to
    1970             :     // the current heat transfer rate and the response to the history of past
    1971             :     // applied heat pulses. The response to each pulse is calculated from a non-
    1972             :     // dimensionalized response function, or G-function, that is specific to the
    1973             :     // given borehole field arrangement, depth and spacing. The data defining
    1974             :     // this function is read from input.
    1975             :     // The heat pulse histories need to be recorded over an extended period (months).
    1976             :     // To aid computational efficiency past pulses are continuously aggregated into
    1977             :     // equivalent heat pulses of longer duration, as each pulse becomes less recent.
    1978             : 
    1979             :     // REFERENCES:
    1980             :     // Eskilson, P. 'Thermal Analysis of Heat Extraction Boreholes' Ph.D. Thesis:
    1981             :     //   Dept. of Mathematical Physics, University of Lund, Sweden, June 1987.
    1982             :     // Yavuzturk, C., J.D. Spitler. 1999. 'A Short Time Step Response Factor Model
    1983             :     //   for Vertical Ground Loop Heat Exchangers.' ASHRAE Transactions. 105(2): 475-485.
    1984             : 
    1985             :     // SUBROUTINE ARGUMENT DEFINITIONS
    1986      373760 :     std::string_view const RoutineName = "CalcGroundHeatExchanger";
    1987             : 
    1988             :     // LOCAL PARAMETERS
    1989             :     Real64 fluidAveTemp;
    1990             :     Real64 tmpQnSubHourly; // current Qn sub-hourly value
    1991      373760 :     Real64 sumTotal(0.0);  // sum of all the Qn (load) blocks
    1992             : 
    1993             :     // Calculate G-Functions
    1994      373760 :     if (this->firstTime) {
    1995          24 :         if (!gFunctionsExist) {
    1996           3 :             makeThisGLHECacheAndCompareWithFileCache(state);
    1997           3 :             if (!gFunctionsExist) {
    1998           3 :                 calcGFunctions(state);
    1999           3 :                 gFunctionsExist = true;
    2000             :             }
    2001             :         }
    2002          24 :         this->firstTime = false;
    2003             :     }
    2004             : 
    2005      373760 :     this->inletTemp = state.dataLoopNodes->Node(this->inletNodeNum).Temp;
    2006             : 
    2007      373760 :     Real64 cpFluid = FluidProperties::GetSpecificHeatGlycol(state,
    2008      373760 :                                                             state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidName,
    2009             :                                                             this->inletTemp,
    2010      373760 :                                                             state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidIndex,
    2011             :                                                             RoutineName);
    2012             : 
    2013      373760 :     Real64 kGroundFactor = 2.0 * Constant::Pi * this->soil.k;
    2014             : 
    2015             :     // Get time constants
    2016      373760 :     getAnnualTimeConstant();
    2017             : 
    2018      373760 :     if (triggerDesignDayReset && state.dataGlobal->WarmupFlag) updateCurSimTime = true;
    2019      373760 :     if (state.dataGlobal->DayOfSim == 1 && updateCurSimTime) {
    2020          94 :         state.dataGroundHeatExchanger->currentSimTime = 0.0;
    2021          94 :         state.dataGroundHeatExchanger->prevTimeSteps = 0.0;
    2022          94 :         this->QnHr = 0.0;
    2023          94 :         this->QnMonthlyAgg = 0.0;
    2024          94 :         this->QnSubHr = 0.0;
    2025          94 :         this->LastHourN = 1;
    2026          94 :         state.dataGroundHeatExchanger->N = 1;
    2027          94 :         updateCurSimTime = false;
    2028          94 :         triggerDesignDayReset = false;
    2029             :     }
    2030             : 
    2031      373760 :     state.dataGroundHeatExchanger->currentSimTime = (state.dataGlobal->DayOfSim - 1) * 24 + state.dataGlobal->HourOfDay - 1 +
    2032      373760 :                                                     (state.dataGlobal->TimeStep - 1) * state.dataGlobal->TimeStepZone +
    2033      373760 :                                                     state.dataHVACGlobal->SysTimeElapsed; //+ TimeStepsys
    2034      373760 :     state.dataGroundHeatExchanger->locHourOfDay = static_cast<int>(mod(state.dataGroundHeatExchanger->currentSimTime, Constant::HoursInDay) + 1);
    2035      373760 :     state.dataGroundHeatExchanger->locDayOfSim = static_cast<int>(state.dataGroundHeatExchanger->currentSimTime / 24 + 1);
    2036             : 
    2037      373760 :     if (state.dataGlobal->DayOfSim > 1) {
    2038      262248 :         updateCurSimTime = true;
    2039             :     }
    2040             : 
    2041      373760 :     if (!state.dataGlobal->WarmupFlag) {
    2042       52504 :         triggerDesignDayReset = true;
    2043             :     }
    2044             : 
    2045      373760 :     if (state.dataGroundHeatExchanger->currentSimTime <= 0.0) {
    2046         952 :         state.dataGroundHeatExchanger->prevTimeSteps = 0.0; // This resets history when rounding 24:00 hours during warmup avoids hard crash later
    2047         952 :         calcAggregateLoad(state);                           // Just allocates and initializes prevHour array
    2048         952 :         return;
    2049             :     }
    2050             : 
    2051             :     // Store currentSimTime in prevTimeSteps only if a time step occurs
    2052      372808 :     if (state.dataGroundHeatExchanger->prevTimeSteps(1) != state.dataGroundHeatExchanger->currentSimTime) {
    2053       42968 :         state.dataGroundHeatExchanger->prevTimeSteps =
    2054       85936 :             eoshift(state.dataGroundHeatExchanger->prevTimeSteps, -1, state.dataGroundHeatExchanger->currentSimTime);
    2055       42968 :         ++state.dataGroundHeatExchanger->N;
    2056             :     }
    2057             : 
    2058      372808 :     if (state.dataGroundHeatExchanger->N != PrevN) {
    2059       42968 :         PrevN = state.dataGroundHeatExchanger->N;
    2060       42968 :         this->QnSubHr = eoshift(this->QnSubHr, -1, this->lastQnSubHr);
    2061             :     }
    2062             : 
    2063      372808 :     calcAggregateLoad(state);
    2064             : 
    2065             :     // Update the heat exchanger resistance each time
    2066      372808 :     this->HXResistance = calcHXResistance(state);
    2067             : 
    2068      372808 :     if (state.dataGroundHeatExchanger->N == 1) {
    2069           0 :         if (this->massFlowRate <= 0.0) {
    2070           0 :             tmpQnSubHourly = 0.0;
    2071           0 :             fluidAveTemp = this->tempGround;
    2072           0 :             this->ToutNew = this->inletTemp;
    2073             :         } else {
    2074           0 :             Real64 gFuncVal = getGFunc(state.dataGroundHeatExchanger->currentSimTime / (this->timeSSFactor));
    2075             : 
    2076           0 :             Real64 C_1 = (this->totalTubeLength) / (2.0 * this->massFlowRate * cpFluid);
    2077           0 :             tmpQnSubHourly = (this->tempGround - this->inletTemp) / (gFuncVal / (kGroundFactor) + this->HXResistance + C_1);
    2078           0 :             fluidAveTemp = this->tempGround - tmpQnSubHourly * this->HXResistance;
    2079           0 :             this->ToutNew = this->tempGround - tmpQnSubHourly * (gFuncVal / (kGroundFactor) + this->HXResistance - C_1);
    2080             :         }
    2081             :     } else {
    2082             :         // no monthly super position
    2083      372808 :         if (state.dataGroundHeatExchanger->currentSimTime < (hrsPerMonth + this->AGG + this->SubAGG)) {
    2084             : 
    2085             :             // Calculate the Sub Hourly Superposition
    2086             : 
    2087             :             // same as above for sub-hourly( with no aggregation]
    2088      372808 :             Real64 sumQnSubHourly = 0.0;
    2089             :             int IndexN;
    2090      372808 :             if (int(state.dataGroundHeatExchanger->currentSimTime) < this->SubAGG) {
    2091       68632 :                 IndexN = int(state.dataGroundHeatExchanger->currentSimTime) + 1;
    2092             :             } else {
    2093      304176 :                 IndexN = this->SubAGG + 1;
    2094             :             }
    2095             : 
    2096      372808 :             int subHourlyLimit = state.dataGroundHeatExchanger->N - this->LastHourN(IndexN); // Check this when running simulation
    2097    31058808 :             for (int I = 1; I <= subHourlyLimit; ++I) {
    2098    31058440 :                 if (I == subHourlyLimit) {
    2099      372440 :                     if (int(state.dataGroundHeatExchanger->currentSimTime) >= this->SubAGG) {
    2100             :                         Real64 gFuncVal =
    2101      304176 :                             getGFunc((state.dataGroundHeatExchanger->currentSimTime - state.dataGroundHeatExchanger->prevTimeSteps(I + 1)) /
    2102      304176 :                                      (this->timeSSFactor));
    2103      304176 :                         Real64 RQSubHr = gFuncVal / (kGroundFactor);
    2104      304176 :                         sumQnSubHourly += (this->QnSubHr(I) - this->QnHr(IndexN)) * RQSubHr;
    2105             :                     } else {
    2106             :                         Real64 gFuncVal =
    2107       68264 :                             getGFunc((state.dataGroundHeatExchanger->currentSimTime - state.dataGroundHeatExchanger->prevTimeSteps(I + 1)) /
    2108       68264 :                                      (this->timeSSFactor));
    2109       68264 :                         Real64 RQSubHr = gFuncVal / (kGroundFactor);
    2110       68264 :                         sumQnSubHourly += this->QnSubHr(I) * RQSubHr;
    2111             :                     }
    2112      372440 :                     break;
    2113             :                 }
    2114             :                 // prevTimeSteps(I+1) This is "I+1" because prevTimeSteps(1) = CurrentTimestep
    2115    30686000 :                 Real64 gFuncVal = getGFunc((state.dataGroundHeatExchanger->currentSimTime - state.dataGroundHeatExchanger->prevTimeSteps(I + 1)) /
    2116    30686000 :                                            (this->timeSSFactor));
    2117    30686000 :                 Real64 RQSubHr = gFuncVal / (kGroundFactor);
    2118    30686000 :                 sumQnSubHourly += (this->QnSubHr(I) - this->QnSubHr(I + 1)) * RQSubHr;
    2119             :             }
    2120             : 
    2121             :             // Calculate the Hourly Superposition
    2122             :             // same as above for hourly
    2123      372808 :             Real64 sumQnHourly = 0.0;
    2124             : 
    2125      372808 :             int hourlyLimit = int(state.dataGroundHeatExchanger->currentSimTime);
    2126    18321208 :             for (int I = this->SubAGG + 1; I <= hourlyLimit; ++I) {
    2127    18249152 :                 if (I == hourlyLimit) {
    2128      300752 :                     Real64 gFuncVal = getGFunc(state.dataGroundHeatExchanger->currentSimTime / (this->timeSSFactor));
    2129      300752 :                     Real64 RQHour = gFuncVal / (kGroundFactor);
    2130      300752 :                     sumQnHourly += this->QnHr(I) * RQHour;
    2131      300752 :                     break;
    2132             :                 }
    2133    17948400 :                 Real64 gFuncVal = getGFunc((state.dataGroundHeatExchanger->currentSimTime - int(state.dataGroundHeatExchanger->currentSimTime) + I) /
    2134    17948400 :                                            (this->timeSSFactor));
    2135    17948400 :                 Real64 RQHour = gFuncVal / (kGroundFactor);
    2136    17948400 :                 sumQnHourly += (this->QnHr(I) - this->QnHr(I + 1)) * RQHour;
    2137             :             }
    2138             : 
    2139             :             // Find the total Sum of the Temperature difference due to all load blocks
    2140      372808 :             sumTotal = sumQnSubHourly + sumQnHourly;
    2141             : 
    2142             :             // Calculate the sub-hourly temperature due the Last Time steps Load
    2143             :             Real64 gFuncVal =
    2144      372808 :                 getGFunc((state.dataGroundHeatExchanger->currentSimTime - state.dataGroundHeatExchanger->prevTimeSteps(2)) / (this->timeSSFactor));
    2145      372808 :             Real64 RQSubHr = gFuncVal / kGroundFactor;
    2146             : 
    2147      372808 :             if (this->massFlowRate <= 0.0) {
    2148       59730 :                 tmpQnSubHourly = 0.0;
    2149       59730 :                 fluidAveTemp = this->tempGround - sumTotal; // Q(N)*RB = 0
    2150       59730 :                 this->ToutNew = this->inletTemp;
    2151             :             } else {
    2152             :                 // Dr.Sitler's Explicit set of equations to calculate the New Outlet Temperature of the U-Tube
    2153      313078 :                 Real64 C0 = RQSubHr;
    2154      313078 :                 Real64 C1 = this->tempGround - (sumTotal - this->QnSubHr(1) * RQSubHr);
    2155      313078 :                 Real64 C2 = this->totalTubeLength / (2.0 * this->massFlowRate * cpFluid);
    2156      313078 :                 Real64 C3 = this->massFlowRate * cpFluid / (this->totalTubeLength);
    2157      313078 :                 tmpQnSubHourly = (C1 - this->inletTemp) / (this->HXResistance + C0 - C2 + (1 / C3));
    2158      313078 :                 fluidAveTemp = C1 - (C0 + this->HXResistance) * tmpQnSubHourly;
    2159      313078 :                 this->ToutNew = C1 + (C2 - C0 - this->HXResistance) * tmpQnSubHourly;
    2160             :             }
    2161             : 
    2162             :         } else { // Monthly Aggregation and super position
    2163             : 
    2164             :             // the number of months of simulation elapsed
    2165           0 :             int numOfMonths = static_cast<int>((state.dataGroundHeatExchanger->currentSimTime + 1) / hrsPerMonth);
    2166             : 
    2167             :             // The Month up to which the monthly blocks are superposed
    2168             :             int currentMonth;
    2169             : 
    2170           0 :             if (state.dataGroundHeatExchanger->currentSimTime < ((numOfMonths)*hrsPerMonth) + this->AGG + this->SubAGG) {
    2171           0 :                 currentMonth = numOfMonths - 1;
    2172             :             } else {
    2173           0 :                 currentMonth = numOfMonths;
    2174             :             }
    2175             : 
    2176             :             // Monthly superposition
    2177             :             // tmp variable which holds the sum of the Temperature difference due to Aggregated heat extraction/rejection step
    2178           0 :             Real64 sumQnMonthly = 0.0;
    2179             : 
    2180           0 :             for (int I = 1; I <= currentMonth; ++I) {
    2181           0 :                 if (I == 1) {
    2182           0 :                     Real64 gFuncVal = getGFunc(state.dataGroundHeatExchanger->currentSimTime / (this->timeSSFactor));
    2183           0 :                     Real64 RQMonth = gFuncVal / (kGroundFactor);
    2184           0 :                     sumQnMonthly += this->QnMonthlyAgg(I) * RQMonth;
    2185           0 :                     continue;
    2186           0 :                 }
    2187           0 :                 Real64 gFuncVal = getGFunc((state.dataGroundHeatExchanger->currentSimTime - (I - 1) * hrsPerMonth) / (this->timeSSFactor));
    2188           0 :                 Real64 RQMonth = gFuncVal / (kGroundFactor);
    2189           0 :                 sumQnMonthly += (this->QnMonthlyAgg(I) - this->QnMonthlyAgg(I - 1)) * RQMonth;
    2190             :             }
    2191             : 
    2192             :             // Hourly Superposition
    2193           0 :             Real64 sumQnHourly = 0.0;
    2194           0 :             int hourlyLimit = int(state.dataGroundHeatExchanger->currentSimTime - currentMonth * hrsPerMonth);
    2195           0 :             for (int I = 1 + this->SubAGG; I <= hourlyLimit; ++I) {
    2196           0 :                 if (I == hourlyLimit) {
    2197             :                     Real64 gFuncVal =
    2198           0 :                         getGFunc((state.dataGroundHeatExchanger->currentSimTime - int(state.dataGroundHeatExchanger->currentSimTime) + I) /
    2199           0 :                                  (this->timeSSFactor));
    2200           0 :                     Real64 RQHour = gFuncVal / (kGroundFactor);
    2201           0 :                     sumQnHourly += (this->QnHr(I) - this->QnMonthlyAgg(currentMonth)) * RQHour;
    2202           0 :                     break;
    2203             :                 }
    2204           0 :                 Real64 gFuncVal = getGFunc((state.dataGroundHeatExchanger->currentSimTime - int(state.dataGroundHeatExchanger->currentSimTime) + I) /
    2205           0 :                                            (this->timeSSFactor));
    2206           0 :                 Real64 RQHour = gFuncVal / (kGroundFactor);
    2207           0 :                 sumQnHourly += (this->QnHr(I) - this->QnHr(I + 1)) * RQHour;
    2208             :             }
    2209             : 
    2210             :             // sub-hourly Superposition
    2211           0 :             int subHourlyLimit = state.dataGroundHeatExchanger->N - this->LastHourN(this->SubAGG + 1);
    2212           0 :             Real64 sumQnSubHourly = 0.0;
    2213           0 :             for (int I = 1; I <= subHourlyLimit; ++I) {
    2214           0 :                 if (I == subHourlyLimit) {
    2215           0 :                     Real64 gFuncVal = getGFunc((state.dataGroundHeatExchanger->currentSimTime - state.dataGroundHeatExchanger->prevTimeSteps(I + 1)) /
    2216           0 :                                                (this->timeSSFactor));
    2217           0 :                     Real64 RQSubHr = gFuncVal / (kGroundFactor);
    2218           0 :                     sumQnSubHourly += (this->QnSubHr(I) - this->QnHr(this->SubAGG + 1)) * RQSubHr;
    2219           0 :                     break;
    2220             :                 }
    2221           0 :                 Real64 gFuncVal = getGFunc((state.dataGroundHeatExchanger->currentSimTime - state.dataGroundHeatExchanger->prevTimeSteps(I + 1)) /
    2222           0 :                                            (this->timeSSFactor));
    2223           0 :                 Real64 RQSubHr = gFuncVal / (kGroundFactor);
    2224           0 :                 sumQnSubHourly += (this->QnSubHr(I) - this->QnSubHr(I + 1)) * RQSubHr;
    2225             :             }
    2226             : 
    2227           0 :             sumTotal = sumQnMonthly + sumQnHourly + sumQnSubHourly;
    2228             : 
    2229             :             // Calculate the sub-hourly temperature due the Last Time steps Load
    2230             :             Real64 gFuncVal =
    2231           0 :                 getGFunc((state.dataGroundHeatExchanger->currentSimTime - state.dataGroundHeatExchanger->prevTimeSteps(2)) / (this->timeSSFactor));
    2232           0 :             Real64 RQSubHr = gFuncVal / (kGroundFactor);
    2233             : 
    2234           0 :             if (this->massFlowRate <= 0.0) {
    2235           0 :                 tmpQnSubHourly = 0.0;
    2236           0 :                 fluidAveTemp = this->tempGround - sumTotal; // Q(N)*RB = 0
    2237           0 :                 this->ToutNew = this->inletTemp;
    2238             :             } else {
    2239             :                 // Explicit set of equations to calculate the New Outlet Temperature of the U-Tube
    2240           0 :                 Real64 C0 = RQSubHr;
    2241           0 :                 Real64 C1 = this->tempGround - (sumTotal - this->QnSubHr(1) * RQSubHr);
    2242           0 :                 Real64 C2 = this->totalTubeLength / (2 * this->massFlowRate * cpFluid);
    2243           0 :                 Real64 C3 = this->massFlowRate * cpFluid / (this->totalTubeLength);
    2244           0 :                 tmpQnSubHourly = (C1 - this->inletTemp) / (this->HXResistance + C0 - C2 + (1 / C3));
    2245           0 :                 fluidAveTemp = C1 - (C0 + this->HXResistance) * tmpQnSubHourly;
    2246           0 :                 this->ToutNew = C1 + (C2 - C0 - this->HXResistance) * tmpQnSubHourly;
    2247             :             }
    2248             :         } //  end of AGG OR NO AGG
    2249             :     }     // end of N  = 1 branch
    2250      372808 :     this->bhTemp = this->tempGround - sumTotal;
    2251             : 
    2252             :     // Load the QnSubHourly Array with a new value at end of every timestep
    2253      372808 :     this->lastQnSubHr = tmpQnSubHourly;
    2254      372808 :     this->outletTemp = this->ToutNew;
    2255      372808 :     this->QGLHE = tmpQnSubHourly * this->totalTubeLength;
    2256      372808 :     this->aveFluidTemp = fluidAveTemp;
    2257             : }
    2258             : 
    2259             : //******************************************************************************
    2260             : 
    2261      373760 : void GLHEBase::updateGHX(EnergyPlusData &state)
    2262             : {
    2263             :     // SUBROUTINE INFORMATION:
    2264             :     //       AUTHOR:          Matt Mitchell
    2265             :     //       DATE WRITTEN:    February, 2015
    2266             : 
    2267             :     // PURPOSE OF THIS SUBROUTINE:
    2268             :     // Updates the outlet node and check for out of bounds temperatures
    2269             : 
    2270             :     // SUBROUTINE ARGUMENT DEFINITIONS
    2271      373760 :     std::string_view const RoutineName = "UpdateGroundHeatExchanger";
    2272      373760 :     constexpr Real64 deltaTempLimit = 100.0; // temp limit for warnings
    2273             : 
    2274      373760 :     PlantUtilities::SafeCopyPlantNode(state, this->inletNodeNum, this->outletNodeNum);
    2275             : 
    2276      373760 :     state.dataLoopNodes->Node(this->outletNodeNum).Temp = this->outletTemp;
    2277      373760 :     state.dataLoopNodes->Node(this->outletNodeNum).Enthalpy =
    2278      373760 :         this->outletTemp * FluidProperties::GetSpecificHeatGlycol(state,
    2279      373760 :                                                                   state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidName,
    2280             :                                                                   this->outletTemp,
    2281      373760 :                                                                   state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidIndex,
    2282             :                                                                   RoutineName);
    2283             : 
    2284      373760 :     Real64 GLHEdeltaTemp = std::abs(this->outletTemp - this->inletTemp);
    2285             : 
    2286      373760 :     if (GLHEdeltaTemp > deltaTempLimit && this->numErrorCalls < state.dataGroundHeatExchanger->numVerticalGLHEs && !state.dataGlobal->WarmupFlag) {
    2287           0 :         Real64 fluidDensity = FluidProperties::GetDensityGlycol(state,
    2288           0 :                                                                 state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidName,
    2289             :                                                                 this->inletTemp,
    2290           0 :                                                                 state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidIndex,
    2291             :                                                                 RoutineName);
    2292           0 :         this->designMassFlow = this->designFlow * fluidDensity;
    2293           0 :         ShowWarningError(state, "Check GLHE design inputs & g-functions for consistency");
    2294           0 :         ShowContinueError(state, format("For GroundHeatExchanger: {}GLHE delta Temp > 100C.", this->name));
    2295           0 :         ShowContinueError(state, "This can be encountered in cases where the GLHE mass flow rate is either significantly");
    2296           0 :         ShowContinueError(state, " lower than the design value, or cases where the mass flow rate rapidly changes.");
    2297           0 :         ShowContinueError(state, format("GLHE Current Flow Rate={:.3T}; GLHE Design Flow Rate={:.3T}", this->massFlowRate, this->designMassFlow));
    2298           0 :         ++this->numErrorCalls;
    2299             :     }
    2300      373760 : }
    2301             : 
    2302             : //******************************************************************************
    2303             : 
    2304      373760 : void GLHEBase::calcAggregateLoad(EnergyPlusData &state)
    2305             : {
    2306             : 
    2307             :     // SUBROUTINE INFORMATION:
    2308             :     //       AUTHOR:          Arun Murugappan
    2309             :     //       DATE WRITTEN:    August, 2000
    2310             : 
    2311             :     // PURPOSE OF THIS SUBROUTINE:
    2312             :     // Manages the heat transfer history.
    2313             : 
    2314             :     // METHODOLOGY EMPLOYED:
    2315             :     // The heat pulse histories need to be recorded over an extended period (months).
    2316             :     // To aid computational efficiency past pulses are continuously aggregated into
    2317             :     // equivalent heat pulses of longer duration, as each pulse becomes less recent.
    2318             :     // Past sub-hourly loads are re-aggregated into equivalent hourly and monthly loads.
    2319             : 
    2320             :     // REFERENCES:
    2321             :     // Eskilson, P. 'Thermal Analysis of Heat Extraction Boreholes' Ph.D. Thesis:
    2322             :     //   Dept. of Mathematical Physics, University of Lund, Sweden, June 1987.
    2323             :     // Yavuzturk, C., J.D. Spitler. 1999. 'A Short Time Step Response Factor Model
    2324             :     //   for Vertical Ground Loop Heat Exchangers. ASHRAE Transactions. 105(2): 475-485.
    2325             : 
    2326      373760 :     if (state.dataGroundHeatExchanger->currentSimTime <= 0.0) return;
    2327             : 
    2328             :     // FOR EVERY HOUR UPDATE THE HOURLY QN this->QnHr(J)
    2329             :     // THIS IS DONE BY AGGREGATING THE sub-hourly QN FROM THE PREVIOUS HOUR TO UNTIL THE CURRENT HOUR
    2330             :     // AND STORING IT IN  verticalGLHE(GLHENum)%QnHr(J)
    2331             : 
    2332             :     // sub-hourly Qn IS NOT AGGREGATED . IT IS THE BASIC LOAD
    2333      372808 :     if (this->prevHour != state.dataGroundHeatExchanger->locHourOfDay) {
    2334        7798 :         Real64 SumQnHr = 0.0;
    2335             :         int J;
    2336       50438 :         for (J = 1; J <= (state.dataGroundHeatExchanger->N - this->LastHourN(1)); ++J) {
    2337       42640 :             SumQnHr +=
    2338       42640 :                 this->QnSubHr(J) * std::abs(state.dataGroundHeatExchanger->prevTimeSteps(J) - state.dataGroundHeatExchanger->prevTimeSteps(J + 1));
    2339             :         }
    2340        7798 :         if (state.dataGroundHeatExchanger->prevTimeSteps(1) != state.dataGroundHeatExchanger->prevTimeSteps(J)) {
    2341        7798 :             SumQnHr /= std::abs(state.dataGroundHeatExchanger->prevTimeSteps(1) - state.dataGroundHeatExchanger->prevTimeSteps(J));
    2342             :         } else {
    2343           0 :             SumQnHr /= 0.05; // estimated small timestep
    2344             :         }
    2345        7798 :         this->QnHr = eoshift(this->QnHr, -1, SumQnHr);
    2346        7798 :         this->LastHourN = eoshift(this->LastHourN, -1, state.dataGroundHeatExchanger->N);
    2347             :     }
    2348             : 
    2349             :     // CHECK IF A MONTH PASSES...
    2350      372808 :     if (mod(((state.dataGroundHeatExchanger->locDayOfSim - 1) * Constant::HoursInDay + (state.dataGroundHeatExchanger->locHourOfDay)), hrsPerMonth) ==
    2351      372808 :             0 &&
    2352           0 :         this->prevHour != state.dataGroundHeatExchanger->locHourOfDay) {
    2353           0 :         Real64 MonthNum = static_cast<int>(
    2354           0 :             (state.dataGroundHeatExchanger->locDayOfSim * Constant::HoursInDay + state.dataGroundHeatExchanger->locHourOfDay) / hrsPerMonth);
    2355           0 :         Real64 SumQnMonth = 0.0;
    2356           0 :         for (int J = 1; J <= int(hrsPerMonth); ++J) {
    2357           0 :             SumQnMonth += this->QnHr(J);
    2358             :         }
    2359           0 :         SumQnMonth /= hrsPerMonth;
    2360           0 :         this->QnMonthlyAgg(MonthNum) = SumQnMonth;
    2361             :     }
    2362      372808 :     this->prevHour = state.dataGroundHeatExchanger->locHourOfDay;
    2363             : }
    2364             : 
    2365             : //******************************************************************************
    2366             : 
    2367          24 : void GetGroundHeatExchangerInput(EnergyPlusData &state)
    2368             : {
    2369             :     // SUBROUTINE INFORMATION:
    2370             :     //       AUTHOR:          Dan Fisher
    2371             :     //       DATE WRITTEN:    August, 2000
    2372             :     //       MODIFIED         Arun Murugappan
    2373             : 
    2374             :     // GET NUMBER OF ALL EQUIPMENT TYPES
    2375          48 :     state.dataGroundHeatExchanger->numVerticalGLHEs =
    2376          24 :         state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "GroundHeatExchanger:System");
    2377          48 :     state.dataGroundHeatExchanger->numSlinkyGLHEs =
    2378          24 :         state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "GroundHeatExchanger:Slinky");
    2379          48 :     state.dataGroundHeatExchanger->numVertArray =
    2380          24 :         state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "GroundHeatExchanger:Vertical:Array");
    2381          48 :     state.dataGroundHeatExchanger->numVertProps =
    2382          24 :         state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "GroundHeatExchanger:Vertical:Properties");
    2383          48 :     state.dataGroundHeatExchanger->numResponseFactors =
    2384          24 :         state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "GroundHeatExchanger:ResponseFactors");
    2385          48 :     state.dataGroundHeatExchanger->numSingleBorehole =
    2386          24 :         state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "GroundHeatExchanger:Vertical:Single");
    2387             : 
    2388          24 :     if (state.dataGroundHeatExchanger->numVerticalGLHEs <= 0 && state.dataGroundHeatExchanger->numSlinkyGLHEs <= 0) {
    2389           0 :         ShowSevereError(state, "Error processing inputs for GLHE objects");
    2390           0 :         ShowContinueError(state, "Simulation indicated these objects were found, but input processor doesn't find any");
    2391           0 :         ShowContinueError(state, "Check inputs for GroundHeatExchanger:System and GroundHeatExchanger:Slinky");
    2392           0 :         ShowContinueError(state, "Also check plant/branch inputs for references to invalid/deleted objects");
    2393             :     }
    2394             : 
    2395          24 :     if (state.dataGroundHeatExchanger->numVertProps > 0) {
    2396             : 
    2397          23 :         std::string const currObj = "GroundHeatExchanger:Vertical:Properties";
    2398             : 
    2399          23 :         auto const instances = state.dataInputProcessing->inputProcessor->epJSON.find(currObj);
    2400          23 :         if (instances == state.dataInputProcessing->inputProcessor->epJSON.end()) {
    2401             :             ShowSevereError(state, format("{}: Somehow getNumObjectsFound was > 0 but epJSON.find found 0", currObj)); // LCOV_EXCL_LINE
    2402             :         }
    2403             : 
    2404          23 :         auto &instancesValue = instances.value();
    2405          46 :         for (auto it = instancesValue.begin(); it != instancesValue.end(); ++it) {
    2406          23 :             auto const &instance = it.value();
    2407          23 :             std::string const &objName = it.key();
    2408          23 :             std::string const &objNameUC = Util::makeUPPER(objName);
    2409          23 :             state.dataInputProcessing->inputProcessor->markObjectAsUsed(currObj, objName);
    2410          23 :             std::shared_ptr<GLHEVertProps> thisObj(new GLHEVertProps(state, objNameUC, instance));
    2411          23 :             state.dataGroundHeatExchanger->vertPropsVector.push_back(thisObj);
    2412          46 :         }
    2413          23 :     }
    2414             : 
    2415          24 :     if (state.dataGroundHeatExchanger->numResponseFactors > 0) {
    2416             : 
    2417          21 :         std::string const currObj = "GroundHeatExchanger:ResponseFactors";
    2418             : 
    2419          21 :         auto const instances = state.dataInputProcessing->inputProcessor->epJSON.find(currObj);
    2420          21 :         if (instances == state.dataInputProcessing->inputProcessor->epJSON.end()) {
    2421             :             ShowSevereError(state, format("{}: Somehow getNumObjectsFound was > 0 but epJSON.find found 0", currObj)); // LCOV_EXCL_LINE
    2422             :         }
    2423             : 
    2424          21 :         auto &instancesValue = instances.value();
    2425          42 :         for (auto it = instancesValue.begin(); it != instancesValue.end(); ++it) {
    2426          21 :             auto const &instance = it.value();
    2427          21 :             std::string const &objName = it.key();
    2428          21 :             std::string const &objNameUC = Util::makeUPPER(objName);
    2429          21 :             state.dataInputProcessing->inputProcessor->markObjectAsUsed(currObj, objName);
    2430          21 :             std::shared_ptr<GLHEResponseFactors> thisObj(new GLHEResponseFactors(state, objNameUC, instance));
    2431          21 :             state.dataGroundHeatExchanger->responseFactorsVector.push_back(thisObj);
    2432          42 :         }
    2433          21 :     }
    2434             : 
    2435          24 :     if (state.dataGroundHeatExchanger->numVertArray > 0) {
    2436             : 
    2437           2 :         std::string const currObj = "GroundHeatExchanger:Vertical:Array";
    2438             : 
    2439           2 :         auto const instances = state.dataInputProcessing->inputProcessor->epJSON.find(currObj);
    2440           2 :         if (instances == state.dataInputProcessing->inputProcessor->epJSON.end()) {
    2441             :             ShowSevereError(state, format("{}: Somehow getNumObjectsFound was > 0 but epJSON.find found 0", currObj)); // LCOV_EXCL_LINE
    2442             :         }
    2443             : 
    2444           2 :         auto &instancesValue = instances.value();
    2445           4 :         for (auto it = instancesValue.begin(); it != instancesValue.end(); ++it) {
    2446           2 :             auto const &instance = it.value();
    2447           2 :             std::string const &objName = it.key();
    2448           2 :             std::string const &objNameUC = Util::makeUPPER(objName);
    2449           2 :             state.dataInputProcessing->inputProcessor->markObjectAsUsed(currObj, objName);
    2450           2 :             std::shared_ptr<GLHEVertArray> thisObj(new GLHEVertArray(state, objNameUC, instance));
    2451           2 :             state.dataGroundHeatExchanger->vertArraysVector.push_back(thisObj);
    2452           4 :         }
    2453           2 :     }
    2454             : 
    2455          24 :     if (state.dataGroundHeatExchanger->numSingleBorehole > 0) {
    2456             : 
    2457           0 :         std::string const currObj = "GroundHeatExchanger:Vertical:Single";
    2458             : 
    2459           0 :         auto const instances = state.dataInputProcessing->inputProcessor->epJSON.find(currObj);
    2460           0 :         if (instances == state.dataInputProcessing->inputProcessor->epJSON.end()) {
    2461             :             ShowSevereError(state, format("{}: Somehow getNumObjectsFound was > 0 but epJSON.find found 0", currObj)); // LCOV_EXCL_LINE
    2462             :         }
    2463             : 
    2464           0 :         auto &instancesValue = instances.value();
    2465           0 :         for (auto it = instancesValue.begin(); it != instancesValue.end(); ++it) {
    2466           0 :             auto const &instance = it.value();
    2467           0 :             std::string const &objName = it.key();
    2468           0 :             std::string const &objNameUC = Util::makeUPPER(objName);
    2469           0 :             state.dataInputProcessing->inputProcessor->markObjectAsUsed(currObj, objName);
    2470           0 :             std::shared_ptr<GLHEVertSingle> thisObj(new GLHEVertSingle(state, objNameUC, instance));
    2471           0 :             state.dataGroundHeatExchanger->singleBoreholesVector.push_back(thisObj);
    2472           0 :         }
    2473           0 :     }
    2474             : 
    2475          24 :     if (state.dataGroundHeatExchanger->numVerticalGLHEs > 0) {
    2476             : 
    2477          23 :         std::string const currObj = "GroundHeatExchanger:System";
    2478             : 
    2479          23 :         auto const instances = state.dataInputProcessing->inputProcessor->epJSON.find(currObj);
    2480          23 :         if (instances == state.dataInputProcessing->inputProcessor->epJSON.end()) {
    2481             :             ShowSevereError(state, format("{}: Somehow getNumObjectsFound was > 0 but epJSON.find found 0", currObj)); // LCOV_EXCL_LINE
    2482             :         }
    2483             : 
    2484          23 :         auto &instancesValue = instances.value();
    2485          46 :         for (auto it = instancesValue.begin(); it != instancesValue.end(); ++it) {
    2486          23 :             auto const &instance = it.value();
    2487          23 :             std::string const &objName = it.key();
    2488          23 :             std::string const &objNameUC = Util::makeUPPER(objName);
    2489          23 :             state.dataInputProcessing->inputProcessor->markObjectAsUsed(currObj, objName);
    2490          23 :             state.dataGroundHeatExchanger->verticalGLHE.emplace_back(state, objNameUC, instance);
    2491          46 :         }
    2492          23 :     }
    2493             : 
    2494             :     // SLINKY GLHE
    2495          24 :     if (state.dataGroundHeatExchanger->numSlinkyGLHEs > 0) {
    2496             : 
    2497           1 :         std::string const currObj = "GroundHeatExchanger:Slinky";
    2498             : 
    2499           1 :         auto const instances = state.dataInputProcessing->inputProcessor->epJSON.find(currObj);
    2500           1 :         if (instances == state.dataInputProcessing->inputProcessor->epJSON.end()) {
    2501             :             ShowSevereError(state, format("{}: Somehow getNumObjectsFound was > 0 but epJSON.find found 0", currObj)); // LCOV_EXCL_LINE
    2502             :         }
    2503             : 
    2504           1 :         auto &instancesValue = instances.value();
    2505           2 :         for (auto it = instancesValue.begin(); it != instancesValue.end(); ++it) {
    2506           1 :             auto const &instance = it.value();
    2507           1 :             std::string const &objName = it.key();
    2508           1 :             std::string const &objNameUC = Util::makeUPPER(objName);
    2509           1 :             state.dataInputProcessing->inputProcessor->markObjectAsUsed(currObj, objName);
    2510           1 :             state.dataGroundHeatExchanger->slinkyGLHE.emplace_back(state, objNameUC, instance);
    2511           2 :         }
    2512           1 :     }
    2513          24 : }
    2514             : 
    2515             : //******************************************************************************
    2516             : 
    2517          24 : void GLHEBase::setupOutput(EnergyPlusData &state)
    2518             : {
    2519          48 :     SetupOutputVariable(state,
    2520             :                         "Ground Heat Exchanger Average Borehole Temperature",
    2521             :                         Constant::Units::C,
    2522          24 :                         this->bhTemp,
    2523             :                         OutputProcessor::TimeStepType::System,
    2524             :                         OutputProcessor::StoreType::Average,
    2525          24 :                         this->name);
    2526          48 :     SetupOutputVariable(state,
    2527             :                         "Ground Heat Exchanger Heat Transfer Rate",
    2528             :                         Constant::Units::W,
    2529          24 :                         this->QGLHE,
    2530             :                         OutputProcessor::TimeStepType::System,
    2531             :                         OutputProcessor::StoreType::Average,
    2532          24 :                         this->name);
    2533          48 :     SetupOutputVariable(state,
    2534             :                         "Ground Heat Exchanger Inlet Temperature",
    2535             :                         Constant::Units::C,
    2536          24 :                         this->inletTemp,
    2537             :                         OutputProcessor::TimeStepType::System,
    2538             :                         OutputProcessor::StoreType::Average,
    2539          24 :                         this->name);
    2540          48 :     SetupOutputVariable(state,
    2541             :                         "Ground Heat Exchanger Outlet Temperature",
    2542             :                         Constant::Units::C,
    2543          24 :                         this->outletTemp,
    2544             :                         OutputProcessor::TimeStepType::System,
    2545             :                         OutputProcessor::StoreType::Average,
    2546          24 :                         this->name);
    2547          48 :     SetupOutputVariable(state,
    2548             :                         "Ground Heat Exchanger Mass Flow Rate",
    2549             :                         Constant::Units::kg_s,
    2550          24 :                         this->massFlowRate,
    2551             :                         OutputProcessor::TimeStepType::System,
    2552             :                         OutputProcessor::StoreType::Average,
    2553          24 :                         this->name);
    2554          48 :     SetupOutputVariable(state,
    2555             :                         "Ground Heat Exchanger Average Fluid Temperature",
    2556             :                         Constant::Units::C,
    2557          24 :                         this->aveFluidTemp,
    2558             :                         OutputProcessor::TimeStepType::System,
    2559             :                         OutputProcessor::StoreType::Average,
    2560          24 :                         this->name);
    2561          48 :     SetupOutputVariable(state,
    2562             :                         "Ground Heat Exchanger Farfield Ground Temperature",
    2563             :                         Constant::Units::C,
    2564          24 :                         this->tempGround,
    2565             :                         OutputProcessor::TimeStepType::System,
    2566             :                         OutputProcessor::StoreType::Average,
    2567          24 :                         this->name);
    2568          24 : }
    2569             : 
    2570             : //******************************************************************************
    2571             : 
    2572      298680 : Real64 GLHEVert::calcBHAverageResistance(EnergyPlusData &state)
    2573             : {
    2574             :     // Calculates the average thermal resistance of the borehole using the first-order multipole method.
    2575             :     // Javed, S. & Spitler, J.D. 2016. 'Accuracy of Borehole Thermal Resistance Calculation Methods
    2576             :     // for Grouted Single U-tube Ground Heat Exchangers.' Applied Energy.187:790-806.
    2577             :     // Equation 13
    2578             : 
    2579      298680 :     Real64 const beta = 2 * Constant::Pi * this->grout.k * calcPipeResistance(state);
    2580             : 
    2581      298680 :     Real64 const final_term_1 = log(this->theta_2 / (2 * this->theta_1 * pow(1 - pow_4(this->theta_1), this->sigma)));
    2582      298680 :     Real64 const num_final_term_2 = pow_2(this->theta_3) * pow_2(1 - (4 * this->sigma * pow_4(this->theta_1)) / (1 - pow_4(this->theta_1)));
    2583      298680 :     Real64 const den_final_term_2_pt_1 = (1 + beta) / (1 - beta);
    2584      298680 :     Real64 const den_final_term_2_pt_2 = pow_2(this->theta_3) * (1 + (16 * this->sigma * pow_4(this->theta_1)) / pow_2(1 - pow_4(this->theta_1)));
    2585      298680 :     Real64 const den_final_term_2 = den_final_term_2_pt_1 + den_final_term_2_pt_2;
    2586      298680 :     Real64 const final_term_2 = num_final_term_2 / den_final_term_2;
    2587             : 
    2588      298680 :     return (1 / (4 * Constant::Pi * this->grout.k)) * (beta + final_term_1 - final_term_2);
    2589             : }
    2590             : 
    2591             : //******************************************************************************
    2592             : 
    2593      298678 : Real64 GLHEVert::calcBHTotalInternalResistance(EnergyPlusData &state)
    2594             : {
    2595             :     // Calculates the total internal thermal resistance of the borehole using the first-order multipole method.
    2596             :     // Javed, S. & Spitler, J.D. 2016. 'Accuracy of Borehole Thermal Resistance Calculation Methods
    2597             :     // for Grouted Single U-tube Ground Heat Exchangers.' Applied Energy. 187:790-806.
    2598             :     // Equation 26
    2599             : 
    2600      298678 :     Real64 beta = 2 * Constant::Pi * this->grout.k * calcPipeResistance(state);
    2601             : 
    2602      298678 :     Real64 final_term_1 = log(pow(1 + pow_2(this->theta_1), this->sigma) / (this->theta_3 * pow(1 - pow_2(this->theta_1), this->sigma)));
    2603      298678 :     Real64 num_term_2 = pow_2(this->theta_3) * pow_2(1 - pow_4(this->theta_1) + 4 * this->sigma * pow_2(this->theta_1));
    2604      298678 :     Real64 den_term_2_pt_1 = (1 + beta) / (1 - beta) * pow_2(1 - pow_4(this->theta_1));
    2605      298678 :     Real64 den_term_2_pt_2 = pow_2(this->theta_3) * pow_2(1 - pow_4(this->theta_1));
    2606      298678 :     Real64 den_term_2_pt_3 = 8 * this->sigma * pow_2(this->theta_1) * pow_2(this->theta_3) * (1 + pow_4(this->theta_1));
    2607      298678 :     Real64 den_term_2 = den_term_2_pt_1 - den_term_2_pt_2 + den_term_2_pt_3;
    2608      298678 :     Real64 final_term_2 = num_term_2 / den_term_2;
    2609             : 
    2610      298678 :     return (1 / (Constant::Pi * this->grout.k)) * (beta + final_term_1 - final_term_2);
    2611             : }
    2612             : 
    2613             : //******************************************************************************
    2614             : 
    2615           0 : Real64 GLHEVert::calcBHGroutResistance(EnergyPlusData &state)
    2616             : {
    2617             :     // Calculates grout resistance. Use for validation.
    2618             :     // Javed, S. & Spitler, J.D. 2016. 'Accuracy of Borehole Thermal Resistance Calculation Methods
    2619             :     // for Grouted Single U-tube Ground Heat Exchangers.' Applied Energy. 187:790-806.
    2620             :     // Equation 3
    2621             : 
    2622           0 :     return calcBHAverageResistance(state) - calcPipeResistance(state) / 2.0;
    2623             : }
    2624             : 
    2625             : //******************************************************************************
    2626             : 
    2627      358408 : Real64 GLHEVert::calcHXResistance(EnergyPlusData &state)
    2628             : {
    2629             :     // Calculates the effective thermal resistance of the borehole assuming a uniform heat flux.
    2630             :     // Javed, S. & Spitler, J.D. Calculation of Borehole Thermal Resistance. In 'Advances in
    2631             :     // Ground-Source Heat Pump Systems,' pp. 84. Rees, S.J. ed. Cambridge, MA. Elsevier Ltd. 2016.
    2632             :     // Eq: 3-67
    2633             : 
    2634      358408 :     if (this->massFlowRate <= 0.0) {
    2635       59730 :         return 0;
    2636             :     } else {
    2637      298678 :         std::string_view const RoutineName = "calcBHResistance";
    2638             : 
    2639      298678 :         Real64 const cpFluid = FluidProperties::GetSpecificHeatGlycol(state,
    2640      298678 :                                                                       state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidName,
    2641             :                                                                       this->inletTemp,
    2642      298678 :                                                                       state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidIndex,
    2643             :                                                                       RoutineName);
    2644      298678 :         return calcBHAverageResistance(state) +
    2645      298678 :                1 / (3 * calcBHTotalInternalResistance(state)) * pow_2(this->bhLength / (this->massFlowRate * cpFluid));
    2646             :     }
    2647             : }
    2648             : 
    2649             : //******************************************************************************
    2650             : 
    2651      597358 : Real64 GLHEVert::calcPipeConductionResistance()
    2652             : {
    2653             :     // Calculates the thermal resistance of a pipe, in [K/(W/m)].
    2654             :     // Javed, S. & Spitler, J.D. 2016. 'Accuracy of Borehole Thermal Resistance Calculation Methods
    2655             :     // for Grouted Single U-tube Ground Heat Exchangers.' Applied Energy. 187:790-806.
    2656             : 
    2657      597358 :     return log(this->pipe.outDia / this->pipe.innerDia) / (2 * Constant::Pi * this->pipe.k);
    2658             : }
    2659             : 
    2660             : //******************************************************************************
    2661             : 
    2662      597360 : Real64 GLHEVert::calcPipeConvectionResistance(EnergyPlusData &state)
    2663             : {
    2664             :     // Calculates the convection resistance using Gnielinski and Petukov, in [K/(W/m)]
    2665             :     // Gneilinski, V. 1976. 'New equations for heat and mass transfer in turbulent pipe and channel flow.'
    2666             :     // International Chemical Engineering 16(1976), pp. 359-368.
    2667             : 
    2668             :     // SUBROUTINE PARAMETER DEFINITIONS:
    2669      597360 :     constexpr const char *RoutineName("calcPipeConvectionResistance");
    2670             : 
    2671             :     // Get fluid props
    2672      597360 :     this->inletTemp = state.dataLoopNodes->Node(this->inletNodeNum).Temp;
    2673             : 
    2674     1194720 :     Real64 const cpFluid = FluidProperties::GetSpecificHeatGlycol(state,
    2675      597360 :                                                                   state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidName,
    2676             :                                                                   this->inletTemp,
    2677      597360 :                                                                   state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidIndex,
    2678             :                                                                   RoutineName);
    2679     1194720 :     Real64 const kFluid = FluidProperties::GetConductivityGlycol(state,
    2680      597360 :                                                                  state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidName,
    2681             :                                                                  this->inletTemp,
    2682      597360 :                                                                  state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidIndex,
    2683             :                                                                  RoutineName);
    2684     1194720 :     Real64 const fluidViscosity = FluidProperties::GetViscosityGlycol(state,
    2685      597360 :                                                                       state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidName,
    2686             :                                                                       this->inletTemp,
    2687      597360 :                                                                       state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidIndex,
    2688             :                                                                       RoutineName);
    2689             : 
    2690             :     // Smoothing fit limits
    2691      597360 :     constexpr Real64 lower_limit = 2000;
    2692      597360 :     constexpr Real64 upper_limit = 4000;
    2693             : 
    2694      597360 :     Real64 const bhMassFlowRate = this->massFlowRate / this->myRespFactors->numBoreholes;
    2695      597360 :     Real64 const reynoldsNum = 4 * bhMassFlowRate / (fluidViscosity * Constant::Pi * this->pipe.innerDia);
    2696             : 
    2697      597360 :     Real64 nusseltNum = 0.0;
    2698      597360 :     if (reynoldsNum < lower_limit) {
    2699      534880 :         nusseltNum = 4.01; // laminar mean(4.36, 3.66)
    2700       62480 :     } else if (reynoldsNum < upper_limit) {
    2701       31580 :         Real64 constexpr nu_low = 4.01;               // laminar
    2702       31580 :         Real64 const f = frictionFactor(reynoldsNum); // turbulent
    2703       31580 :         Real64 const prandtlNum = (cpFluid * fluidViscosity) / (kFluid);
    2704       31580 :         Real64 const nu_high = (f / 8) * (reynoldsNum - 1000) * prandtlNum / (1 + 12.7 * std::sqrt(f / 8) * (pow(prandtlNum, 2.0 / 3.0) - 1));
    2705       31580 :         Real64 const sf = 1 / (1 + std::exp(-(reynoldsNum - 3000) / 150.0)); // smoothing function
    2706       31580 :         nusseltNum = (1 - sf) * nu_low + sf * nu_high;
    2707             :     } else {
    2708       30900 :         Real64 const f = frictionFactor(reynoldsNum);
    2709       30900 :         Real64 const prandtlNum = (cpFluid * fluidViscosity) / (kFluid);
    2710       30900 :         nusseltNum = (f / 8) * (reynoldsNum - 1000) * prandtlNum / (1 + 12.7 * std::sqrt(f / 8) * (pow(prandtlNum, 2.0 / 3.0) - 1));
    2711             :     }
    2712             : 
    2713      597360 :     Real64 h = nusseltNum * kFluid / this->pipe.innerDia;
    2714             : 
    2715      597360 :     return 1 / (h * Constant::Pi * this->pipe.innerDia);
    2716             : }
    2717             : 
    2718             : //******************************************************************************
    2719             : 
    2720       62480 : Real64 GLHEVert::frictionFactor(Real64 const reynoldsNum)
    2721             : {
    2722             :     // Calculates the friction factor in smooth tubes
    2723             :     // Petukov, B.S. 1970. 'Heat transfer and friction in turbulent pipe flow with variable physical properties.'
    2724             :     // In Advances in Heat Transfer, ed. T.F. Irvine and J.P. Hartnett, Vol. 6. New York Academic Press.
    2725             : 
    2726             :     // limits picked to be within about 1% of actual values
    2727       62480 :     constexpr Real64 lower_limit = 1500;
    2728       62480 :     constexpr Real64 upper_limit = 5000;
    2729             : 
    2730       62480 :     if (reynoldsNum < lower_limit) {
    2731           0 :         return 64.0 / reynoldsNum; // pure laminar flow
    2732       62480 :     } else if (reynoldsNum < upper_limit) {
    2733             :         // pure laminar flow
    2734       62480 :         Real64 const f_low = 64.0 / reynoldsNum;
    2735             :         // pure turbulent flow
    2736       62480 :         Real64 const f_high = pow(0.79 * log(reynoldsNum) - 1.64, -2.0);
    2737       62480 :         Real64 const sf = 1 / (1 + exp(-(reynoldsNum - 3000.0) / 450.0)); // smoothing function
    2738       62480 :         return (1 - sf) * f_low + sf * f_high;
    2739             :     } else {
    2740           0 :         return pow(0.79 * log(reynoldsNum) - 1.64, -2.0); // pure turbulent flow
    2741             :     }
    2742             : }
    2743             : 
    2744             : //******************************************************************************
    2745             : 
    2746      597358 : Real64 GLHEVert::calcPipeResistance(EnergyPlusData &state)
    2747             : {
    2748             :     // Calculates the combined conduction and convection pipe resistance
    2749             :     // Javed, S. & Spitler, J.D. 2016. 'Accuracy of Borehole Thermal Resistance Calculation Methods
    2750             :     // for Grouted Single U-tube Ground Heat Exchangers.' J. Energy Engineering. Draft in progress.
    2751             :     // Equation 3
    2752             : 
    2753      597358 :     return calcPipeConductionResistance() + calcPipeConvectionResistance(state);
    2754             : }
    2755             : 
    2756             : //******************************************************************************
    2757             : 
    2758       14400 : Real64 GLHESlinky::calcHXResistance(EnergyPlusData &state)
    2759             : {
    2760             : 
    2761             :     // SUBROUTINE INFORMATION:
    2762             :     //       AUTHOR         Matt Mitchell
    2763             :     //       DATE WRITTEN   February, 2015
    2764             : 
    2765             :     // PURPOSE OF THIS SUBROUTINE:
    2766             :     //    Calculates the resistance of the slinky HX from the fluid to the outer tube wall.
    2767             : 
    2768             :     // SUBROUTINE PARAMETER DEFINITIONS:
    2769       14400 :     std::string_view const RoutineName = "CalcSlinkyGroundHeatExchanger";
    2770             : 
    2771             :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    2772             :     Real64 nusseltNum;
    2773             :     Real64 Rconv;
    2774       14400 :     constexpr Real64 A = 3150;
    2775       14400 :     constexpr Real64 B = 350;
    2776       14400 :     constexpr Real64 laminarNusseltNo = 4.364;
    2777             : 
    2778       14400 :     Real64 cpFluid = FluidProperties::GetSpecificHeatGlycol(state,
    2779       14400 :                                                             state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidName,
    2780             :                                                             this->inletTemp,
    2781       14400 :                                                             state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidIndex,
    2782             :                                                             RoutineName);
    2783       14400 :     Real64 kFluid = FluidProperties::GetConductivityGlycol(state,
    2784       14400 :                                                            state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidName,
    2785             :                                                            this->inletTemp,
    2786       14400 :                                                            state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidIndex,
    2787             :                                                            RoutineName);
    2788       14400 :     Real64 fluidDensity = FluidProperties::GetDensityGlycol(state,
    2789       14400 :                                                             state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidName,
    2790             :                                                             this->inletTemp,
    2791       14400 :                                                             state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidIndex,
    2792             :                                                             RoutineName);
    2793       14400 :     Real64 fluidViscosity = FluidProperties::GetViscosityGlycol(state,
    2794       14400 :                                                                 state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidName,
    2795             :                                                                 this->inletTemp,
    2796       14400 :                                                                 state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidIndex,
    2797             :                                                                 RoutineName);
    2798             : 
    2799             :     // calculate mass flow rate
    2800       14400 :     Real64 singleSlinkyMassFlowRate = this->massFlowRate / this->numTrenches;
    2801             : 
    2802       14400 :     Real64 pipeInnerRad = this->pipe.outRadius - this->pipe.thickness;
    2803       14400 :     Real64 pipeInnerDia = 2.0 * pipeInnerRad;
    2804             : 
    2805       14400 :     if (singleSlinkyMassFlowRate == 0.0) {
    2806           0 :         Rconv = 0.0;
    2807             :     } else {
    2808             :         // Re=Rho*V*D/Mu
    2809             :         Real64 reynoldsNum =
    2810       14400 :             fluidDensity * pipeInnerDia * (singleSlinkyMassFlowRate / fluidDensity / (Constant::Pi * pow_2(pipeInnerRad))) / fluidViscosity;
    2811       14400 :         Real64 prandtlNum = (cpFluid * fluidViscosity) / (kFluid);
    2812             :         //   Convection Resistance
    2813       14400 :         if (reynoldsNum <= 2300) {
    2814           0 :             nusseltNum = laminarNusseltNo;
    2815       14400 :         } else if (reynoldsNum > 2300 && reynoldsNum <= 4000) {
    2816           0 :             Real64 sf = 0.5 + 0.5 * std::tanh((reynoldsNum - A) / B);
    2817           0 :             Real64 turbulentNusseltNo = 0.023 * std::pow(reynoldsNum, 0.8) * std::pow(prandtlNum, 0.35);
    2818           0 :             nusseltNum = laminarNusseltNo * (1 - sf) + turbulentNusseltNo * sf;
    2819           0 :         } else {
    2820       14400 :             nusseltNum = 0.023 * std::pow(reynoldsNum, 0.8) * std::pow(prandtlNum, 0.35);
    2821             :         }
    2822       14400 :         Real64 hci = nusseltNum * kFluid / pipeInnerDia;
    2823       14400 :         Rconv = 1.0 / (2.0 * Constant::Pi * pipeInnerDia * hci);
    2824             :     }
    2825             : 
    2826             :     //   Conduction Resistance
    2827       14400 :     Real64 Rcond = std::log(this->pipe.outRadius / pipeInnerRad) / (2.0 * Constant::Pi * this->pipe.k) / 2.0; // pipe in parallel so /2
    2828             : 
    2829       14400 :     return Rcond + Rconv;
    2830             : }
    2831             : 
    2832             : //******************************************************************************
    2833             : 
    2834    49680400 : Real64 GLHEBase::interpGFunc(Real64 const x_val) const
    2835             : {
    2836             :     // Purpose: interpolate between g-function values, with linear extrapolation above and below range
    2837             : 
    2838    49680400 :     auto const &x = this->myRespFactors->LNTTS;
    2839    49680400 :     auto const &y = this->myRespFactors->GFNC;
    2840    49680400 :     auto const &upper_it = std::upper_bound(x.begin(), x.end(), x_val);
    2841             : 
    2842    49680400 :     int l_idx = 0;
    2843    49680400 :     int u_idx = 0;
    2844             : 
    2845    49680400 :     if (upper_it == x.begin()) {
    2846             :         // Linear extrapolation beyond the lower bound
    2847      388720 :         l_idx = 0;
    2848      388720 :         u_idx = 1;
    2849    49291680 :     } else if (upper_it == x.end()) {
    2850             :         // Linear extrapolation beyond the upper bound
    2851           0 :         u_idx = x.size() - 1;
    2852           0 :         l_idx = u_idx - 1;
    2853             :     } else {
    2854             :         // In the middle of the range
    2855    49291680 :         u_idx = std::distance(x.begin(), upper_it);
    2856    49291680 :         l_idx = u_idx - 1;
    2857             :     }
    2858             : 
    2859    49680400 :     Real64 const x_low = x[l_idx];
    2860    49680400 :     Real64 const x_high = x[u_idx];
    2861    49680400 :     Real64 const y_low = y[l_idx];
    2862    49680400 :     Real64 const y_high = y[u_idx];
    2863             : 
    2864    49680400 :     return (x_val - x_low) / (x_high - x_low) * (y_high - y_low) + y_low;
    2865             : }
    2866             : 
    2867             : //******************************************************************************
    2868             : 
    2869     1699912 : Real64 GLHESlinky::getGFunc(Real64 const time)
    2870             : {
    2871             :     // SUBROUTINE INFORMATION:
    2872             :     //       AUTHOR:          Matt Mitchell
    2873             :     //       DATE WRITTEN:    February, 2015
    2874             : 
    2875             :     // PURPOSE OF THIS SUBROUTINE:
    2876             :     // Gets the g-function for slinky GHXs Note: Base 10 here.
    2877             : 
    2878     1699912 :     Real64 LNTTS = std::log10(time);
    2879     1699912 :     return interpGFunc(LNTTS);
    2880             : }
    2881             : 
    2882             : //******************************************************************************
    2883             : 
    2884    47980488 : Real64 GLHEVert::getGFunc(Real64 const time)
    2885             : {
    2886             :     // SUBROUTINE INFORMATION:
    2887             :     //       AUTHOR:          Matt Mitchell
    2888             :     //       DATE WRITTEN:    February, 2015
    2889             : 
    2890             :     // PURPOSE OF THIS SUBROUTINE:
    2891             :     // Gets the g-function for vertical GHXs Note: Base e here.
    2892             : 
    2893    47980488 :     Real64 LNTTS = std::log(time);
    2894    47980488 :     Real64 gFuncVal = interpGFunc(LNTTS);
    2895    47980488 :     Real64 RATIO = this->bhRadius / this->bhLength;
    2896             : 
    2897    47980488 :     if (RATIO != this->myRespFactors->gRefRatio) {
    2898           0 :         gFuncVal -= std::log(this->bhRadius / (this->bhLength * this->myRespFactors->gRefRatio));
    2899             :     }
    2900             : 
    2901    47980488 :     return gFuncVal;
    2902             : }
    2903             : 
    2904             : //******************************************************************************
    2905             : 
    2906      361451 : void GLHEVert::initGLHESimVars(EnergyPlusData &state)
    2907             : {
    2908             :     // SUBROUTINE INFORMATION:
    2909             :     //       AUTHOR:          Dan Fisher
    2910             :     //       DATE WRITTEN:    August, 2000
    2911             :     //       MODIFIED         Arun Murugappan
    2912             : 
    2913             :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    2914      361451 :     Real64 currTime = ((state.dataGlobal->DayOfSim - 1) * 24 + (state.dataGlobal->HourOfDay - 1) +
    2915      361451 :                        (state.dataGlobal->TimeStep - 1) * state.dataGlobal->TimeStepZone + state.dataHVACGlobal->SysTimeElapsed) *
    2916      361451 :                       Constant::SecInHour;
    2917             : 
    2918      361451 :     if (this->myEnvrnFlag && state.dataGlobal->BeginEnvrnFlag) {
    2919         125 :         this->initEnvironment(state, currTime);
    2920             :     }
    2921             : 
    2922             :     // Calculate the average ground temperature over the depth of the borehole
    2923      361451 :     Real64 minDepth = this->myRespFactors->props->bhTopDepth;
    2924      361451 :     Real64 maxDepth = this->myRespFactors->props->bhLength + minDepth;
    2925      361451 :     Real64 oneQuarterDepth = minDepth + (maxDepth - minDepth) * 0.25;
    2926      361451 :     Real64 halfDepth = minDepth + (maxDepth - minDepth) * 0.5;
    2927      361451 :     Real64 threeQuarterDepth = minDepth + (maxDepth - minDepth) * 0.75;
    2928             : 
    2929      361451 :     this->tempGround = 0;
    2930             : 
    2931      361451 :     this->tempGround += this->groundTempModel->getGroundTempAtTimeInSeconds(state, minDepth, currTime);
    2932      361451 :     this->tempGround += this->groundTempModel->getGroundTempAtTimeInSeconds(state, maxDepth, currTime);
    2933      361451 :     this->tempGround += this->groundTempModel->getGroundTempAtTimeInSeconds(state, oneQuarterDepth, currTime);
    2934      361451 :     this->tempGround += this->groundTempModel->getGroundTempAtTimeInSeconds(state, halfDepth, currTime);
    2935      361451 :     this->tempGround += this->groundTempModel->getGroundTempAtTimeInSeconds(state, threeQuarterDepth, currTime);
    2936             : 
    2937      361451 :     this->tempGround /= 5;
    2938             : 
    2939      361451 :     this->massFlowRate = PlantUtilities::RegulateCondenserCompFlowReqOp(state, this->plantLoc, this->designMassFlow);
    2940             : 
    2941      361451 :     PlantUtilities::SetComponentFlowRate(state, this->massFlowRate, this->inletNodeNum, this->outletNodeNum, this->plantLoc);
    2942             : 
    2943             :     // Reset local environment init flag
    2944      361451 :     if (!state.dataGlobal->BeginEnvrnFlag) this->myEnvrnFlag = true;
    2945      361451 : }
    2946             : 
    2947             : //******************************************************************************
    2948             : 
    2949         125 : void GLHEVert::initEnvironment(EnergyPlusData &state, [[maybe_unused]] Real64 const CurTime)
    2950             : {
    2951         125 :     std::string_view const RoutineName = "initEnvironment";
    2952         125 :     this->myEnvrnFlag = false;
    2953             : 
    2954         125 :     Real64 fluidDensity = FluidProperties::GetDensityGlycol(state,
    2955         125 :                                                             state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidName,
    2956             :                                                             20.0,
    2957         125 :                                                             state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidIndex,
    2958             :                                                             RoutineName);
    2959         125 :     this->designMassFlow = this->designFlow * fluidDensity;
    2960         125 :     PlantUtilities::InitComponentNodes(state, 0.0, this->designMassFlow, this->inletNodeNum, this->outletNodeNum);
    2961             : 
    2962         125 :     this->lastQnSubHr = 0.0;
    2963         125 :     state.dataLoopNodes->Node(this->inletNodeNum).Temp = this->tempGround;
    2964         125 :     state.dataLoopNodes->Node(this->outletNodeNum).Temp = this->tempGround;
    2965             : 
    2966             :     // zero out all history arrays
    2967         125 :     this->QnHr = 0.0;
    2968         125 :     this->QnMonthlyAgg = 0.0;
    2969         125 :     this->QnSubHr = 0.0;
    2970         125 :     this->LastHourN = 0;
    2971         125 :     state.dataGroundHeatExchanger->prevTimeSteps = 0.0;
    2972         125 :     state.dataGroundHeatExchanger->currentSimTime = 0.0;
    2973         125 :     this->QGLHE = 0.0;
    2974         125 :     this->prevHour = 1;
    2975         125 : }
    2976             : 
    2977             : //******************************************************************************
    2978             : 
    2979          23 : void GLHEVert::oneTimeInit_new(EnergyPlusData &state)
    2980             : {
    2981             :     // Locate the hx on the plant loops for later usage
    2982          23 :     bool errFlag = false;
    2983          46 :     PlantUtilities::ScanPlantLoopsForObject(
    2984          23 :         state, this->name, DataPlant::PlantEquipmentType::GrndHtExchgSystem, this->plantLoc, errFlag, _, _, _, _, _);
    2985          23 :     if (errFlag) {
    2986           0 :         ShowFatalError(state, "initGLHESimVars: Program terminated due to previous condition(s).");
    2987             :     }
    2988          23 : }
    2989           0 : void GLHEVert::oneTimeInit([[maybe_unused]] EnergyPlusData &state)
    2990             : {
    2991           0 : }
    2992             : 
    2993             : //******************************************************************************
    2994             : 
    2995       14554 : void GLHESlinky::initGLHESimVars(EnergyPlusData &state)
    2996             : {
    2997             :     // SUBROUTINE INFORMATION:
    2998             :     //       AUTHOR:          Dan Fisher
    2999             :     //       DATE WRITTEN:    August, 2000
    3000             :     //       MODIFIED         Arun Murugappan
    3001             : 
    3002       14554 :     Real64 CurTime = ((state.dataGlobal->DayOfSim - 1) * 24 + (state.dataGlobal->HourOfDay - 1) +
    3003       14554 :                       (state.dataGlobal->TimeStep - 1) * state.dataGlobal->TimeStepZone + state.dataHVACGlobal->SysTimeElapsed) *
    3004       14554 :                      Constant::SecInHour;
    3005             : 
    3006             :     // Init more variables
    3007       14554 :     if (this->myEnvrnFlag && state.dataGlobal->BeginEnvrnFlag) {
    3008           6 :         this->initEnvironment(state, CurTime);
    3009             :     }
    3010             : 
    3011       14554 :     this->tempGround = this->groundTempModel->getGroundTempAtTimeInSeconds(state, this->coilDepth, CurTime);
    3012             : 
    3013       14554 :     this->massFlowRate = PlantUtilities::RegulateCondenserCompFlowReqOp(state, this->plantLoc, this->designMassFlow);
    3014             : 
    3015       14554 :     PlantUtilities::SetComponentFlowRate(state, this->massFlowRate, this->inletNodeNum, this->outletNodeNum, this->plantLoc);
    3016             : 
    3017             :     // Reset local environment init flag
    3018       14554 :     if (!state.dataGlobal->BeginEnvrnFlag) this->myEnvrnFlag = true;
    3019       14554 : }
    3020             : 
    3021             : //******************************************************************************
    3022             : 
    3023           6 : void GLHESlinky::initEnvironment(EnergyPlusData &state, Real64 const CurTime)
    3024             : {
    3025             : 
    3026           6 :     std::string_view const RoutineName = "initEnvironment";
    3027           6 :     this->myEnvrnFlag = false;
    3028             : 
    3029           6 :     Real64 fluidDensity = FluidProperties::GetDensityGlycol(state,
    3030           6 :                                                             state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidName,
    3031             :                                                             20.0,
    3032           6 :                                                             state.dataPlnt->PlantLoop(this->plantLoc.loopNum).FluidIndex,
    3033             :                                                             RoutineName);
    3034           6 :     this->designMassFlow = this->designFlow * fluidDensity;
    3035           6 :     PlantUtilities::InitComponentNodes(state, 0.0, this->designMassFlow, this->inletNodeNum, this->outletNodeNum);
    3036             : 
    3037           6 :     this->lastQnSubHr = 0.0;
    3038           6 :     state.dataLoopNodes->Node(this->inletNodeNum).Temp = this->groundTempModel->getGroundTempAtTimeInSeconds(state, this->coilDepth, CurTime);
    3039           6 :     state.dataLoopNodes->Node(this->outletNodeNum).Temp = this->groundTempModel->getGroundTempAtTimeInSeconds(state, this->coilDepth, CurTime);
    3040             : 
    3041             :     // zero out all history arrays
    3042           6 :     this->QnHr = 0.0;
    3043           6 :     this->QnMonthlyAgg = 0.0;
    3044           6 :     this->QnSubHr = 0.0;
    3045           6 :     this->LastHourN = 0;
    3046           6 :     state.dataGroundHeatExchanger->prevTimeSteps = 0.0;
    3047           6 :     state.dataGroundHeatExchanger->currentSimTime = 0.0;
    3048           6 :     this->QGLHE = 0.0;
    3049           6 :     this->prevHour = 1;
    3050           6 : }
    3051             : 
    3052             : //******************************************************************************
    3053             : 
    3054           1 : void GLHESlinky::oneTimeInit_new(EnergyPlusData &state)
    3055             : {
    3056             :     // Locate the hx on the plant loops for later usage
    3057           1 :     bool errFlag = false;
    3058           2 :     PlantUtilities::ScanPlantLoopsForObject(
    3059           1 :         state, this->name, DataPlant::PlantEquipmentType::GrndHtExchgSlinky, this->plantLoc, errFlag, _, _, _, _, _);
    3060           1 :     if (errFlag) {
    3061           0 :         ShowFatalError(state, "initGLHESimVars: Program terminated due to previous condition(s).");
    3062             :     }
    3063           1 : }
    3064           0 : void GLHESlinky::oneTimeInit([[maybe_unused]] EnergyPlusData &state)
    3065             : {
    3066           0 : }
    3067             : 
    3068             : //******************************************************************************
    3069             : 
    3070             : } // namespace EnergyPlus::GroundHeatExchangers

Generated by: LCOV version 1.14