LCOV - code coverage report
Current view: top level - EnergyPlus - GroundHeatExchangers.cc (source / functions) Hit Total Coverage
Test: lcov.output.filtered Lines: 1233 1498 82.3 %
Date: 2023-01-17 19:17:23 Functions: 69 75 92.0 %

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

Generated by: LCOV version 1.13