LCOV - code coverage report
Current view: top level - EnergyPlus - Construction.cc (source / functions) Coverage Total Hit
Test: lcov.output.filtered Lines: 67.4 % 849 572
Test Date: 2025-05-22 16:09:37 Functions: 100.0 % 12 12

            Line data    Source code
       1              : // EnergyPlus, Copyright (c) 1996-2025, The Board of Trustees of the University of Illinois,
       2              : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
       3              : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
       4              : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
       5              : // contributors. All rights reserved.
       6              : //
       7              : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
       8              : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
       9              : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
      10              : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
      11              : // derivative works, and perform publicly and display publicly, and to permit others to do so.
      12              : //
      13              : // Redistribution and use in source and binary forms, with or without modification, are permitted
      14              : // provided that the following conditions are met:
      15              : //
      16              : // (1) Redistributions of source code must retain the above copyright notice, this list of
      17              : //     conditions and the following disclaimer.
      18              : //
      19              : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
      20              : //     conditions and the following disclaimer in the documentation and/or other materials
      21              : //     provided with the distribution.
      22              : //
      23              : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
      24              : //     the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
      25              : //     used to endorse or promote products derived from this software without specific prior
      26              : //     written permission.
      27              : //
      28              : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
      29              : //     without changes from the version obtained under this License, or (ii) Licensee makes a
      30              : //     reference solely to the software portion of its product, Licensee must refer to the
      31              : //     software as "EnergyPlus version X" software, where "X" is the version number Licensee
      32              : //     obtained under this License and may not use a different name for the software. Except as
      33              : //     specifically required in this Section (4), Licensee shall not use in a company name, a
      34              : //     product name, in advertising, publicity, or other promotional activities any name, trade
      35              : //     name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
      36              : //     similar designation, without the U.S. Department of Energy's prior written consent.
      37              : //
      38              : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
      39              : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
      40              : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
      41              : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
      42              : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
      43              : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
      44              : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
      45              : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
      46              : // POSSIBILITY OF SUCH DAMAGE.
      47              : 
      48              : // EnergyPlus Headers
      49              : #include <EnergyPlus/Construction.hh>
      50              : #include <EnergyPlus/Data/EnergyPlusData.hh>
      51              : #include <EnergyPlus/DataConversions.hh>
      52              : #include <EnergyPlus/DisplayRoutines.hh>
      53              : #include <EnergyPlus/Material.hh>
      54              : #include <EnergyPlus/OutputReportPredefined.hh>
      55              : #include <EnergyPlus/UtilityRoutines.hh>
      56              : 
      57              : namespace EnergyPlus::Construction {
      58              : 
      59          300 : void ConstructionProps::calculateTransferFunction(EnergyPlusData &state, bool &ErrorsFound, bool &DoCTFErrorReport)
      60              : {
      61              : 
      62              :     // SUBROUTINE INFORMATION:
      63              :     //       AUTHOR         Russ Taylor
      64              :     //       DATE WRITTEN   June 1990
      65              :     //       MODIFIED       July 1994, LKL, cosmetic and improve execution time
      66              :     //                      Dec 1995, Apr 1996, RKS, cosmetic and clean-up changes, changes to allow proper
      67              :     //                       handling of resistive layers
      68              :     //                      June 2000, RKS, addition of QTFs (both 1- and 2-D solutions for constructions
      69              :     //                       with embedded/internal heat sources/sinks)
      70              :     //                      July 2010-August 2011, RKS, R-value only layer enhancement
      71              :     //       RE-ENGINEERED  June 1996, February 1997, August-October 1997, RKS; Nov 1999, LKL
      72              : 
      73              :     // PURPOSE OF THIS SUBROUTINE:
      74              :     // This subroutine serves as the main drive for the
      75              :     // calculation of Conduction Transfer Functions (CTFs)
      76              :     // using the state space method.
      77              : 
      78              :     // METHODOLOGY EMPLOYED:
      79              :     // The basic steps of this routine (which may be a little difficult
      80              :     // to decipher until another major revision is done) are:
      81              :     //   1. Determine if enough material info has been entered
      82              :     //   2. Determine whether construct is (a) all resistive,
      83              :     //      (b) the reverse of a previously calculated construct, or
      84              :     //      (c) neither (a) nor (b), i.e. a layer for which CTFs must
      85              :     //      be calculated.
      86              :     //   3. If the answer to 2 is (a), calculate the overall resistance
      87              :     //      and use this as the CTF (steady state conduction).
      88              :     //   4. If the answer to 2 is (b), transfer the CTFs for the reverse
      89              :     //      construction to the CTF arrays for this construct (reversing
      90              :     //      the inside and outside terms).
      91              :     //   5. If the answer to 2 is (c), calculate the CTFs using the state
      92              :     //      space method described below.
      93              :     // The state space method of calculating CTFs involves
      94              :     // applying a finite difference grid to a multilayered
      95              :     // building element and performing linear algebra on the
      96              :     // resulting system of equations (in matrix form).
      97              :     // CTFs must be calculated for non-reversed layers which
      98              :     // have an appreciable thermal mass.  A conversion from
      99              :     // SI units to English units is made due to concerns
     100              :     // about round off problems noted in earlier version of
     101              :     // this subroutine.
     102              : 
     103              :     // REFERENCES:
     104              :     // Seem, J.E.  "Modeling of Heat Transfer in Buildings",
     105              :     //  Department of Mechanical Engineering, University of
     106              :     //  Wisconsin-Madison, 1987.
     107              :     // Strand, R.K. "Testing Design Description for the CTF
     108              :     //  Calculation Code in BEST", BSO internal document,
     109              :     //  May/June 1996.
     110              :     // Strand, R.K. "Heat Source Transfer Functions and Their
     111              :     //  Applicatoin to Low Temperature Radiant Heating System",
     112              :     //  Ph.D. Dissertation, Department of Mechanical and
     113              :     //  Industrial Engineering, University of Illinois at
     114              :     //  Urbana-Champaign, 1995.
     115              : 
     116              :     // SUBROUTINE PARAMETER DEFINITIONS:
     117          300 :     constexpr Real64 PhysPropLimit(1.0e-6); // Physical properties limit.
     118              :     // This is more or less the traditional value from BLAST.
     119              : 
     120          300 :     constexpr Real64 RValueLowLimit(1.0e-3); // Physical properties limit for R-value only layers
     121              :     // This value was based on trial and error related to CR 7791 where a
     122              :     // user had entered a "no insulation" layer with an R-value of 1.0E-05.
     123              :     // Some trial and error established this as a potential value though
     124              :     // there is no guarantee that this is a good value.
     125              : 
     126          300 :     constexpr int MinNodes(6); // Minimum number of state space nodes
     127              :     // per layer.  This value was chosen based on experience with IBLAST.
     128              : 
     129          300 :     constexpr Real64 MaxAllowedCTFSumError(0.01); // Allow a 1 percent
     130              :     // difference between the CTF series summations.  If the difference is
     131              :     // greater than this, then the coefficients will not yield a valid steady
     132              :     // state solution.
     133              : 
     134          300 :     constexpr Real64 MaxAllowedTimeStep = 7.0; // Sets the maximum allowed time step
     135              :     // for CTF calculations to be 7 hours.  This is done in response to some
     136              :     // rare situations where odd or faulty input will cause the routine to
     137              :     // go off and get some huge time step (in excess of 20 hours).  This value
     138              :     // is a compromise that does not really solve any input problems.  One run
     139              :     // indicated that 2 meters of concrete will result in a time step of slightly
     140              :     // more than 3 hours.  So, 7 hours was arbitrarily picked as a ceiling for
     141              :     // time steps so that an error message can be produced to warn the user
     142              :     // that something isn't right.  Note that the 7 hour limit does not guarantee
     143              :     // that problems won't exist and it does not necessarily avoid any problems
     144              :     // that interpolated temperature histories might cause.
     145              : 
     146          300 :     this->CTFCross.fill(0.0);
     147          300 :     this->CTFFlux.fill(0.0);
     148          300 :     this->CTFInside.fill(0.0);
     149          300 :     this->CTFOutside.fill(0.0);
     150          300 :     this->CTFSourceIn.fill(0.0);
     151          300 :     this->CTFSourceOut.fill(0.0);
     152          300 :     this->CTFTimeStep = 0.0;
     153          300 :     this->CTFTSourceOut.fill(0.0);
     154          300 :     this->CTFTSourceIn.fill(0.0);
     155          300 :     this->CTFTSourceQ.fill(0.0);
     156          300 :     this->CTFTUserOut.fill(0.0);
     157          300 :     this->CTFTUserIn.fill(0.0);
     158          300 :     this->CTFTUserSource.fill(0.0);
     159          300 :     this->NumHistories = 0;
     160          300 :     this->NumCTFTerms = 0;
     161          300 :     this->UValue = 0.0;
     162              : 
     163          300 :     if (!this->IsUsedCTF) {
     164           54 :         return;
     165              :     }
     166              : 
     167          246 :     Array1D<Real64> cp(Construction::MaxLayersInConstruct);              // Specific heat of a material layer
     168          246 :     Array1D<Real64> dl(Construction::MaxLayersInConstruct);              // Thickness of a material layer
     169          246 :     Array1D<Real64> dx(Construction::MaxLayersInConstruct);              // Distance between nodes in a particular material layer
     170          246 :     Array1D<Real64> lr(Construction::MaxLayersInConstruct);              // R value of a material layer
     171          246 :     Array1D_int Nodes(Construction::MaxLayersInConstruct);               // Array containing the number of nodes per layer
     172          246 :     Array1D_bool ResLayer(Construction::MaxLayersInConstruct);           // Set true if the layer must be handled as a resistive
     173          246 :     Array1D<Real64> rho(Construction::MaxLayersInConstruct);             // Density of a material layer
     174          246 :     Array1D<Real64> rk(Construction::MaxLayersInConstruct);              // Thermal conductivity of a material layer
     175          246 :     Array1D_int AdjacentResLayerNum(Construction::MaxLayersInConstruct); // Layers that are adjacent to each other which are resistive
     176              : 
     177              :     Real64 amatx;      // Intermediate calculation variable
     178              :     Real64 amatxx;     // Intermediate calculation variable
     179              :     Real64 amaty;      // Intermediate calculation variable
     180              :     Real64 BiggestSum; // Largest CTF series summation (maximum of SumXi, SumYi, and SumZi)
     181              :     Real64 cap;        // Thermal capacitance of a node (intermediate calculation)
     182              :     Real64 capavg;     // Thermal capacitance of a node (average value for a node at an interface)
     183              :     Real64 cnd;        // Total thermal conductance (1/Rtot) of the bldg element
     184              :     Real64 dtn;        // Intermediate calculation of the time step
     185              :     Real64 dxn;        // Intermediate calculation of nodal spacing
     186              :     Real64 dxtmp;      // Intermediate calculation variable ( = 1/dx/cap)
     187              :     Real64 dyn;        // Nodal spacing in the direction perpendicular to the main direction
     188              :     Real64 SumXi;      // Summation of all of the Xi terms (inside CTFs) for a construction
     189              :     Real64 SumYi;      // Summation of all of the Xi terms (cross CTFs) for a construction
     190              :     Real64 SumZi;      // Summation of all of the Xi terms (outside CTFs) for a construction
     191              : 
     192              :     Real64 DeltaTimestep; // zone timestep in seconds, for local check of properties
     193              : 
     194          246 :     this->CTFTimeStep = state.dataGlobal->TimeStepZone;
     195          246 :     Real64 rs = 0.0;
     196          246 :     int LayersInConstruct = 0;
     197          246 :     int NumResLayers = 0;
     198          246 :     ResLayer = false;
     199          246 :     AdjacentResLayerNum = 0; // Zero this out for each construct
     200              : 
     201          647 :     for (int Layer = 1; Layer <= this->TotLayers; ++Layer) { // Begin layer loop ...
     202              : 
     203              :         // Loop through all of the layers in the current construct. The purpose
     204              :         // of this loop is to define the thermal properties necessary to
     205              :         // calculate the CTFs.
     206              : 
     207          401 :         int CurrentLayer = this->LayerPoint(Layer);
     208              : 
     209          401 :         ++LayersInConstruct;
     210              : 
     211              :         // Obtain thermal properties from the Material derived type
     212              : 
     213          401 :         auto *thisMaterial = state.dataMaterial->materials(CurrentLayer);
     214          401 :         assert(thisMaterial != nullptr);
     215              : 
     216          401 :         dl(Layer) = thisMaterial->Thickness;
     217          401 :         rk(Layer) = thisMaterial->Conductivity;
     218          401 :         rho(Layer) = thisMaterial->Density;
     219          401 :         cp(Layer) = thisMaterial->SpecHeat; // Must convert
     220              :         // from kJ/kg-K to J/kg-k due to rk units
     221              : 
     222          401 :         if (this->SourceSinkPresent && !thisMaterial->WarnedForHighDiffusivity) {
     223              :             // check for materials that are too conductive or thin
     224            0 :             if ((rho(Layer) * cp(Layer)) > 0.0) {
     225            0 :                 Real64 Alpha = rk(Layer) / (rho(Layer) * cp(Layer));
     226            0 :                 if (Alpha > DataHeatBalance::HighDiffusivityThreshold) {
     227            0 :                     DeltaTimestep = state.dataGlobal->TimeStepZoneSec;
     228            0 :                     Real64 const ThicknessThreshold = std::sqrt(Alpha * DeltaTimestep * 3.0);
     229            0 :                     if (thisMaterial->Thickness < ThicknessThreshold) {
     230            0 :                         ShowSevereError(
     231              :                             state,
     232            0 :                             format(
     233              :                                 "InitConductionTransferFunctions: Found Material that is too thin and/or too highly conductive, material name = {}",
     234            0 :                                 thisMaterial->Name));
     235            0 :                         ShowContinueError(state,
     236            0 :                                           format("High conductivity Material layers are not well supported for internal source constructions, "
     237              :                                                  "material conductivity = {:.3R} [W/m-K]",
     238            0 :                                                  thisMaterial->Conductivity));
     239            0 :                         ShowContinueError(state, format("Material thermal diffusivity = {:.3R} [m2/s]", Alpha));
     240            0 :                         ShowContinueError(state,
     241            0 :                                           format("Material with this thermal diffusivity should have thickness > {:.5R} [m]", ThicknessThreshold));
     242            0 :                         if (thisMaterial->Thickness < DataHeatBalance::ThinMaterialLayerThreshold) {
     243            0 :                             ShowContinueError(state,
     244            0 :                                               format("Material may be too thin to be modeled well, thickness = {:.5R} [m]", thisMaterial->Thickness));
     245            0 :                             ShowContinueError(state,
     246            0 :                                               format("Material with this thermal diffusivity should have thickness > {:.5R} [m]",
     247              :                                                      DataHeatBalance::ThinMaterialLayerThreshold));
     248              :                         }
     249            0 :                         thisMaterial->WarnedForHighDiffusivity = true;
     250              :                     }
     251              :                 }
     252              :             }
     253              :         }
     254          401 :         if (thisMaterial->Thickness > 3.0) {
     255            0 :             ShowSevereError(state, "InitConductionTransferFunctions: Material too thick for CTF calculation");
     256            0 :             ShowContinueError(state, format("material name = {}", thisMaterial->Name));
     257            0 :             ErrorsFound = true;
     258              :         }
     259              : 
     260          401 :         if (rk(Layer) <= PhysPropLimit) { // Thermal conductivity too small,
     261              :             // thus this must be handled as a resistive layer
     262          135 :             ResLayer(Layer) = true;
     263              :         } else {
     264          266 :             lr(Layer) = dl(Layer) / rk(Layer);
     265          266 :             ResLayer(Layer) = (dl(Layer) * std::sqrt(rho(Layer) * cp(Layer) / rk(Layer))) < PhysPropLimit;
     266              :         }
     267              : 
     268              :         // If not a resistive layer, nothing further is required
     269              :         // for this layer.
     270              : 
     271          401 :         if (ResLayer(Layer)) {                    // Resistive layer-check for R-value, etc.
     272          135 :             ++NumResLayers;                       // Increment number of resistive layers
     273          135 :             lr(Layer) = thisMaterial->Resistance; // User defined thermal resistivity
     274          135 :             if (lr(Layer) < RValueLowLimit) {     // User didn't define enough
     275              :                 // parameters to calculate CTFs for a building element
     276              :                 // containing this layer.
     277              : 
     278            0 :                 ShowSevereError(state, format("InitConductionTransferFunctions: Material={}R Value below lowest allowed value", thisMaterial->Name));
     279            0 :                 ShowContinueError(state, format("Lowest allowed value=[{:.3R}], Material R Value=[{:.3R}].", RValueLowLimit, lr(Layer)));
     280            0 :                 ErrorsFound = true;
     281              : 
     282              :             } else { // A valid user defined R-value is available.
     283              :                 // If this is either the first or last layer in the construction,
     284              :                 // then assign other properties based on air at 1 atm, 300K.
     285              :                 // Reference for air properties:  Incropera and DeWitt,
     286              :                 // Introduction to Heat Transfer, Appendix A, Table A.4,
     287              :                 // John Wiley & Sons, New York, 1985.
     288              :                 // If this is not the first or last layer in the construction,
     289              :                 // then use the "exact" approach to model a massless layer
     290              :                 // based on the node equations for the state space method.
     291              : 
     292          135 :                 if ((Layer == 1) || (Layer == this->TotLayers) || (!state.dataMaterial->materials(this->LayerPoint(Layer))->ROnly)) {
     293          117 :                     cp(Layer) = 1.007;
     294          117 :                     rho(Layer) = 1.1614;
     295          117 :                     rk(Layer) = 0.0263;
     296          117 :                     dl(Layer) = rk(Layer) * lr(Layer);
     297              :                 } else {
     298           18 :                     cp(Layer) = 0.0;
     299           18 :                     rho(Layer) = 0.0;
     300           18 :                     rk(Layer) = 1.0;
     301           18 :                     dl(Layer) = lr(Layer);
     302              :                 }
     303              :             }
     304              :         } // ... end of resistive layer determination IF-THEN block.
     305              :     }     // ... end of layer loop.
     306              : 
     307              :     // If errors have been found, just return
     308              : 
     309          246 :     if (ErrorsFound) return;
     310              : 
     311              :     // Combine any adjacent resistive-only (no mass) layers together
     312              :     // to avoid a divide by zero error in the CTF calculations below.
     313              :     // Since the inner and outer layers cannot be resistive layers
     314              :     // (inner and outer layer still converted to equivalent air layer)
     315              :     // there can only be resistive layers adjacent to one another if
     316              :     // there are more than three total layers and more than one
     317              :     // resistive layer.
     318          246 :     if ((LayersInConstruct > 3) && (NumResLayers > 1)) {
     319            1 :         int NumAdjResLayers = 0;
     320            3 :         for (int Layer = 2; Layer <= LayersInConstruct - 2; ++Layer) {
     321            2 :             if ((ResLayer(Layer)) && (ResLayer(Layer + 1))) {
     322            0 :                 ++NumAdjResLayers;
     323              :                 // There is method to the next assignment statement.  As the layers get shifted, the layer
     324              :                 // numbers will also shift.  Thus, we have to also shift which layer we are dealing with.
     325            0 :                 AdjacentResLayerNum(NumAdjResLayers) = Layer + 1 - NumAdjResLayers;
     326              :             }
     327              :         }
     328            1 :         for (int AdjLayer = 1; AdjLayer <= NumAdjResLayers; ++AdjLayer) {
     329            0 :             int Layer = AdjacentResLayerNum(AdjLayer);
     330              :             // Double check to make sure we are in the right place...
     331            0 :             if ((ResLayer(Layer)) && (ResLayer(Layer + 1))) {
     332              :                 // Shift layers forward after combining two adjacent layers.  Then
     333              :                 // restart the do loop.
     334            0 :                 cp(Layer) = 0.0;
     335            0 :                 rho(Layer) = 0.0;
     336            0 :                 rk(Layer) = 1.0;
     337            0 :                 lr(Layer) += lr(Layer + 1);
     338            0 :                 dl(Layer) = lr(Layer);
     339            0 :                 --NumResLayers; // Combining layers so decrease number of resistive layers
     340            0 :                 for (int Layer1 = Layer + 1; Layer1 <= LayersInConstruct - 1; ++Layer1) {
     341            0 :                     lr(Layer1) = lr(Layer1 + 1);
     342            0 :                     dl(Layer1) = dl(Layer1 + 1);
     343            0 :                     rk(Layer1) = rk(Layer1 + 1);
     344            0 :                     rho(Layer1) = rho(Layer1 + 1);
     345            0 :                     cp(Layer1) = cp(Layer1 + 1);
     346            0 :                     ResLayer(Layer1) = ResLayer(Layer1 + 1);
     347              :                 }
     348              :                 // Then zero out the layer that got shifted forward
     349            0 :                 cp(LayersInConstruct) = 0.0;
     350            0 :                 rho(LayersInConstruct) = 0.0;
     351            0 :                 rk(LayersInConstruct) = 0.0;
     352            0 :                 lr(LayersInConstruct) = 0.0;
     353            0 :                 dl(LayersInConstruct) = 0.0;
     354              :                 // Now reduce the number of layers in construct since merger is complete
     355            0 :                 --LayersInConstruct;
     356              :                 // Also adjust layers with source/sinks if two layers are merged
     357            0 :                 if (this->SourceSinkPresent) {
     358            0 :                     --this->SourceAfterLayer;
     359            0 :                     --this->TempAfterLayer;
     360              :                 }
     361              :             } else { // These are not adjacent layers and there is a logic flaw here (should not happen)
     362            0 :                 ShowFatalError(state, format("Combining resistance layers failed for {}", this->Name));
     363            0 :                 ShowContinueError(state, "This should never happen.  Contact EnergyPlus Support for further assistance.");
     364              :             }
     365              :         }
     366              :     }
     367              : 
     368              :     // Convert SI units to English.  In theory, conversion to English
     369              :     // units is not necessary; however, Russ Taylor noted that some
     370              :     // numerical problems when SI units were used and decided to continue
     371              :     // calculating CTFs in English units.
     372              : 
     373          647 :     for (int Layer = 1; Layer <= LayersInConstruct; ++Layer) { // Begin units conversion loop ...
     374              : 
     375          401 :         lr(Layer) *= DataConversions::CFU;
     376          401 :         dl(Layer) /= DataConversions::CFL;
     377          401 :         rk(Layer) /= DataConversions::CFK;
     378          401 :         rho(Layer) /= DataConversions::CFD;
     379          401 :         cp(Layer) /= (DataConversions::CFC * 1000.0);
     380              : 
     381              :     } // ... end of layer loop for units conversion.
     382              : 
     383          246 :     if (this->SolutionDimensions == 1) {
     384          246 :         dyn = 0.0;
     385              :     } else {
     386            0 :         dyn = (this->ThicknessPerpend / DataConversions::CFL) / double(NumOfPerpendNodes - 1);
     387              :     }
     388              : 
     389              :     // Compute total construct conductivity and resistivity.
     390              : 
     391          647 :     for (int Layer = 1; Layer <= LayersInConstruct; ++Layer) {
     392          401 :         rs += lr(Layer); // Resistances in series sum algebraically
     393              :     }
     394              : 
     395          246 :     cnd = 1.0 / rs; // Conductivity is the inverse of resistivity
     396              : 
     397          246 :     bool RevConst = false;
     398              : 
     399          246 :     if (LayersInConstruct > NumResLayers) {
     400              : 
     401              :         // One or more are not simple resistive layers so CTFs will have to be
     402              :         // calculated unless this is a reverse of a previously defined
     403              :         // this->
     404              : 
     405              :         // Check for reversed construction of interzone surfaces by checking
     406              :         // previous constructions for same number of layers as first indicator.
     407              : 
     408              :         // previously this loop would go from 1..currentConstructionIndex-1
     409              :         // instead of that, we'll loop through the list and stop when we get to the current construction
     410              :         // should be the same behavior, we're just checking it by address
     411          352 :         for (auto &otherConstruction : state.dataConstruction->Construct) {
     412          352 :             if (&otherConstruction == this) break;
     413              : 
     414              :             // If a source or sink is present in this construction, do not allow any
     415              :             // checks for reversed constructions, i.e., always force EnergyPlus to
     416              :             // calculate CTF/QTFs.  So, don't even check for reversed constructions.
     417          206 :             if (this->SourceSinkPresent) break; // Constr DO loop
     418              : 
     419          206 :             if (this->TotLayers == otherConstruction.TotLayers) { // Same number of layers--now | check for reversed construct.
     420              : 
     421           80 :                 RevConst = true;
     422              : 
     423           90 :                 for (int Layer = 1; Layer <= this->TotLayers; ++Layer) { // Begin layers loop ...
     424              : 
     425              :                     // RevConst is set to FALSE anytime a mismatch in materials is found.
     426              :                     // This will exit this DO immediately and go on to the next construct
     427              :                     // (if any remain).
     428              : 
     429           87 :                     int OppositeLayer = this->TotLayers - Layer + 1;
     430              : 
     431           87 :                     if (this->LayerPoint(Layer) != otherConstruction.LayerPoint(OppositeLayer)) {
     432              : 
     433           77 :                         RevConst = false;
     434           77 :                         break; // Layer DO loop
     435              :                     }
     436              : 
     437              :                 } // ... end of layers loop.
     438              : 
     439              :                 // If the reverse construction isn't used by any surfaces then the CTFs
     440              :                 // still need to be defined.
     441           80 :                 if (RevConst && !otherConstruction.IsUsedCTF) {
     442            0 :                     RevConst = false;
     443              :                 }
     444              : 
     445           80 :                 if (RevConst) { // Curent construction is a reverse of
     446              :                     // construction Constr.  Thus, CTFs do not need to be re-
     447              :                     // calculated.  Copy CTF info for construction Constr to
     448              :                     // construction ConstrNum.
     449              : 
     450            3 :                     this->CTFTimeStep = otherConstruction.CTFTimeStep;
     451            3 :                     this->NumHistories = otherConstruction.NumHistories;
     452            3 :                     this->NumCTFTerms = otherConstruction.NumCTFTerms;
     453              : 
     454              :                     // Transfer the temperature and flux history terms to CTF arrays.
     455              :                     // Loop through the number of CTF history terms ...
     456           29 :                     for (int HistTerm = 0; HistTerm <= this->NumCTFTerms; ++HistTerm) {
     457              : 
     458           26 :                         this->CTFInside[HistTerm] = otherConstruction.CTFOutside[HistTerm];
     459           26 :                         this->CTFCross[HistTerm] = otherConstruction.CTFCross[HistTerm];
     460           26 :                         this->CTFOutside[HistTerm] = otherConstruction.CTFInside[HistTerm];
     461           26 :                         if (HistTerm != 0) this->CTFFlux[HistTerm] = otherConstruction.CTFFlux[HistTerm];
     462              : 
     463              :                     } // ... end of CTF history terms loop.
     464              : 
     465            3 :                     break; // Constr DO loop
     466              : 
     467              :                 } // ... end of reversed construction found block
     468              : 
     469              :             } // ... end of reversed construct (same number of layers) block.
     470              : 
     471              :         } // ... end of construct loop (check reversed--Constr)
     472              : 
     473          149 :         if (!RevConst) { // Calculate CTFs (non-reversed constr)
     474              : 
     475              :             // Estimate number of nodes each layer of the construct will require
     476              :             // and calculate the nodal spacing from that
     477              : 
     478          444 :             for (int Layer = 1; Layer <= LayersInConstruct; ++Layer) { // Begin loop thru layers ...
     479              : 
     480              :                 // The calculation of dxn used here is based on a standard stability
     481              :                 // criteria for explicit finite difference solutions.  This criteria
     482              :                 // was chosen not because it is viewed to be correct, but rather for
     483              :                 // lack of any better criteria at this time.  The use of a Fourier
     484              :                 // number based criteria such as this is probably physically correct,
     485              :                 // though the coefficient (2.0) may not be.
     486              : 
     487              :                 // If this is a "resistive" layer, only need a single node
     488          298 :                 if ((ResLayer(Layer)) && (Layer > 1) && (Layer < LayersInConstruct)) {
     489           18 :                     Nodes(Layer) = 1;
     490           18 :                     dx(Layer) = dl(Layer);
     491              :                 } else {
     492          280 :                     dxn = std::sqrt(2.0 * (rk(Layer) / rho(Layer) / cp(Layer)) * this->CTFTimeStep);
     493              : 
     494          280 :                     int ipts1 = int(dl(Layer) / dxn); // number of nodes=thickness/spacing
     495              : 
     496              :                     // Limit the upper and lower bounds of the number of
     497              :                     // nodes to MaxCTFTerms and MinNodes respectively.
     498              : 
     499          280 :                     if (ipts1 > Construction::MaxCTFTerms) { // Too many nodes
     500            0 :                         Nodes(Layer) = Construction::MaxCTFTerms;
     501          280 :                     } else if (ipts1 < MinNodes) { // Too few nodes
     502          264 :                         Nodes(Layer) = MinNodes;
     503              :                     } else { // Calculated number of nodes ok
     504           16 :                         Nodes(Layer) = ipts1;
     505              :                     }
     506              : 
     507          280 :                     dx(Layer) = dl(Layer) / double(Nodes(Layer)); // calc node spacing
     508              :                 }
     509              : 
     510              :             } // . .. end of layers in construction loop (calculating #nodes per layer)
     511              : 
     512              :             // Determine the total number of nodes (rcmax)
     513              : 
     514          146 :             this->rcmax = 0;
     515          444 :             for (int Layer = 1; Layer <= LayersInConstruct; ++Layer) {
     516          298 :                 this->rcmax += Nodes(Layer);
     517              :             }
     518              : 
     519              :             // Nodes are placed throughout layers and at the interface between
     520              :             // layers.  As a result, the end layers share a node with the adjacent
     521              :             // layer-leaving one less node total for all layers.
     522              : 
     523          146 :             --this->rcmax;
     524          146 :             if (this->SolutionDimensions > 1) this->rcmax *= NumOfPerpendNodes;
     525              : 
     526              :             // This section no longer needed as rcmax/number of total nodes is allowed to float.
     527              :             // If reinstated, this node reduction section would have to be modified to account for
     528              :             // the possibility that a 2-D solution is potentially being performed.
     529              :             // Check to see if the maximum number of nodes for the construct has
     530              :             // been exceeded.  Reduce the nodes per layer if necessary, but only
     531              :             // if the number of nodes in a particular layer is greater than the
     532              :             // minimum node limit.
     533              : 
     534              :             //        DO WHILE (rcmax > MaxTotNodes)     ! Begin total node reduction loop ...
     535              : 
     536              :             //          rcmax = 0
     537              : 
     538              :             //          DO Layer = 1, LayersInConstruct   ! Begin layer node reduction ...
     539              : 
     540              :             //          ! If more nodes than the minimum limit for a layer, reduce the
     541              :             //          ! number of nodes.
     542              : 
     543              :             //            IF (Nodes(Layer) > MinNodes) THEN
     544              :             //              Nodes(Layer) = Nodes(Layer)-1
     545              :             //              dx(Layer) = dl(Layer)/DBLE(Nodes(Layer)) ! Recalc node spacing
     546              :             //            END IF
     547              : 
     548              :             //            rcmax = rcmax + Nodes(Layer) ! Recalculate total number of nodes
     549              : 
     550              :             //          END DO        ! ... end of layer loop for node reduction.
     551              : 
     552              :             //          rcmax = rcmax-1 ! See note above on counting rcmax
     553              : 
     554              :             //        END DO      ! ... end of total node reduction loop.
     555              : 
     556              :             // For constructions that have sources or sinks present, determine which
     557              :             // node the source/sink is applied at and also where the temperature
     558              :             // calculation has been requested.
     559          146 :             this->setNodeSourceAndUserTemp(Nodes);
     560              : 
     561              :             // "Adjust time step to ensure stability."  If the time step is too
     562              :             // small, it will result in too many history terms which can lead to
     563              :             // solution instability.  The method used here to determine whether or
     564              :             // not the time step will produce a stable solution is based on a pure
     565              :             // Fourier number calculation (Fo = 1) and has not proven to be
     566              :             // completely effective.  If too many history terms are calculated,
     567              :             // the time step is adjusted and the CTFs end up being recalculated
     568              :             // (see later code in this routine).
     569              : 
     570          146 :             dtn = 0.0;
     571          146 :             this->CTFTimeStep = 0.0;
     572          444 :             for (int Layer = 1; Layer <= LayersInConstruct; ++Layer) {
     573          298 :                 if (Nodes(Layer) >= Construction::MaxCTFTerms) {
     574            0 :                     if (this->SolutionDimensions == 1) {
     575            0 :                         dtn = rho(Layer) * cp(Layer) * pow_2(dx(Layer)) / rk(Layer);
     576              :                     } else { // 2-D solution requested-->this changes length parameter in Fourier number calculation
     577            0 :                         dtn = rho(Layer) * cp(Layer) * (pow_2(dx(Layer)) + pow_2(dyn)) / rk(Layer);
     578              :                     }
     579            0 :                     if (dtn > this->CTFTimeStep) this->CTFTimeStep = dtn;
     580              :                 }
     581              :             }
     582              : 
     583              :             // If the user defined time step is significantly different than the
     584              :             // calculated time step for this construct, then CTFTimeStep must be
     585              :             // revised.
     586              : 
     587          146 :             if (std::abs((state.dataGlobal->TimeStepZone - this->CTFTimeStep) / state.dataGlobal->TimeStepZone) > 0.1) {
     588              : 
     589          146 :                 if (this->CTFTimeStep > state.dataGlobal->TimeStepZone) {
     590              : 
     591              :                     // CTFTimeStep larger than TimeStepZone:  Make sure TimeStepZone
     592              :                     // divides evenly into CTFTimeStep
     593            0 :                     this->NumHistories = int((this->CTFTimeStep / state.dataGlobal->TimeStepZone) + 0.5);
     594            0 :                     this->CTFTimeStep = state.dataGlobal->TimeStepZone * double(this->NumHistories);
     595              : 
     596              :                 } else {
     597              : 
     598              :                     // CTFTimeStep smaller than TimeStepZone:  Set to TimeStepZone
     599          146 :                     this->CTFTimeStep = state.dataGlobal->TimeStepZone;
     600          146 :                     this->NumHistories = 1;
     601              :                 }
     602              :             }
     603              : 
     604              :             // Calculate the CTFs using the state space method
     605              :             // outlined in Seem's dissertation.  The main matrices
     606              :             // AMat, BMat, CMat, and DMat must be derived from
     607              :             // applying a finite difference network to the layers of
     608              :             // each bldg element.
     609              : 
     610              :             // This section must continue looping until the CTFs
     611              :             // calculated here will produce a stable solution (less
     612              :             // history terms than MaxCTFTerms).
     613              : 
     614              :             // This first subsection calculates the elements of AMat
     615              :             // which characterizes the heat transfer inside the
     616              :             // building element.
     617              : 
     618              :             // Initialize loop control logical
     619              :             // Set after CTFs are calculated, based on whether there are too many CTF terms
     620          146 :             bool CTFConvrg = false;
     621              : 
     622          146 :             this->AExp.allocate(this->rcmax, this->rcmax);
     623          146 :             this->AExp = 0.0;
     624          146 :             this->AMat.allocate(this->rcmax, this->rcmax);
     625          146 :             this->AMat = 0.0;
     626          146 :             this->AInv.allocate(this->rcmax, this->rcmax);
     627          146 :             this->AInv = 0.0;
     628          146 :             this->IdenMatrix.allocate(this->rcmax, this->rcmax);
     629          146 :             this->IdenMatrix = 0.0;
     630         1731 :             for (int ir = 1; ir <= this->rcmax; ++ir) {
     631         1585 :                 this->IdenMatrix(ir, ir) = 1.0;
     632              :             }
     633          146 :             this->e.dimension(this->rcmax, 0.0);
     634          146 :             this->Gamma1.allocate(3, this->rcmax);
     635          146 :             this->Gamma1 = 0.0;
     636          146 :             this->Gamma2.allocate(3, this->rcmax);
     637          146 :             this->Gamma2 = 0.0;
     638          146 :             this->s.allocate(3, 4, this->rcmax);
     639          146 :             this->s = 0.0;
     640              : 
     641          292 :             while (!CTFConvrg) { // Begin CTF calculation loop ...
     642              : 
     643          146 :                 this->BMat(3) = 0.0;
     644              : 
     645          146 :                 if (this->SolutionDimensions == 1) {
     646              : 
     647              :                     // Set up intermediate calculations for the first layer.
     648          146 :                     cap = rho(1) * cp(1) * dx(1);
     649          146 :                     cap *= 1.5; // For the first node, account for the fact that the
     650              :                     // half-node at the surface results in a "loss" of some
     651              :                     // thermal mass.  Therefore, for simplicity, include it
     652              :                     // at this node.  Same thing done at the last node...
     653          146 :                     dxtmp = 1.0 / dx(1) / cap;
     654              : 
     655          146 :                     this->AMat(1, 1) = -2.0 * rk(1) * dxtmp; // Assign the matrix values for the
     656          146 :                     this->AMat(2, 1) = rk(1) * dxtmp;        // first node.
     657          146 :                     this->BMat(1) = rk(1) * dxtmp;           // Assign non-zero value of BMat.
     658              : 
     659          146 :                     int Layer = 1; // Initialize the "layer" counter
     660              : 
     661          146 :                     int NodeInLayer = 2; // Initialize the node (in a layer) counter (already
     662              :                     // on the second node for the first layer
     663              : 
     664         1439 :                     for (int Node = 2; Node <= this->rcmax - 1; ++Node) { // Begin nodes loop (includes all nodes except the
     665              :                         // first/last which have special equations) ...
     666              : 
     667         1293 :                         if ((NodeInLayer == Nodes(Layer)) && (LayersInConstruct != 1)) { // For a node at
     668              :                             // the interface between two adjacent layers, the
     669              :                             // capacitance of the node must be calculated from the 2
     670              :                             // halves which may be made up of 2 different materials.
     671              : 
     672          152 :                             cap = (rho(Layer) * cp(Layer) * dx(Layer) + rho(Layer + 1) * cp(Layer + 1) * dx(Layer + 1)) * 0.5;
     673              : 
     674          152 :                             this->AMat(Node - 1, Node) = rk(Layer) / dx(Layer) / cap;                                      // Assign matrix
     675          152 :                             this->AMat(Node, Node) = -1.0 * (rk(Layer) / dx(Layer) + rk(Layer + 1) / dx(Layer + 1)) / cap; // values for | the current
     676          152 :                             this->AMat(Node + 1, Node) = rk(Layer + 1) / dx(Layer + 1) / cap;                              // node.
     677              : 
     678          152 :                             NodeInLayer = 0; // At an interface, reset nodes in layer counter
     679          152 :                             ++Layer;         // Also increment the layer counter
     680              : 
     681              :                         } else { // Standard node within any layer
     682              : 
     683         1141 :                             cap = rho(Layer) * cp(Layer) * dx(Layer);          // Intermediate
     684         1141 :                             dxtmp = 1.0 / dx(Layer) / cap;                     // calculations.
     685         1141 :                             this->AMat(Node - 1, Node) = rk(Layer) * dxtmp;    // Assign matrix
     686         1141 :                             this->AMat(Node, Node) = -2.0 * rk(Layer) * dxtmp; // values for the
     687         1141 :                             this->AMat(Node + 1, Node) = rk(Layer) * dxtmp;    // current node.
     688              :                         }
     689              : 
     690         1293 :                         ++NodeInLayer; // Increment nodes in layer counter
     691         1293 :                         if (Node == this->NodeSource) this->BMat(3) = 1.0 / cap;
     692              : 
     693              :                     } // ... end of nodes loop.
     694              : 
     695              :                     // Intermediate calculations for the last node.
     696          146 :                     cap = rho(LayersInConstruct) * cp(LayersInConstruct) * dx(LayersInConstruct);
     697          146 :                     cap *= 1.5; // For the last node, account for the fact that the
     698              :                     // half-node at the surface results in a "loss" of some
     699              :                     // thermal mass.  Therefore, for simplicity, include it
     700              :                     // at this node.  Same thing done at the first node...
     701          146 :                     dxtmp = 1.0 / dx(LayersInConstruct) / cap;
     702              : 
     703          146 :                     this->AMat(this->rcmax, this->rcmax) = -2.0 * rk(LayersInConstruct) * dxtmp; // Assign matrix
     704          146 :                     this->AMat(this->rcmax - 1, this->rcmax) = rk(LayersInConstruct) * dxtmp;    // values for the
     705          146 :                     this->BMat(2) = rk(LayersInConstruct) * dxtmp;                               // last node.
     706              : 
     707          146 :                     this->CMat(1) = -rk(1) / dx(1);                                 // Compute the necessary elements
     708          146 :                     this->CMat(2) = rk(LayersInConstruct) / dx(LayersInConstruct);  // of all other
     709          146 :                     this->DMat(1) = rk(1) / dx(1);                                  // matrices for the state
     710          146 :                     this->DMat(2) = -rk(LayersInConstruct) / dx(LayersInConstruct); // space method
     711              : 
     712              :                 } else { // 2-D solution requested (assign matrices appropriately)
     713              : 
     714              :                     // As with the 1-D solution, we are accounting for the thermal mass
     715              :                     // of the half-node at the surface by adding it to the first row
     716              :                     // of interior nodes at both sides of the construction.  This is not
     717              :                     // exact, but it does take all of the thermal mass into account.
     718            0 :                     amatx = rk(1) / (1.5 * rho(1) * cp(1) * dx(1) * dx(1));
     719            0 :                     amaty = rk(1) / (1.5 * rho(1) * cp(1) * dyn * dyn);
     720              : 
     721              :                     // FIRST ROW OF NODES: This first row within the first material layer
     722              :                     // is special in that it is exposed to a boundary condition.  Thus,
     723              :                     // the equations are slightly different.
     724              :                     // Note also that the first and last nodes in a row are slightly
     725              :                     // different from the rest since they are on an adiabatic plane in
     726              :                     // the direction perpendicular to the main direction of heat transfer.
     727            0 :                     this->AMat(1, 1) = -2.0 * (amatx + amaty);
     728            0 :                     this->AMat(2, 1) = 2.0 * amaty;
     729            0 :                     this->AMat(this->NumOfPerpendNodes + 1, 1) = amatx;
     730              : 
     731            0 :                     for (int Node = 2; Node <= this->NumOfPerpendNodes - 1; ++Node) {
     732            0 :                         this->AMat(Node - 1, Node) = amaty;
     733            0 :                         this->AMat(Node, Node) = -2.0 * (amatx + amaty);
     734            0 :                         this->AMat(Node + 1, Node) = amaty;
     735            0 :                         this->AMat(Node + this->NumOfPerpendNodes, Node) = amatx;
     736              :                     }
     737              : 
     738            0 :                     this->AMat(this->NumOfPerpendNodes, this->NumOfPerpendNodes) = -2.0 * (amatx + amaty);
     739            0 :                     this->AMat(this->NumOfPerpendNodes - 1, this->NumOfPerpendNodes) = 2.0 * amaty;
     740            0 :                     this->AMat(this->NumOfPerpendNodes + this->NumOfPerpendNodes, this->NumOfPerpendNodes) = amatx;
     741              : 
     742            0 :                     BMat(1) = amatx;
     743              : 
     744            0 :                     int Layer = 1;
     745            0 :                     int NodeInLayer = 2;
     746            0 :                     amatx = rk(1) / (rho(1) * cp(1) * dx(1) * dx(1)); // Reset these to the normal capacitance
     747            0 :                     amaty = rk(1) / (rho(1) * cp(1) * dyn * dyn);     // Reset these to the normal capacitance
     748            0 :                     assert(this->NumOfPerpendNodes > 0);              // Autodesk:F2C++ Loop setup assumption
     749            0 :                     int const Node_stop(this->rcmax + 1 - 2 * this->NumOfPerpendNodes);
     750            0 :                     for (int Node = this->NumOfPerpendNodes + 1; Node <= Node_stop; Node += this->NumOfPerpendNodes) {
     751              :                         // INTERNAL ROWS OF NODES: This is the majority of nodes which are all within
     752              :                         // a solid layer and not exposed to a boundary condition.
     753            0 :                         if ((LayersInConstruct == 1) || (NodeInLayer != Nodes(Layer))) {
     754              :                             // Single material row: This row of nodes are all contained within a material
     755              :                             // and thus there is no special considerations necessary.
     756            0 :                             if (NodeInLayer == 1) {
     757              :                                 // These intermediate variables only need to be reassigned when a new layer is started.
     758              :                                 // When this is simply another row of the same material, these have already been assigned correctly.
     759            0 :                                 amatx = rk(Layer) / (rho(Layer) * cp(Layer) * dx(Layer) * dx(Layer));
     760            0 :                                 amaty = rk(Layer) / (rho(Layer) * cp(Layer) * dyn * dyn);
     761              :                             }
     762              : 
     763              :                             // Note that the first and last layers in a row are slightly different
     764              :                             // from the rest since they are on an adiabatic plane in the direction
     765              :                             // perpendicular to the main direction of heat transfer.
     766            0 :                             this->AMat(Node, Node) = -2.0 * (amatx + amaty);
     767            0 :                             this->AMat(Node + 1, Node) = 2.0 * amaty;
     768            0 :                             this->AMat(Node - this->NumOfPerpendNodes, Node) = amatx;
     769            0 :                             this->AMat(Node + this->NumOfPerpendNodes, Node) = amatx;
     770              : 
     771            0 :                             for (int NodeInRow = 2; NodeInRow <= this->NumOfPerpendNodes - 1; ++NodeInRow) {
     772            0 :                                 int Node2 = Node + NodeInRow - 1;
     773            0 :                                 this->AMat(Node2 - 1, Node2) = amaty;
     774            0 :                                 this->AMat(Node2, Node2) = -2.0 * (amatx + amaty);
     775            0 :                                 this->AMat(Node2 + 1, Node2) = amaty;
     776            0 :                                 this->AMat(Node2 - this->NumOfPerpendNodes, Node2) = amatx;
     777            0 :                                 this->AMat(Node2 + this->NumOfPerpendNodes, Node2) = amatx;
     778              :                             }
     779              : 
     780            0 :                             int Node2 = Node - 1 + this->NumOfPerpendNodes;
     781            0 :                             this->AMat(Node2, Node2) = -2.0 * (amatx + amaty);
     782            0 :                             this->AMat(Node2 - 1, Node2) = 2.0 * amaty;
     783            0 :                             this->AMat(Node2 - this->NumOfPerpendNodes, Node2) = amatx;
     784            0 :                             this->AMat(Node2 + this->NumOfPerpendNodes, Node2) = amatx;
     785              : 
     786              :                         } else { // Row at a two-layer interface (half of node consists of one layer's materials
     787              :                             // and the other half consist of the next layer's materials)
     788            0 :                             capavg = 0.5 * (rho(Layer) * cp(Layer) * dx(Layer) + rho(Layer + 1) * cp(Layer + 1) * dx(Layer + 1));
     789            0 :                             amatx = rk(Layer) / (capavg * dx(Layer));
     790            0 :                             amatxx = rk(Layer + 1) / (capavg * dx(Layer + 1));
     791            0 :                             amaty = (rk(Layer) * dx(Layer) + rk(Layer + 1) * dx(Layer + 1)) / (capavg * dyn * dyn);
     792              : 
     793            0 :                             this->AMat(Node, Node) = -amatx - amatxx - 2.0 * amaty;
     794            0 :                             this->AMat(Node + 1, Node) = 2.0 * amaty;
     795            0 :                             this->AMat(Node - this->NumOfPerpendNodes, Node) = amatx;
     796            0 :                             this->AMat(Node + this->NumOfPerpendNodes, Node) = amatxx;
     797              : 
     798            0 :                             for (int NodeInRow = 2; NodeInRow <= this->NumOfPerpendNodes - 1; ++NodeInRow) {
     799            0 :                                 int Node2 = Node + NodeInRow - 1;
     800            0 :                                 this->AMat(Node2 - 1, Node2) = amaty;
     801            0 :                                 this->AMat(Node2, Node2) = -amatx - amatxx - 2.0 * amaty;
     802            0 :                                 this->AMat(Node2 + 1, Node2) = amaty;
     803            0 :                                 this->AMat(Node2 - this->NumOfPerpendNodes, Node2) = amatx;
     804            0 :                                 this->AMat(Node2 + this->NumOfPerpendNodes, Node2) = amatxx;
     805              :                             }
     806              : 
     807            0 :                             int Node2 = Node - 1 + this->NumOfPerpendNodes;
     808            0 :                             this->AMat(Node2, Node2) = -amatx - amatxx - 2.0 * amaty;
     809            0 :                             this->AMat(Node2 - 1, Node2) = 2.0 * amaty;
     810            0 :                             this->AMat(Node2 - this->NumOfPerpendNodes, Node2) = amatx;
     811            0 :                             this->AMat(Node2 + this->NumOfPerpendNodes, Node2) = amatxx;
     812              : 
     813            0 :                             if (Node == this->NodeSource) BMat(3) = 2.0 * double(this->NumOfPerpendNodes - 1) / capavg;
     814            0 :                             NodeInLayer = 0;
     815            0 :                             ++Layer;
     816              :                         }
     817            0 :                         ++NodeInLayer;
     818              :                     }
     819              : 
     820              :                     // LAST ROW OF NODES: Like the first row of nodes, this row is exposed to a boundary
     821              :                     // condition and thus has slightly modified nodal equations.
     822              : 
     823              :                     // As with the 1-D solution, we are accounting for the thermal mass
     824              :                     // of the half-node at the surface by adding it to the first row
     825              :                     // of interior nodes at both sides of the construction.  This is not
     826              :                     // exact, but it does take all of the thermal mass into account.
     827            0 :                     amatx /= 1.5;
     828            0 :                     amaty /= 1.5;
     829              : 
     830            0 :                     int Node = this->rcmax + 1 - this->NumOfPerpendNodes;
     831            0 :                     this->AMat(Node, Node) = -2.0 * (amatx + amaty);
     832            0 :                     this->AMat(Node + 1, Node) = 2.0 * amaty;
     833            0 :                     this->AMat(Node - this->NumOfPerpendNodes, Node) = amatx;
     834              : 
     835            0 :                     for (int thisNode = this->rcmax + 2 - this->NumOfPerpendNodes; thisNode <= this->rcmax - 1; ++thisNode) {
     836            0 :                         this->AMat(thisNode - 1, thisNode) = amaty;
     837            0 :                         this->AMat(thisNode, thisNode) = -2.0 * (amatx + amaty);
     838            0 :                         this->AMat(thisNode + 1, thisNode) = amaty;
     839            0 :                         this->AMat(thisNode - this->NumOfPerpendNodes, thisNode) = amatx;
     840              :                     }
     841              : 
     842            0 :                     this->AMat(this->rcmax, this->rcmax) = -2.0 * (amatx + amaty);
     843            0 :                     this->AMat(this->rcmax - 1, this->rcmax) = 2.0 * amaty;
     844            0 :                     this->AMat(this->rcmax - this->NumOfPerpendNodes, this->rcmax) = amatx;
     845              : 
     846            0 :                     this->BMat(2) = amatx;
     847              : 
     848            0 :                     this->CMat(1) = -rk(1) / dx(1) / double(this->NumOfPerpendNodes - 1);
     849            0 :                     this->CMat(2) = rk(LayersInConstruct) / dx(LayersInConstruct) / double(this->NumOfPerpendNodes - 1);
     850              : 
     851            0 :                     this->DMat(1) = rk(1) / dx(1) / double(this->NumOfPerpendNodes - 1);
     852            0 :                     this->DMat(2) = -rk(LayersInConstruct) / dx(LayersInConstruct) / double(this->NumOfPerpendNodes - 1);
     853              :                 }
     854              : 
     855              :                 // Calculation of the CTFs based on the state space
     856              :                 // method.  This process involves finding the exponential
     857              :                 // and inverse of AMat and using these results to
     858              :                 // determine the CTFs.  The Gammas are an intermediate
     859              :                 // calculations which are necessary before the CTFs can
     860              :                 // be computed in TransFuncCoeffs.
     861          146 :                 DisplayString(state, "Calculating CTFs for \"" + this->Name + "\"");
     862              : 
     863              :                 //          CALL DisplayNumberAndString(ConstrNum,'Matrix exponential for Construction #')
     864          146 :                 this->calculateExponentialMatrix(); // Compute exponential of AMat
     865              : 
     866              :                 //          CALL DisplayNumberAndString(ConstrNum,'Invert Matrix for Construction #')
     867          146 :                 this->calculateInverseMatrix(); // Compute inverse of AMat
     868              : 
     869              :                 //          CALL DisplayNumberAndString(ConstrNum,'Gamma calculation for Construction #')
     870          146 :                 this->calculateGammas();
     871              :                 // Compute "gamma"s from AMat, AExp, and AInv
     872              : 
     873              :                 //          CALL DisplayNumberAndString(ConstrNum,'Compute CTFs for Construction #')
     874          146 :                 this->calculateFinalCoefficients(); // Compute CTFs
     875              : 
     876              :                 // Now check to see if the number of transfer functions
     877              :                 // is greater than MaxCTFTerms.  If it is, then increase the
     878              :                 // time step and the number of history terms and
     879              :                 // recalculate.  Whether or not it will be necessary to
     880              :                 // recalculate the CTFs is controlled by this DO WHILE
     881              :                 // loop and the logical CTFConvrg.
     882              : 
     883          146 :                 CTFConvrg = true; // Assume solution convergence
     884              : 
     885              :                 // If too many terms, then solution did not converge.  Increase the
     886              :                 // number of histories and the time step.  Reset CTFConvrg to continue
     887              :                 // the DO loop.
     888          146 :                 if (this->NumCTFTerms > (Construction::MaxCTFTerms - 1)) {
     889            0 :                     ++this->NumHistories;
     890            0 :                     this->CTFTimeStep += state.dataGlobal->TimeStepZone;
     891            0 :                     CTFConvrg = false;
     892              :                 }
     893              : 
     894              :                 // If the number of terms is okay, then do a further check on the summation of
     895              :                 // the various series summations.  In theory, Sum(Xi) = Sum(Yi) = Sum(Zi).  If
     896              :                 // this is not the case, then the terms have not reached a valid solution, and
     897              :                 // we need to increase the number of histories and the time step as above.
     898          146 :                 if (CTFConvrg) {
     899          146 :                     SumXi = this->s0(2, 2);
     900          146 :                     SumYi = this->s0(1, 2);
     901          146 :                     SumZi = this->s0(1, 1);
     902         1057 :                     for (int HistTerm = 1; HistTerm <= this->NumCTFTerms; ++HistTerm) {
     903          911 :                         SumXi += this->s(2, 2, HistTerm);
     904          911 :                         SumYi += this->s(1, 2, HistTerm);
     905          911 :                         SumZi += this->s(1, 1, HistTerm);
     906              :                     }
     907          146 :                     SumXi = std::abs(SumXi);
     908          146 :                     SumYi = std::abs(SumYi);
     909          146 :                     SumZi = std::abs(SumZi);
     910          146 :                     BiggestSum = max(SumXi, SumYi, SumZi);
     911          146 :                     if (BiggestSum > 0.0) {
     912          292 :                         if (((std::abs(SumXi - SumYi) / BiggestSum) > MaxAllowedCTFSumError) ||
     913          146 :                             ((std::abs(SumZi - SumYi) / BiggestSum) > MaxAllowedCTFSumError)) {
     914            0 :                             ++this->NumHistories;
     915            0 :                             this->CTFTimeStep += state.dataGlobal->TimeStepZone;
     916            0 :                             CTFConvrg = false;
     917              :                         }
     918              :                     } else { // Something terribly wrong--the surface has no CTFs, not even an R-value
     919            0 :                         ShowFatalError(state, format("Illegal construction definition, no CTFs calculated for {}", this->Name));
     920              :                     }
     921              :                 }
     922              : 
     923              :                 // Once the time step has reached a certain point, it is highly likely that
     924              :                 // there is either a problem with the input or the solution.  This should
     925              :                 // be extremely rare since other checks should flag most bad user input.
     926              :                 // Thus, if the time step reaches a certain point, error out and let the
     927              :                 // user know that something needs to be checked in the input file.
     928          146 :                 if (this->CTFTimeStep >= MaxAllowedTimeStep) {
     929            0 :                     ShowSevereError(state, format("CTF calculation convergence problem for Construction=\"{}\".", this->Name));
     930            0 :                     ShowContinueError(state, "...with Materials (outside layer to inside)");
     931            0 :                     ShowContinueError(state, format("(outside)=\"{}\"", state.dataMaterial->materials(this->LayerPoint(1))->Name));
     932            0 :                     for (int Layer = 2; Layer <= this->TotLayers; ++Layer) {
     933            0 :                         if (Layer != this->TotLayers) {
     934            0 :                             ShowContinueError(state, format("(next)=\"{}\"", state.dataMaterial->materials(this->LayerPoint(Layer))->Name));
     935              :                         } else {
     936            0 :                             ShowContinueError(state, format("(inside)=\"{}\"", state.dataMaterial->materials(this->LayerPoint(Layer))->Name));
     937              :                         }
     938              :                     }
     939            0 :                     ShowContinueError(state,
     940              :                                       "The Construction report will be produced. This will show more details on Constructions and their materials.");
     941            0 :                     ShowContinueError(state, "Attempts will be made to complete the CTF process but the report may be incomplete.");
     942            0 :                     ShowContinueError(state, "Constructs reported after this construction may appear to have all 0 CTFs.");
     943            0 :                     ShowContinueError(state, "The potential causes of this problem are related to the input for the construction");
     944            0 :                     ShowContinueError(state, "listed in the severe error above.  The CTF calculate routine is unable to come up");
     945            0 :                     ShowContinueError(state, "with a series of CTF terms that have a reasonable time step and this indicates an");
     946            0 :                     ShowContinueError(state, "error.  Check the definition of this construction and the materials that make up");
     947            0 :                     ShowContinueError(state, "the construction.  Very thin, highly conductive materials may cause problems.");
     948            0 :                     ShowContinueError(state, "This may be avoided by ignoring the presence of those materials since they probably");
     949            0 :                     ShowContinueError(state, "do not effect the heat transfer characteristics of the construction.  Highly");
     950            0 :                     ShowContinueError(state, "conductive or highly resistive layers that are alternated with high mass layers");
     951            0 :                     ShowContinueError(state, "may also result in problems.  After confirming that the input is correct and");
     952            0 :                     ShowContinueError(state, "realistic, the user should contact the EnergyPlus support team.");
     953            0 :                     DoCTFErrorReport = true;
     954            0 :                     ErrorsFound = true;
     955            0 :                     break;
     956              :                     //            CALL ShowFatalError(state, 'Program terminated for reasons listed (InitConductionTransferFunctions) ')
     957              :                 }
     958              : 
     959              :             } // ... end of CTF calculation loop.
     960              : 
     961              :         } // ... end of IF block for non-reversed constructs.
     962              : 
     963              :     } else { // Construct has only resistive layers (no thermal mass).
     964              :         // CTF calculation not necessary, overall resistance
     965              :         // (R-value) is all that is needed.
     966              : 
     967              :         // Set time step for construct to user time step and the number of
     968              :         // inter-time step interpolations to 1
     969           97 :         this->CTFTimeStep = state.dataGlobal->TimeStepZone;
     970           97 :         this->NumHistories = 1;
     971           97 :         this->NumCTFTerms = 1;
     972              : 
     973           97 :         this->s0(1, 1) = cnd;  // CTFs for current time
     974           97 :         this->s0(2, 1) = -cnd; // step are set to the
     975           97 :         this->s0(1, 2) = cnd;  // overall conductance
     976           97 :         this->s0(2, 2) = -cnd; // of the construction.
     977              : 
     978           97 :         this->e.allocate(1);
     979           97 :         this->e = 0.0;
     980           97 :         this->s.allocate(2, 2, 1);
     981           97 :         this->s = 0.0;
     982           97 :         this->s(1, 1, 1) = 0.0; // CTF temperature
     983           97 :         this->s(2, 1, 1) = 0.0; // and flux
     984           97 :         this->s(1, 2, 1) = 0.0; // history terms
     985           97 :         this->s(2, 2, 1) = 0.0; // are all
     986           97 :         this->e(1) = 0.0;       // zero.
     987              : 
     988           97 :         if (this->SourceSinkPresent) {
     989            0 :             ShowSevereError(state, format("Sources/sinks not allowed in purely resistive constructions --> {}", this->Name));
     990            0 :             ErrorsFound = true;
     991              :         }
     992              : 
     993           97 :         RevConst = false; // In the code that follows, handle a resistive
     994              :         // layer as a non-reversed this->
     995              : 
     996              :     } // ... end of resistive construction IF block.
     997              : 
     998              :     // Transfer the CTFs to the storage arrays for all non-reversed
     999              :     // constructions.  This transfer was done earlier in the routine for
    1000              :     // reversed constructions.
    1001              : 
    1002          246 :     if (!RevConst) { // If this is either a new construction or a non-
    1003              :         // reversed construction, the CTFs must be stored
    1004              :         // in the proper arrays.  If this is a reversed
    1005              :         // construction, nothing further needs to be done.
    1006              : 
    1007              :         // Copy the CTFs into the storage arrays, converting them back to SI
    1008              :         // units in the process.  First the "zero" terms and then the history terms...
    1009          243 :         this->CTFOutside[0] = this->s0(1, 1) * DataConversions::CFU;
    1010          243 :         this->CTFCross[0] = this->s0(1, 2) * DataConversions::CFU;
    1011          243 :         this->CTFInside[0] = -this->s0(2, 2) * DataConversions::CFU;
    1012          243 :         if (this->SourceSinkPresent) {
    1013              :             // QTFs...
    1014            0 :             this->CTFSourceOut[0] = this->s0(3, 1);
    1015            0 :             this->CTFSourceIn[0] = this->s0(3, 2);
    1016              :             // QTFs for temperature calculation at source/sink location
    1017            0 :             this->CTFTSourceOut[0] = this->s0(1, 3);
    1018            0 :             this->CTFTSourceIn[0] = this->s0(2, 3);
    1019            0 :             this->CTFTSourceQ[0] = this->s0(3, 3) / DataConversions::CFU;
    1020            0 :             if (this->TempAfterLayer != 0) {
    1021              :                 // QTFs for user specified interior temperature calculations...
    1022            0 :                 this->CTFTUserOut[0] = this->s0(1, 4);
    1023            0 :                 this->CTFTUserIn[0] = this->s0(2, 4);
    1024            0 :                 this->CTFTUserSource[0] = this->s0(3, 4) / DataConversions::CFU;
    1025              :             }
    1026              :         }
    1027              : 
    1028         1251 :         for (int HistTerm = 1; HistTerm <= this->NumCTFTerms; ++HistTerm) {
    1029              :             // "REGULAR" CTFs...
    1030         1008 :             this->CTFOutside[HistTerm] = this->s(1, 1, HistTerm) * DataConversions::CFU;
    1031         1008 :             this->CTFCross[HistTerm] = this->s(1, 2, HistTerm) * DataConversions::CFU;
    1032         1008 :             this->CTFInside[HistTerm] = -this->s(2, 2, HistTerm) * DataConversions::CFU;
    1033         1008 :             if (HistTerm != 0) this->CTFFlux[HistTerm] = -e(HistTerm);
    1034         1008 :             if (this->SourceSinkPresent) {
    1035              :                 // QTFs...
    1036            0 :                 this->CTFSourceOut[HistTerm] = this->s(3, 1, HistTerm);
    1037            0 :                 this->CTFSourceIn[HistTerm] = this->s(3, 2, HistTerm);
    1038              :                 // QTFs for temperature calculation at source/sink location
    1039            0 :                 this->CTFTSourceOut[HistTerm] = this->s(1, 3, HistTerm);
    1040            0 :                 this->CTFTSourceIn[HistTerm] = this->s(2, 3, HistTerm);
    1041            0 :                 this->CTFTSourceQ[HistTerm] = this->s(3, 3, HistTerm) / DataConversions::CFU;
    1042            0 :                 if (this->TempAfterLayer != 0) {
    1043              :                     // QTFs for user specified interior temperature calculations...
    1044            0 :                     this->CTFTUserOut[HistTerm] = this->s(1, 4, HistTerm);
    1045            0 :                     this->CTFTUserIn[HistTerm] = this->s(2, 4, HistTerm);
    1046            0 :                     this->CTFTUserSource[HistTerm] = this->s(3, 4, HistTerm) / DataConversions::CFU;
    1047              :                 }
    1048              :             }
    1049              :         }
    1050              : 
    1051              :     } // ... end of the reversed construction IF block.
    1052              : 
    1053          246 :     this->UValue = cnd * DataConversions::CFU;
    1054              : 
    1055          246 :     if (allocated(this->AExp)) this->AExp.deallocate();
    1056          246 :     if (allocated(this->AMat)) AMat.deallocate();
    1057          246 :     if (allocated(this->AInv)) this->AInv.deallocate();
    1058          246 :     if (allocated(this->IdenMatrix)) this->IdenMatrix.deallocate();
    1059          246 :     if (allocated(this->e)) this->e.deallocate();
    1060          246 :     if (allocated(this->Gamma1)) this->Gamma1.deallocate();
    1061          246 :     if (allocated(this->Gamma2)) this->Gamma2.deallocate();
    1062          246 :     if (allocated(this->s)) this->s.deallocate();
    1063          246 : }
    1064              : 
    1065          146 : void ConstructionProps::calculateExponentialMatrix()
    1066              : {
    1067              : 
    1068              :     // SUBROUTINE INFORMATION:
    1069              :     //       AUTHOR         Russ Taylor
    1070              :     //       DATE WRITTEN   June 1990
    1071              :     //       MODIFIED       Dec 1995, Apr 1996, RKS; June 2000 RKS
    1072              :     //       RE-ENGINEERED  June 1996, RKS; Nov 1999, LKL;
    1073              : 
    1074              :     // PURPOSE OF THIS SUBROUTINE:
    1075              :     // This subroutine computes the exponential matrix exp(AMat*delt) for
    1076              :     // use in the state space method for the calculation of CTFs.
    1077              : 
    1078              :     // METHODOLOGY EMPLOYED:
    1079              :     // Uses the method of Taylor expansion combined with scaling and
    1080              :     // squaring to most efficiently compute the exponential matrix.  The
    1081              :     // steps in the procedure are outlined in Seem's dissertation in
    1082              :     // Appendix A, page 128.  Exponential matrix multiplication modified
    1083              :     // to take advantage of the characteristic form of AMat.  AMat starts
    1084              :     // out as a tri-diagonal matrix.  Each time AMat is raised to a higher
    1085              :     // power two extra non-zero diagonals are added.  ExponMatrix now
    1086              :     // recognizes this.  This should speed up the calcs somewhat.  Also, a
    1087              :     // new cut-off criteria based on the significant figures of double-
    1088              :     // precision variables has been added.  The main loop for higher powers
    1089              :     // of AMat is now stopped whenever these powers of AMat will no longer
    1090              :     // add to the summation (AExp) instead ofstopping potentially at the
    1091              :     // artifical limit of AMat**100.
    1092              : 
    1093              :     // REFERENCES:
    1094              :     // Seem, J.E.  "Modeling of Heat Transfer in Buildings",
    1095              :     //  Department of Mechanical Engineering, University of
    1096              :     //  Wisconsin-Madison, 1987.
    1097              :     // Strand, R.K. "Testing Design Description for the CTF
    1098              :     //  Calculation Code in BEST", BSO internal document,
    1099              :     //  May/June 1996.
    1100              : 
    1101          146 :     constexpr Real64 DPLimit(1.0e-20);
    1102              :     // This argument is nice, but not sure it's accurate -- LKL Nov 1999.
    1103              :     // Parameter set to the significant figures limit of double
    1104              :     // precision variables plus a safety factor.- The argument for setting this parameter to 1E-20 involves the
    1105              :     // number of significant figures for REAL(r64) variables which is 16 and the largest power to which
    1106              :     // AMat will be raised which is 100.  This would be a factor of 1E-18.  A factor of "safety" of another 100
    1107              :     // arrives at the value chosen.  It is argued that if one number is 1E-16 larger than a second number, then
    1108              :     // adding the second to the first will not effect the first.  However, on the conservative side, there could
    1109              :     // be up to 100 numbers which might, added together, still could effect the original number.  Each
    1110              :     // successive power of AMat will have terms smaller than the previous power.  Thus, when the ratio between
    1111              :     // the terms of the latest power of AMat and the total (AExp) is less than DPLim, all further powers of
    1112              :     // AMat will have absolutely no effect on the REAL(r64) value of AExp.  Thus, there is no need to
    1113              :     // continue the calculation.  In effect, AExp has "converged".  In REAL(r64)ity, 1E-16 would probably guarantee
    1114              :     // convergence since AMat terms drop off quickly, but the extra powers allows for differences between
    1115              :     // computer platforms.
    1116              : 
    1117              :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    1118              :     Real64 AMatRowNorm;    // Row norm for AMat
    1119              :     Real64 AMatRowNormMax; // Largest row norm for AMat
    1120          146 :     Array2D<Real64> AMat1; // AMat factored by (delt/2^k)
    1121          146 :     Array2D<Real64> AMato; // AMat raised to the previous power (power of AMat1-1)
    1122          146 :     Array2D<Real64> AMatN; // Current value of AMat raised to power n (n = 1,2...)
    1123              :     Real64 CheckVal;       // Used to avoid possible overflow from Double->REAL(r64)->Integer
    1124              :     Real64 fact;           // Intermediate calculation variable (delt/2^k)
    1125              :     int i;                 // Loop counter
    1126              :     int ic;                // Loop counter
    1127              :     int ict;               // Loop counter
    1128              :     int idm;               // Loop counter
    1129              :     int ir;                // Loop counter
    1130              :     int isq;               // Loop counter
    1131              :     int j;                 // Loop counter
    1132              :     int k;                 // Power of 2 which is used to factor AMat
    1133              :     int l;                 // Theoretical power to which the A matrix must be
    1134              :     // raised to accurately calculate the exponential matrix
    1135              :     // when exponential calculation loop can be exited (i.e.
    1136              :     // the significant figure limit for REAL(r64)
    1137              :     // variables reached)
    1138              : 
    1139          146 :     AMat1.allocate(this->rcmax, this->rcmax);
    1140          146 :     AMato.allocate(this->rcmax, this->rcmax);
    1141          146 :     AMatN.allocate(this->rcmax, this->rcmax);
    1142              : 
    1143              :     // Subroutine initializations.  AMat is assigned to local variable AMat1 to
    1144              :     // avoid the corruption of the original AMat 2-d array.
    1145          146 :     AMat1 = AMat;
    1146              : 
    1147              :     //  Other arrays are initialized to zero.
    1148          146 :     this->AExp = 0.0;
    1149          146 :     AMato = 0.0;
    1150          146 :     AMatN = 0.0;
    1151              : 
    1152              :     // Step 1, page 128 (Seem's thesis):  Compute the matrix row norm.
    1153              :     // See equation (A.3) which states that the matrix row norm is the
    1154              :     // maximum summation of the elements in a row of AMat multiplied by
    1155              :     // the time step.
    1156              : 
    1157              :     // Note With change to row-major arrays "row" here now means "column"
    1158              : 
    1159          146 :     AMatRowNormMax = 0.0; // Start of Step 1 ...
    1160              : 
    1161         1731 :     for (i = 1; i <= this->rcmax; ++i) {
    1162              : 
    1163         1585 :         AMatRowNorm = 0.0;
    1164        24582 :         for (j = 1; j <= this->rcmax; ++j) {
    1165        22997 :             AMatRowNorm += std::abs(AMat1(j, i));
    1166              :         }
    1167              : 
    1168         1585 :         AMatRowNorm *= this->CTFTimeStep;
    1169              : 
    1170         1585 :         AMatRowNormMax = max(AMatRowNormMax, AMatRowNorm);
    1171              : 
    1172              :     } // ... end of Step 1.
    1173              : 
    1174              :     // Step 2, page 128:  Find smallest integer k such that
    1175              :     // AMatRowNormMax< = 2^k
    1176              : 
    1177          146 :     k = int(std::log(AMatRowNormMax) / std::log(2.0)) + 1; // Autodesk:Num Handle AMatRowNormMax=0
    1178              : 
    1179              :     // Step 3, page 128:  Divide (AMat*delt) by 2^k.  This section of code
    1180              :     // takes advantage of the fact that AMat is tridiagonal.  Thus, it
    1181              :     // only factors the elements of the AMat that are known to be non-zero.
    1182              : 
    1183          146 :     fact = this->CTFTimeStep / std::pow(2.0, k); // Start of Step 3 ...
    1184          146 :     AMat1 *= fact;                               // ... end of Step 3.
    1185              : 
    1186              :     // Step 4, page 128:  Calculate l, the highest power to which AMat
    1187              :     // must be taken theoretically to accurately calculate its exponential.
    1188              :     // This is based on a paper by Cadzow and Martens ("Discrete-Time and
    1189              :     // Computer Control Systems",Prentice-Hall, pp. 389-390, 1970).  This
    1190              :     // number is now used as the maximum power to which AMat must be
    1191              :     // raised in order to calculate the exponential matrix.  A new cut-off
    1192              :     // criteria based on the number of significant figures in a double-
    1193              :     // precision variable is used as a more practical limit on the
    1194              :     // exponentiation algorithm.  One thing to note is that the EnergyPlus
    1195              :     // code is slightly different here than what is presented in Seem's
    1196              :     // dissertation.  In Seem's dissertation, AMatRowNormMax (delta, the
    1197              :     // timestep, is already factored into this term above) is divided by
    1198              :     // 2^k with k defined in the code above.  Dividing AMatRowNormMax by
    1199              :     // 2^k would have the net effect of decreasing the value of
    1200              :     // AMatRowNormMax and thus potentially CheckVal as well.  l, the integer
    1201              :     // value of CheckVal, is used to define the number of terms in the
    1202              :     // exponential matrix of AMat that are required for an accurate estimation
    1203              :     // of that matrix.  In practice, additional terms probably won't have
    1204              :     // a large effect but in general should make the calculation MORE accurate.
    1205              :     // Also, if the new terms are too small, then as noted above the cut-off
    1206              :     // criteria will not use them.
    1207              : 
    1208          146 :     CheckVal = min(3.0 * AMatRowNormMax + 6.0, 100.0);
    1209          146 :     l = int(CheckVal);
    1210              : 
    1211              :     // Step 5, page 128:  Calculate the exponential.  First, add the
    1212              :     // linear term to the identity matrix.
    1213          146 :     this->AExp = AMat1 + this->IdenMatrix; // Start of Step 5 ...
    1214              : 
    1215              :     // Now, add successive terms to the expansion as per the standard
    1216              :     // exponential formula.  AMato contains the last "power" of AMat
    1217              :     // which saves the program from having to remultiply the entire power
    1218              :     // of AMat each time.  Since this is still the linear power of AMat,
    1219              :     // AMat1 is still tridiagonal in nature.
    1220          146 :     AMato = AMat1;
    1221              : 
    1222          146 :     i = 1; // Initialize the counter for the following DO loop
    1223              : 
    1224              :     // The following DO WHILE loop continues to raise AMat to successive
    1225              :     // powers and add it to the exponential matrix (AExp).
    1226         8935 :     while (i < l) { // Begin power raising loop ...
    1227              : 
    1228         8789 :         ++i;                     // Increment the loop counter
    1229         8789 :         bool SigFigLimit = true; // Set the significant factor limit flag
    1230              : 
    1231       135813 :         for (ir = 1; ir <= this->rcmax; ++ir) { // Begin matrix multiplication loop ...
    1232              :             // The following matrix multiplication could be "optimized" since
    1233              :             // for one-dimensional heat transfer AMat is 3-diagonal, AMat squared
    1234              :             // is 5-diagonal, etc.  However, the code can be much simpler if we
    1235              :             // ignore this fact and just do a generic matrix multiplication.
    1236              :             // For 2-D heat transfer, the number of off-diagonal non-zero terms
    1237              :             // is slightly more complicated as well.
    1238      2239182 :             for (ic = 1; ic <= this->rcmax; ++ic) {
    1239      2112158 :                 AMatN(ic, ir) = 0.0;
    1240     41050656 :                 for (ict = 1; ict <= this->rcmax; ++ict) {
    1241              :                     // Make sure the next term won't cause an underflow.  If it will end up being
    1242              :                     // so small as to go below TinyLimit, then ignore it since it won't add anything
    1243              :                     // to AMatN anyway.
    1244     38938498 :                     if (std::abs(AMat1(ic, ict)) > Constant::rTinyValue) {
    1245      6082426 :                         if (std::abs(AMato(ict, ir)) > std::abs(double(i) * Constant::rTinyValue / AMat1(ic, ict)))
    1246       282571 :                             AMatN(ic, ir) += AMato(ict, ir) * AMat1(ic, ict) / double(i);
    1247              :                     }
    1248              :                 }
    1249              :             }
    1250              :         } // ... end of matrix multiplication loop.
    1251              : 
    1252              :         // Update AMato and AExp matrices
    1253         8789 :         AMato = AMatN;
    1254         8789 :         this->AExp += AMato;
    1255              : 
    1256              :         // The next DO loop tests the significant figures limit criteria to
    1257              :         // see if any values in AExp are still changing appreciably.
    1258         9152 :         for (ir = 1; ir <= this->rcmax; ++ir) {
    1259        60671 :             for (ic = 1; ic <= this->rcmax; ++ic) {
    1260              :                 // Test of limit criteria:
    1261        60308 :                 if (std::abs(this->AExp(ic, ir)) > Constant::rTinyValue) { // Next line divides by AExp entry so it
    1262              :                     // must be checked to avoid dividing by zero.
    1263              :                     // If the ratio between any current element in the power
    1264              :                     // of AMat and its corresponding element in AExp is
    1265              :                     // greater than the number which might effect the overall
    1266              :                     // exponential matrix based on stability criteria, then
    1267              :                     // continue raising AMat to another power (SigFigLimit = false).
    1268              : 
    1269        53066 :                     if (std::abs(AMato(ic, ir) / this->AExp(ic, ir)) > DPLimit) {
    1270         1489 :                         SigFigLimit = false;
    1271         1489 :                         break; // DO loop (anytime SigFigLimit is false, AMat must continue to be raised another power)
    1272              :                     }
    1273              : 
    1274              :                 } else { // There are still elements of AExp which are zero, so
    1275              :                     // the raising of AMat to higher powers should continue.
    1276              : 
    1277         7242 :                     SigFigLimit = false;
    1278         7242 :                     break; // DO loop (anytime SigFigLimit is false, AMat must continue to be raised another power)
    1279              :                 }
    1280              :             }
    1281         9094 :             if (!SigFigLimit) break; // DO loop (anytime SigFigLimit is false, AMat must continue to be raised another power)
    1282              :         }
    1283              : 
    1284              :         // Compute next term, only if necessary.  If SigFigLimit is still true,
    1285              :         // then all of the new terms being added to AExp are too small to
    1286              :         // affect it.  Thus, there is no need to continue this do loop further.
    1287              : 
    1288         8789 :         if (SigFigLimit) i = 100; // SigFigLimit is still true, set i to maximum possible
    1289              :         // value of l (100).
    1290              : 
    1291              :     } // ... end of power raising loop and Step 5.
    1292              : 
    1293              :     // Step 6, page 128:
    1294              :     // Square AExp "k times" to obtain the actual exponential matrix
    1295              :     // (remember that AExp was scaled earlier in this routine).
    1296              : 
    1297         1459 :     for (isq = 1; isq <= k; ++isq) { // Begin squaring DO loop and Step 6 ...
    1298              : 
    1299              :         // Use AMato to store the old values of AExp
    1300         1314 :         AMato = this->AExp;
    1301         1314 :         bool Backup = true; // Used when numerics get to small in Exponentiation
    1302         1314 :         this->AExp = 0.0;
    1303              : 
    1304              :         // Multiply the old value of AExp (AMato) by itself and store in AExp.
    1305        17927 :         for (ir = 1; ir <= this->rcmax; ++ir) {
    1306       268680 :             for (ic = 1; ic <= this->rcmax; ++ic) {
    1307      4644252 :                 for (idm = 1; idm <= this->rcmax; ++idm) {
    1308      4392185 :                     if (std::abs(AMato(idm, ir) * AMato(ic, idm)) > Constant::rTinyValue) {
    1309      2168531 :                         this->AExp(ic, ir) += AMato(idm, ir) * AMato(ic, idm);
    1310      2168531 :                         Backup = false;
    1311              :                     }
    1312              :                 }
    1313              :             }
    1314              :         }
    1315              :         // Backup is true when every item of AExp didnt pass the TinyLimit test
    1316         1314 :         if (Backup) {
    1317            1 :             this->AExp = AMato;
    1318            1 :             break;
    1319              :         }
    1320              : 
    1321              :     } // ... end of squaring loop and Step 6.
    1322              : 
    1323          146 :     AMat1.deallocate();
    1324          146 :     AMato.deallocate();
    1325          146 :     AMatN.deallocate();
    1326          146 : }
    1327              : 
    1328          146 : void ConstructionProps::calculateInverseMatrix()
    1329              : {
    1330              : 
    1331              :     // SUBROUTINE INFORMATION:
    1332              :     //       AUTHOR         Rick Strand
    1333              :     //       DATE WRITTEN   Dec 1995
    1334              :     //       MODIFIED       June 2000 RKS (made routine generic to allow for 2-D solutions)
    1335              :     //       RE-ENGINEERED  June 1996, February 1997 RKS
    1336              : 
    1337              :     // PURPOSE OF THIS SUBROUTINE:
    1338              :     // This subroutine computes the inverse of AMat for use
    1339              :     // in the calculation of the CTFs.
    1340              : 
    1341              :     // METHODOLOGY EMPLOYED:
    1342              :     // Uses row elimination to zero the off-diagonal terms of
    1343              :     // AMat while performing the same operations on another
    1344              :     // matrix which starts as the identity matrix.  Once AMat
    1345              :     // has been converted to an identity matrix(I), the other
    1346              :     // matrix which started as the I will then be the inverse
    1347              :     // of A.  This algorithm has been customized for a
    1348              :     // tri-diagonal matrix.
    1349              : 
    1350              :     // REFERENCES:
    1351              :     // Any linear algebra test (this is a generic routine).
    1352              : 
    1353              :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    1354          146 :     Array2D<Real64> AMat1; // Intermediate calculation matrix equivalent at first to AMat
    1355              : 
    1356              :     // Subroutine initializations ...
    1357          146 :     AMat1.allocate(this->rcmax, this->rcmax);
    1358              : 
    1359          146 :     AMat1 = AMat;                  // Set AMat1 = AMat to avoid AMat changes
    1360          146 :     this->AInv = this->IdenMatrix; // Set AInv to Identity Matrix
    1361              : 
    1362              :     // Use Gaussian elimination to zero all of the elements of AMat left
    1363              :     // of the diagonal.
    1364              :     // This DO loop will cycle through each of the rows in AMat except the
    1365              :     // last row which is handled later because it does not have to be used
    1366              :     // to eliminate any other rows.  The index ir is the current row
    1367              :     // number and also the column of the current diagonal element.
    1368              : 
    1369         1585 :     for (int ir = 1; ir <= this->rcmax - 1; ++ir) { // Begin forward elimination loop ...
    1370              : 
    1371              :         // Factor all of the elements of the row being used to zero the next
    1372              :         // row in both AMat and AInv by the diagonal element of this row.
    1373              :         // We should only need to factor the elements to the right of the
    1374              :         // diagonal since those to the right of it should be zero.
    1375        12145 :         for (int ic = ir + 1; ic <= this->rcmax; ++ic) {
    1376        10706 :             AMat1(ic, ir) /= AMat1(ir, ir);
    1377              :         }
    1378              : 
    1379              :         // In the forward elimination process, all the elements in AInv to the
    1380              :         // right of the diagonal are zero so they do not need to be factored.
    1381        12145 :         for (int ic = 1; ic <= ir; ++ic) {
    1382        10706 :             this->AInv(ic, ir) /= AMat1(ir, ir);
    1383              :         }
    1384              : 
    1385         1439 :         AMat1(ir, ir) = 1.0; // By definition, the diagonal of AMat is now 1.
    1386              : 
    1387              :         // Use this factored row to eliminate the off-diagonal element of the
    1388              :         // rows below the current one (ir)...
    1389              : 
    1390        12145 :         for (int irr = ir + 1; irr <= this->rcmax; ++irr) { // Start of row reduction loop...
    1391              : 
    1392       133842 :             for (int ic = ir + 1; ic <= this->rcmax; ++ic) {
    1393       123136 :                 AMat1(ic, irr) -= AMat1(ir, irr) * AMat1(ic, ir);
    1394              :             }
    1395              : 
    1396              :             // Now, determine the effect on the next row of AInv.  Again, all of
    1397              :             // the elements in AInv to the right of the diagonal are zero, so they
    1398              :             // can be ignored.
    1399              : 
    1400        77627 :             for (int ic = 1; ic <= ir; ++ic) {
    1401        66921 :                 this->AInv(ic, irr) -= AMat1(ir, irr) * this->AInv(ic, ir);
    1402              :             }
    1403              : 
    1404        10706 :             AMat1(ir, irr) = 0.0; // By definition, the element to the left of the
    1405              :             // diagonal in the next row of AMat is now zero.
    1406              : 
    1407              :         } // ...end of row reduction loop
    1408              : 
    1409              :     } // ... end of the forward elimination loop.
    1410              : 
    1411              :     // Factor the last row of AInv by the current value of the last
    1412              :     // diagonal element of AMat. After this is done, all of the diagonal
    1413              :     // elements of AMat are unity and all of the elements in AMat left of
    1414              :     // the diagonal are zero.
    1415              : 
    1416         1731 :     for (int ic = 1; ic <= this->rcmax; ++ic) {
    1417         1585 :         this->AInv(ic, this->rcmax) /= AMat1(this->rcmax, this->rcmax);
    1418              :     }
    1419          146 :     AMat1(this->rcmax, this->rcmax) = 1.0;
    1420              : 
    1421              :     // Now, use back substitution to eliminate the elements to the right
    1422              :     // of the diagonal in AMat.  The procedure is similar to the forward
    1423              :     // elimination process except that we only have to operate on AInv,
    1424              :     // though now all of the columns of AInv may be non-zero.
    1425              : 
    1426              :     // This DO loop will cycle through the remaining rows which are not
    1427              :     // yet diagonalized in reverse order.  Note that the only effect on
    1428              :     // AMat is that the off-diagonal element is zeroed.  The diagonal
    1429              :     // (which has already been set to unity) is not effected by this row
    1430              :     // elimination process.
    1431              :     // In the following code ir is the column being zeroed and irr is the
    1432              :     // row being worked on
    1433              : 
    1434         1585 :     for (int ir = this->rcmax; ir >= 2; --ir) { // Begin reverse elimination loop ...
    1435        12145 :         for (int irr = 1; irr <= ir - 1; ++irr) {
    1436       200763 :             for (int ic = 1; ic <= this->rcmax; ++ic) {
    1437       190057 :                 this->AInv(ic, irr) -= AMat1(ir, irr) * this->AInv(ic, ir);
    1438              :             }
    1439        10706 :             AMat1(ir, irr) = 0.0;
    1440              :         }
    1441              :     } // ... end of reverse elimination loop.
    1442              : 
    1443              :     // At this point, AMat1 is equal to the identity matrix (I)
    1444              :     // and AInv is equal to the inverse of AMat.
    1445              : 
    1446          146 :     AMat1.deallocate();
    1447          146 : }
    1448              : 
    1449          146 : void ConstructionProps::calculateGammas()
    1450              : {
    1451              : 
    1452              :     // SUBROUTINE INFORMATION:
    1453              :     //       AUTHOR         Russ Taylor
    1454              :     //       DATE WRITTEN   June 1990
    1455              :     //       RE-ENGINEERED  July 1996, RKS
    1456              : 
    1457              :     // PURPOSE OF THIS SUBROUTINE:
    1458              :     // Compute gammas as defined in Seem's dissertation.
    1459              :     // Runs as a subroutine of the conduction transfer
    1460              :     // function solver (InitializeCTFs).
    1461              : 
    1462              :     // METHODOLOGY EMPLOYED:
    1463              :     // Determine the Gamma1 and Gamma2 based on the results
    1464              :     // from the ExponMatrix and InvertMatrix subroutines.
    1465              :     // This routine is specialized to take advantage of the
    1466              :     // fact that most of BMat consists of zeroes.
    1467              : 
    1468              :     // REFERENCES:
    1469              :     // The state space method of calculating CTFs is
    1470              :     // outlined in the doctoral dissertation of John Seem,
    1471              :     // "Modeling of Heat Transfer in Buildings", Department
    1472              :     // of Mechanical Engineering, University of Wisconsin-
    1473              :     // Madison, 1987.
    1474              : 
    1475              :     // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
    1476          146 :     Array2D<Real64> ATemp; // Intermediate variable equal to AExp - I
    1477              : 
    1478              :     // Compute Gamma1 from equation (2.1.12) in Seem's dissertation which
    1479              :     // states that:  Gamma1  =  [AInv] * ([AExp]-[I]) * [BMat]
    1480              :     // noting that BMat contains only the non-zero values of the B Matrix.
    1481              : 
    1482          146 :     ATemp.allocate(this->rcmax, this->rcmax);
    1483          146 :     ATemp = this->AExp - this->IdenMatrix;
    1484          146 :     Gamma1 = 0.0;
    1485              : 
    1486         1731 :     for (int i = 1; i <= this->rcmax; ++i) {
    1487              : 
    1488        24582 :         for (int is1 = 1; is1 <= this->rcmax; ++is1) {
    1489              : 
    1490        22997 :             if (this->SolutionDimensions == 1) {
    1491        22997 :                 this->Gamma1(1, i) += this->AInv(is1, i) * ATemp(1, is1) * this->BMat(1);
    1492        22997 :                 this->Gamma1(2, i) += this->AInv(is1, i) * ATemp(this->rcmax, is1) * this->BMat(2);
    1493              :             } else { // SolutionDimensions = 2
    1494            0 :                 for (int SurfNode = 1; SurfNode <= this->NumOfPerpendNodes; ++SurfNode) {
    1495            0 :                     this->Gamma1(1, i) += this->AInv(is1, i) * ATemp(SurfNode, is1) * this->BMat(1);
    1496            0 :                     this->Gamma1(2, i) += this->AInv(is1, i) * ATemp(this->rcmax + 1 - SurfNode, is1) * this->BMat(2);
    1497              :                 }
    1498              :             }
    1499              : 
    1500        22997 :             if (this->NodeSource > 0) {
    1501            0 :                 this->Gamma1(3, i) += this->AInv(is1, i) * ATemp(this->NodeSource, is1) * this->BMat(3);
    1502              :             }
    1503              :         }
    1504              :     }
    1505              : 
    1506          146 :     ATemp.deallocate();
    1507              :     // Compute Gamma2 from equation (2.1.13) in Seem's dissertation which
    1508              :     // states that:  Gamma2  =  [AInv] * ([Gamma1]/delt - [BMat])
    1509              :     // again noting that BMat contains only the non-zero values of B.
    1510          146 :     Gamma2 = 0.0;
    1511              : 
    1512         1731 :     for (int i = 1; i <= this->rcmax; ++i) {
    1513              : 
    1514         6340 :         for (int j = 1; j <= 3; ++j) {
    1515              : 
    1516        73746 :             for (int is1 = 1; is1 <= this->rcmax; ++is1) {
    1517              : 
    1518        68991 :                 if (this->SolutionDimensions == 1) {
    1519        68991 :                     if ((j == 1) && (is1 == 1)) {
    1520         1585 :                         this->Gamma2(j, i) += this->AInv(is1, i) * (this->Gamma1(j, is1) / this->CTFTimeStep - this->BMat(1));
    1521        67406 :                     } else if ((j == 2) && (is1 == this->rcmax)) {
    1522         1585 :                         this->Gamma2(j, i) += this->AInv(is1, i) * (this->Gamma1(j, is1) / this->CTFTimeStep - this->BMat(2));
    1523        65821 :                     } else if ((j == 3) && (is1 == this->NodeSource)) {
    1524            0 :                         this->Gamma2(j, i) += this->AInv(is1, i) * (this->Gamma1(j, is1) / this->CTFTimeStep - this->BMat(3));
    1525              :                     } else { // the element of the actual BMat is zero
    1526        65821 :                         this->Gamma2(j, i) += this->AInv(is1, i) * (this->Gamma1(j, is1) / this->CTFTimeStep);
    1527              :                     }
    1528              :                 } else { // SolutionDimensions = 2
    1529            0 :                     if ((j == 1) && ((is1 >= 1) && (is1 <= this->NumOfPerpendNodes))) {
    1530            0 :                         this->Gamma2(j, i) += this->AInv(is1, i) * (this->Gamma1(j, is1) / this->CTFTimeStep - this->BMat(1));
    1531            0 :                     } else if ((j == 2) && ((is1 <= this->rcmax) && (is1 >= this->rcmax + 1 - this->NumOfPerpendNodes))) {
    1532            0 :                         this->Gamma2(j, i) += this->AInv(is1, i) * (this->Gamma1(j, is1) / this->CTFTimeStep - this->BMat(2));
    1533            0 :                     } else if ((j == 3) && (is1 == this->NodeSource)) {
    1534            0 :                         this->Gamma2(j, i) += this->AInv(is1, i) * (this->Gamma1(j, is1) / this->CTFTimeStep - this->BMat(3));
    1535              :                     } else { // the element of the actual BMat is zero
    1536            0 :                         this->Gamma2(j, i) += this->AInv(is1, i) * (this->Gamma1(j, is1) / this->CTFTimeStep);
    1537              :                     }
    1538              :                 }
    1539              :             }
    1540              :         }
    1541              :     }
    1542          146 : }
    1543              : 
    1544          146 : void ConstructionProps::calculateFinalCoefficients()
    1545              : {
    1546              : 
    1547              :     // SUBROUTINE INFORMATION:
    1548              :     //       AUTHOR         Russ Taylor
    1549              :     //       DATE WRITTEN   June 1990
    1550              :     //       MODIFIED       Apr 96, RKS, cosmetic, algorithm neutral changes
    1551              :     //       RE-ENGINEERED  July 1996, RKS; Nov 1999, LKL (allocatable arrays)
    1552              : 
    1553              :     // PURPOSE OF THIS SUBROUTINE:
    1554              :     // Subprogram to calculate the Sj and ej coefficients of Seem's
    1555              :     // dissertation.  Follows Seem's technique to compute the coefficients
    1556              :     // in order with minimum storage requirements.
    1557              : 
    1558              :     // METHODOLOGY EMPLOYED:
    1559              :     // Combine the results of the ExponMatrix, InvertMatrix, and
    1560              :     // CalculateGammas routines together to arrive at the temperature
    1561              :     // coefficients (s, s0) and the heat flux history coefficients (e) of
    1562              :     // the CTFs.  The outline of this subroutine is based on step 5 of
    1563              :     // Seem's suggested implementation of the state space method found on
    1564              :     // pages 26+27 of his dissertation.
    1565              : 
    1566              :     // REFERENCES:
    1567              :     // The state space method of calculating CTFs is outlined in the
    1568              :     // doctoral dissertation of John Seem, "Modeling of Heat Transfer in
    1569              :     // Buildings", Department of Mechanical Engineering, University of
    1570              :     // Wisconsin-Madison, 1987.  In particular, the equations used for
    1571              :     // these calculations are equations (2.1.24) through (2.1.26) in Seem's
    1572              :     // dissertation.
    1573              : 
    1574              :     // SUBROUTINE PARAMETER DEFINITIONS:
    1575          146 :     constexpr Real64 ConvrgLim(1.0e-13); // Convergence limit (ratio) for cutting off the calculation of further
    1576              :     // CTFs.  This value was found to give suitable accuracy in IBLAST.
    1577              : 
    1578              :     Real64 avg;            // Intermediate calculation variable (average)
    1579              :     bool CTFConvrg;        // Set after CTFs are calculated, based on whether there are too many CTFs terms
    1580          146 :     Array2D<Real64> PhiR0; // Product of Phi( = AExp) and R0 matrices from the state space method
    1581              :     Real64 rat;            // Intermediate calculation variable (ratio of flux history terms)
    1582          146 :     Array2D<Real64> Rnew;  // Current R matrix
    1583          146 :     Array2D<Real64> Rold;  // R matrix from the last iteration
    1584              :     Real64 SurfNodeFac;    // Multiplying factor applied to various surface nodes
    1585              :     Real64 trace;          // Trace of the product of Phi( = AExp) and R0
    1586              : 
    1587              :     // Subroutine initializations
    1588          146 :     PhiR0.allocate(this->rcmax, this->rcmax);
    1589          146 :     Rnew.allocate(this->rcmax, this->rcmax);
    1590          146 :     Rold.allocate(this->rcmax, this->rcmax);
    1591          146 :     PhiR0 = 0.0;
    1592          146 :     Rold = 0.0;
    1593              : 
    1594          146 :     this->s0 = 0.0;
    1595          146 :     this->s = 0.0;
    1596          146 :     this->e = 0.0;
    1597          146 :     Rnew = this->IdenMatrix; // Rnew initialized to the identity matrix
    1598              : 
    1599              :     // Calculate Gamma1-Gamma2.  Gamma1 is not used by itself in the
    1600              :     // equations, only Gamma1-Gamma2.  Thus, reset Gamma1 to:
    1601              :     // Gamma1-Gamma2
    1602         1731 :     for (int i = 1; i <= this->rcmax; ++i) {
    1603         6340 :         for (int j = 1; j <= 3; ++j) {
    1604         4755 :             this->Gamma1(j, i) -= this->Gamma2(j, i);
    1605              :         }
    1606              :     }
    1607              : 
    1608              :     // Compute s0.  See Seem's thesis equation (2.1.24) which states that:
    1609              :     // s0  =  (CMat*R0*Gamma2) + (DMat)
    1610              :     // Note that for a two-dimensional solution, there is more than one
    1611              :     // node at the surface and the effect of each of these must be added
    1612              :     // together.
    1613          146 :     if (this->SolutionDimensions == 1) {
    1614          146 :         this->s0(1, 1) = this->CMat(1) * this->Gamma2(1, 1) + this->DMat(1);
    1615          146 :         this->s0(2, 1) = this->CMat(1) * this->Gamma2(2, 1);
    1616          146 :         this->s0(3, 1) = this->CMat(1) * this->Gamma2(3, 1);
    1617          146 :         this->s0(1, 2) = this->CMat(2) * this->Gamma2(1, this->rcmax);
    1618          146 :         this->s0(2, 2) = this->CMat(2) * this->Gamma2(2, this->rcmax) + this->DMat(2);
    1619          146 :         this->s0(3, 2) = this->CMat(2) * this->Gamma2(3, this->rcmax);
    1620              :     } else { // SolutionDimensions = 2
    1621            0 :         for (int SurfNode = 1; SurfNode <= this->NumOfPerpendNodes; ++SurfNode) {
    1622            0 :             if ((SurfNode == 1) || (SurfNode == this->NumOfPerpendNodes)) {
    1623            0 :                 SurfNodeFac = 0.5;
    1624              :             } else {
    1625            0 :                 SurfNodeFac = 1.0;
    1626              :             }
    1627            0 :             this->s0(1, 1) += SurfNodeFac * this->CMat(1) * this->Gamma2(1, SurfNode);
    1628            0 :             this->s0(2, 1) += SurfNodeFac * this->CMat(1) * this->Gamma2(2, SurfNode);
    1629            0 :             this->s0(3, 1) += SurfNodeFac * this->CMat(1) * this->Gamma2(3, SurfNode);
    1630            0 :             this->s0(1, 2) += SurfNodeFac * this->CMat(2) * this->Gamma2(1, this->rcmax + 1 - SurfNode);
    1631            0 :             this->s0(2, 2) += SurfNodeFac * this->CMat(2) * this->Gamma2(2, this->rcmax + 1 - SurfNode);
    1632            0 :             this->s0(3, 2) += SurfNodeFac * this->CMat(2) * this->Gamma2(3, this->rcmax + 1 - SurfNode);
    1633              :         }
    1634            0 :         this->s0(1, 1) += double(this->NumOfPerpendNodes - 1) * this->DMat(1);
    1635            0 :         this->s0(2, 2) += double(this->NumOfPerpendNodes - 1) * this->DMat(2);
    1636              :     }
    1637              : 
    1638          146 :     if (this->NodeSource > 0) {
    1639            0 :         this->s0(1, 3) = this->Gamma2(1, this->NodeSource);
    1640            0 :         this->s0(2, 3) = this->Gamma2(2, this->NodeSource);
    1641            0 :         this->s0(3, 3) = this->Gamma2(3, this->NodeSource);
    1642              :     }
    1643          146 :     if (this->NodeUserTemp > 0) {
    1644            0 :         this->s0(1, 4) = this->Gamma2(1, this->NodeUserTemp);
    1645            0 :         this->s0(2, 4) = this->Gamma2(2, this->NodeUserTemp);
    1646            0 :         this->s0(3, 4) = this->Gamma2(3, this->NodeUserTemp);
    1647              :     }
    1648              : 
    1649              :     // Check for and enforce symmetry in the cross term (Y)
    1650          146 :     if (std::abs(this->s0(2, 1)) != std::abs(this->s0(1, 2))) {
    1651          138 :         avg = (std::abs(this->s0(2, 1)) + std::abs(this->s0(1, 2))) * 0.5;
    1652          138 :         this->s0(2, 1) *= avg / std::abs(this->s0(2, 1));
    1653          138 :         this->s0(1, 2) *= avg / std::abs(this->s0(1, 2));
    1654              :     }
    1655              : 
    1656              :     // Compute S's and e's from 1 to n-1.  See equations (2.1.25) and
    1657              :     // (2.1.26) and Appendix C.
    1658          146 :     int inum = 1;      // Set history term counter
    1659          146 :     CTFConvrg = false; // Set the convergence logical to false
    1660              : 
    1661              :     // The following DO WHILE loop calculates each successive set of time
    1662              :     // history terms until there are rcmax number of history terms or the
    1663              :     // latest flux history term is negligibly small compared to the first
    1664              :     // flux history term.
    1665         1003 :     while ((!CTFConvrg) && (inum < this->rcmax)) { // Begin CTF calculation loop ...
    1666              : 
    1667              :         // Compute e(inum) based on Appendix C (Seem's dissertation). First,
    1668              :         // compute the new PhiR0 and its trace.
    1669              : 
    1670          857 :         trace = 0.0;
    1671              : 
    1672        11804 :         for (int ir = 1; ir <= this->rcmax; ++ir) {
    1673              : 
    1674       187140 :             for (int ic = 1; ic <= this->rcmax; ++ic) {
    1675       176193 :                 PhiR0(ic, ir) = 0.0;
    1676      3433992 :                 for (int is = 1; is <= this->rcmax; ++is) {
    1677              :                     // Make sure the next term won't cause an underflow.  If it will end up being so small
    1678              :                     // as to go below TinyLimit, then ignore it since it won't add anything to PhiR0 anyway.
    1679      3257799 :                     if (std::abs(Rnew(ic, is)) > Constant::rTinyValue) {
    1680      2788317 :                         if (std::abs(this->AExp(is, ir)) > std::abs(Constant::rTinyValue / Rnew(ic, is)))
    1681      2430221 :                             PhiR0(ic, ir) += this->AExp(is, ir) * Rnew(ic, is);
    1682              :                     }
    1683              :                 }
    1684              :             }
    1685              : 
    1686        10947 :             trace += PhiR0(ir, ir);
    1687              :         }
    1688              : 
    1689              :         // Now calculate ej from the trace.  According to Appendix C:
    1690              :         // e(j) = -Trace[AExp*R(j-1)]/j
    1691              : 
    1692          857 :         this->e(inum) = -trace / double(inum);
    1693              : 
    1694              :         // Update Rold and compute Rnew.  Note:  PhiR0 = AExp*R(j-1) here.
    1695              :         // According to Appendix C:  R(j) = AExp*R(j-1) + e(j-1)
    1696              : 
    1697        11804 :         for (int ir = 1; ir <= this->rcmax; ++ir) {
    1698       187140 :             for (int ic = 1; ic <= this->rcmax; ++ic) {
    1699       176193 :                 Rold(ic, ir) = Rnew(ic, ir);
    1700       176193 :                 Rnew(ic, ir) = PhiR0(ic, ir);
    1701              :             }
    1702        10947 :             Rnew(ir, ir) += this->e(inum);
    1703              :         }
    1704              : 
    1705              :         // Compute S(inum) based on eq.(2.1.25) which states:
    1706              :         // S(j)  =  CMat*[R(j-1)*(Gamma1-Gamma2)+R(j)*Gamma2]
    1707              :         //          + e(j)*DMat
    1708          857 :         if (this->SolutionDimensions == 1) {
    1709         3428 :             for (int j = 1; j <= 3; ++j) {
    1710        35412 :                 for (int is2 = 1; is2 <= this->rcmax; ++is2) {
    1711        32841 :                     this->s(j, 1, inum) += this->CMat(1) * (Rold(is2, 1) * this->Gamma1(j, is2) + Rnew(is2, 1) * this->Gamma2(j, is2));
    1712        65682 :                     this->s(j, 2, inum) +=
    1713        32841 :                         this->CMat(2) * (Rold(is2, this->rcmax) * this->Gamma1(j, is2) + Rnew(is2, this->rcmax) * this->Gamma2(j, is2));
    1714        32841 :                     if (this->NodeSource > 0) {
    1715            0 :                         this->s(j, 3, inum) +=
    1716            0 :                             (Rold(is2, this->NodeSource) * this->Gamma1(j, is2) + Rnew(is2, this->NodeSource) * this->Gamma2(j, is2));
    1717              :                     }
    1718        32841 :                     if (this->NodeUserTemp > 0) {
    1719            0 :                         this->s(j, 4, inum) +=
    1720            0 :                             (Rold(is2, this->NodeUserTemp) * this->Gamma1(j, is2) + Rnew(is2, this->NodeUserTemp) * this->Gamma2(j, is2));
    1721              :                     }
    1722              :                 }
    1723         2571 :                 if (j != 3) this->s(j, j, inum) += this->e(inum) * this->DMat(j);
    1724              :             }
    1725              :         } else { // SolutionDimensions = 2
    1726            0 :             for (int j = 1; j <= 3; ++j) {
    1727            0 :                 for (int is2 = 1; is2 <= this->rcmax; ++is2) {
    1728            0 :                     for (int SurfNode = 1; SurfNode <= this->NumOfPerpendNodes; ++SurfNode) {
    1729            0 :                         if ((SurfNode == 1) || (SurfNode == this->NumOfPerpendNodes)) {
    1730            0 :                             SurfNodeFac = 0.5;
    1731              :                         } else {
    1732            0 :                             SurfNodeFac = 1.0;
    1733              :                         }
    1734            0 :                         this->s(j, 1, inum) +=
    1735            0 :                             SurfNodeFac * this->CMat(1) * (Rold(is2, SurfNode) * this->Gamma1(j, is2) + Rnew(is2, SurfNode) * this->Gamma2(j, is2));
    1736            0 :                         this->s(j, 2, inum) += SurfNodeFac * this->CMat(2) *
    1737            0 :                                                (Rold(is2, this->rcmax + 1 - SurfNode) * this->Gamma1(j, is2) +
    1738            0 :                                                 Rnew(is2, this->rcmax + 1 - SurfNode) * this->Gamma2(j, is2));
    1739              :                     }
    1740            0 :                     if (this->NodeSource > 0) {
    1741            0 :                         this->s(j, 3, inum) +=
    1742            0 :                             (Rold(is2, this->NodeSource) * this->Gamma1(j, is2) + Rnew(is2, this->NodeSource) * this->Gamma2(j, is2));
    1743              :                     }
    1744            0 :                     if (this->NodeUserTemp > 0) {
    1745            0 :                         this->s(j, 4, inum) +=
    1746            0 :                             (Rold(is2, this->NodeUserTemp) * this->Gamma1(j, is2) + Rnew(is2, this->NodeUserTemp) * this->Gamma2(j, is2));
    1747              :                     }
    1748              :                 }
    1749              :             }
    1750            0 :             this->s(1, 1, inum) += this->e(inum) * this->DMat(1) * double(this->NumOfPerpendNodes - 1);
    1751            0 :             this->s(2, 2, inum) += this->e(inum) * this->DMat(2) * double(this->NumOfPerpendNodes - 1);
    1752              :         }
    1753              : 
    1754              :         // Check for and enforce symmetry in the cross term (Y)
    1755          857 :         if (std::abs(s(2, 1, inum)) != std::abs(s(1, 2, inum))) {
    1756          855 :             avg = (std::abs(s(2, 1, inum)) + std::abs(s(1, 2, inum))) * 0.5;
    1757          855 :             this->s(2, 1, inum) *= avg / std::abs(s(2, 1, inum));
    1758          855 :             this->s(1, 2, inum) *= avg / std::abs(s(1, 2, inum));
    1759              :         }
    1760              : 
    1761              :         // Check for convergence of the CTFs.
    1762          857 :         if (e(1) == 0.0) {
    1763              : 
    1764            0 :             this->NumCTFTerms = 1; // e(1) is zero, so there are no history terms.
    1765            0 :             CTFConvrg = true;      // CTF calculations have converged--set logical.
    1766              : 
    1767              :         } else {
    1768              :             // e(1) is non-zero -- Calculate and compare the ratio of the flux
    1769              :             // terms to the convergence limit.
    1770          857 :             rat = std::abs(e(inum) / this->e(1));
    1771              : 
    1772          857 :             if (rat < ConvrgLim) {
    1773              : 
    1774              :                 // If the ratio is less than the convergence limit, then any other
    1775              :                 // terms would have a neglible impact on the CTF-based energy balances.
    1776           92 :                 this->NumCTFTerms = inum;
    1777           92 :                 CTFConvrg = true; // CTF calculations have converged--set logical.
    1778              :             }
    1779              :         } // ... end of convergence check block.
    1780              : 
    1781          857 :         ++inum;
    1782              : 
    1783              :     } // ... end of CTF calculation loop.
    1784              :     // Continue to the next coefficient if the solution has not converged
    1785          146 :     if (!CTFConvrg) { // Compute last e and S, if still unconverged.
    1786              : 
    1787              :         // Compute e(inum) based on Appendix C (Seem's dissertation) or see
    1788              :         // equation above.  First compute the new PhiR0 and its trace.
    1789              : 
    1790           54 :         trace = 0.0;
    1791              : 
    1792          336 :         for (int ir = 1; ir <= this->rcmax; ++ir) {
    1793         1800 :             for (int is = 1; is <= this->rcmax; ++is) {
    1794         1518 :                 trace += this->AExp(is, ir) * Rnew(ir, is);
    1795              :             }
    1796              :         }
    1797              : 
    1798           54 :         this->e(this->rcmax) = -trace / double(this->rcmax); // Now calculate ej from the trace.
    1799              : 
    1800              :         // Compute S(inum) based on eq.(2.1.25) which states:
    1801              :         //   S(last) = CMat*R(last-1)*(Gamma1-Gamma2)+e(last)*DMat
    1802              : 
    1803           54 :         if (this->SolutionDimensions == 1) {
    1804          216 :             for (int j = 1; j <= 3; ++j) {
    1805         1008 :                 for (int is2 = 1; is2 <= this->rcmax; ++is2) {
    1806          846 :                     this->s(j, 1, this->rcmax) += this->CMat(1) * Rnew(is2, 1) * this->Gamma1(j, is2);
    1807          846 :                     this->s(j, 2, this->rcmax) += this->CMat(2) * Rnew(is2, this->rcmax) * this->Gamma1(j, is2);
    1808          846 :                     if (this->NodeSource > 0) {
    1809            0 :                         this->s(j, 3, this->rcmax) += Rnew(is2, this->NodeSource) * this->Gamma1(j, is2);
    1810              :                     }
    1811          846 :                     if (this->NodeUserTemp > 0) {
    1812            0 :                         this->s(j, 4, this->rcmax) += Rnew(is2, this->NodeUserTemp) * this->Gamma1(j, is2);
    1813              :                     }
    1814              :                 }
    1815              :             }
    1816           54 :             this->s(1, 1, this->rcmax) += this->e(this->rcmax) * this->DMat(1);
    1817           54 :             this->s(2, 2, this->rcmax) += this->e(this->rcmax) * this->DMat(2);
    1818           54 :             this->NumCTFTerms = this->rcmax;
    1819              :         } else { // SolutionDimensions = 2
    1820            0 :             for (int j = 1; j <= 3; ++j) {
    1821            0 :                 for (int is2 = 1; is2 <= this->rcmax; ++is2) {
    1822            0 :                     for (int SurfNode = 1; SurfNode <= this->NumOfPerpendNodes; ++SurfNode) {
    1823            0 :                         if ((SurfNode == 1) || (SurfNode == this->NumOfPerpendNodes)) {
    1824            0 :                             SurfNodeFac = 0.5;
    1825              :                         } else {
    1826            0 :                             SurfNodeFac = 1.0;
    1827              :                         }
    1828            0 :                         this->s(j, 1, this->rcmax) += SurfNodeFac * this->CMat(1) * Rnew(is2, SurfNode) * this->Gamma1(j, is2);
    1829            0 :                         this->s(j, 2, this->rcmax) += SurfNodeFac * this->CMat(2) * Rnew(is2, this->rcmax + 1 - SurfNode) * this->Gamma1(j, is2);
    1830              :                     }
    1831            0 :                     if (this->NodeSource > 0) {
    1832            0 :                         this->s(j, 3, this->rcmax) += Rnew(is2, this->NodeSource) * this->Gamma1(j, is2);
    1833              :                     }
    1834            0 :                     if (this->NodeUserTemp > 0) {
    1835            0 :                         this->s(j, 4, this->rcmax) += Rnew(is2, this->NodeUserTemp) * this->Gamma1(j, is2);
    1836              :                     }
    1837              :                 }
    1838              :             }
    1839            0 :             this->s(1, 1, this->rcmax) += this->e(this->rcmax) * this->DMat(1) * double(this->NumOfPerpendNodes - 1);
    1840            0 :             this->s(2, 2, this->rcmax) += this->e(this->rcmax) * this->DMat(2) * double(this->NumOfPerpendNodes - 1);
    1841              :         }
    1842              : 
    1843              :         // Check for and enforce symmetry in the cross term (Y)
    1844              : 
    1845           54 :         if (std::abs(s(2, 1, this->rcmax)) != std::abs(s(1, 2, this->rcmax))) {
    1846           54 :             avg = (std::abs(s(2, 1, this->rcmax)) + std::abs(s(1, 2, this->rcmax))) * 0.5;
    1847           54 :             this->s(2, 1, this->rcmax) *= avg / std::abs(s(2, 1, this->rcmax));
    1848           54 :             this->s(1, 2, this->rcmax) *= avg / std::abs(s(1, 2, this->rcmax));
    1849              :         }
    1850              : 
    1851              :     } // ... end of IF block for calculation of last e and S.
    1852              : 
    1853          146 :     PhiR0.deallocate();
    1854          146 :     Rnew.deallocate();
    1855          146 :     Rold.deallocate();
    1856          146 : }
    1857              : 
    1858           74 : void ConstructionProps::reportTransferFunction(EnergyPlusData &state, int const cCounter)
    1859              : {
    1860              : 
    1861              :     static constexpr std::string_view Format_700{" Construction CTF,{},{:4},{:4},{:4},{:8.3F},{:15.4N},{:8.3F},{:8.3F},{:8.3F},{:8.3F},{}\n"};
    1862           74 :     print(state.files.eio,
    1863              :           Format_700,
    1864           74 :           this->Name,
    1865              :           cCounter,
    1866           74 :           this->TotLayers,
    1867           74 :           this->NumCTFTerms,
    1868           74 :           this->CTFTimeStep,
    1869           74 :           this->UValue,
    1870           74 :           this->OutsideAbsorpThermal,
    1871           74 :           this->InsideAbsorpThermal,
    1872           74 :           this->OutsideAbsorpSolar,
    1873           74 :           this->InsideAbsorpSolar,
    1874           74 :           Material::surfaceRoughnessNames[(int)this->OutsideRoughness]);
    1875              : 
    1876          164 :     for (int I = 1; I <= this->TotLayers; ++I) {
    1877           90 :         int Layer = this->LayerPoint(I);
    1878           90 :         auto const *thisMaterial = state.dataMaterial->materials(Layer);
    1879           90 :         switch (thisMaterial->group) {
    1880            1 :         case Material::Group::AirGap: {
    1881              :             static constexpr std::string_view Format_702(" Material:Air,{},{:12.4N}\n");
    1882            1 :             print(state.files.eio, Format_702, thisMaterial->Name, thisMaterial->Resistance);
    1883            1 :         } break;
    1884           89 :         default: {
    1885              :             static constexpr std::string_view Format_701(" Material CTF Summary,{},{:8.4F},{:14.3F},{:11.3F},{:13.3F},{:12.4N}\n");
    1886           89 :             Material::MaterialBase const *mp = thisMaterial;
    1887           89 :             print(state.files.eio, Format_701, mp->Name, mp->Thickness, mp->Conductivity, mp->Density, mp->SpecHeat, mp->Resistance);
    1888           89 :         } break;
    1889              :         }
    1890              :     }
    1891              : 
    1892          380 :     for (int I = this->NumCTFTerms; I >= 0; --I) {
    1893          306 :         if (I != 0) {
    1894              :             static constexpr std::string_view Format_703(" CTF,{:4},{:20.8N},{:20.8N},{:20.8N},{:20.8N}\n");
    1895          232 :             print(state.files.eio, Format_703, I, this->CTFOutside[I], this->CTFCross[I], this->CTFInside[I], this->CTFFlux[I]);
    1896              :         } else {
    1897              :             static constexpr std::string_view Format_704(" CTF,{:4},{:20.8N},{:20.8N},{:20.8N}\n");
    1898           74 :             print(state.files.eio, Format_704, I, this->CTFOutside[I], this->CTFCross[I], this->CTFInside[I]);
    1899              :         }
    1900              :     }
    1901              : 
    1902           74 :     if (this->SourceSinkPresent) {
    1903              :         // QTFs...
    1904            0 :         for (int I = this->NumCTFTerms; I >= 0; --I) {
    1905              :             static constexpr std::string_view Format_705(" QTF,{:4},{:20.8N},{:20.8N}\n");
    1906            0 :             print(state.files.eio, Format_705, I, this->CTFSourceOut[I], this->CTFSourceIn[I]);
    1907              :         }
    1908              :         // QTFs for source/sink location temperature calculation...
    1909            0 :         for (int I = this->NumCTFTerms; I >= 0; --I) {
    1910              :             static constexpr std::string_view Format_706(" Source/Sink Loc Internal Temp QTF,{:4},{:20.8N},{:20.8N},{:20.8N}\n");
    1911            0 :             print(state.files.eio, Format_706, I, this->CTFTSourceOut[I], this->CTFTSourceIn[I], this->CTFTSourceQ[I]);
    1912              :         }
    1913            0 :         if (this->TempAfterLayer != 0) {
    1914              :             // QTFs for user specified interior temperature calculation...
    1915            0 :             for (int I = this->NumCTFTerms; I >= 0; --I) {
    1916              :                 static constexpr std::string_view Format_707(" User Loc Internal Temp QTF,{:4},{:20.8N},{:20.8N},{:20.8N}\n");
    1917            0 :                 print(state.files.eio, Format_707, I, this->CTFTUserOut[I], this->CTFTUserIn[I], this->CTFTUserSource[I]);
    1918              :             }
    1919              :         }
    1920              :     }
    1921           74 : }
    1922              : 
    1923          249 : void ConstructionProps::reportLayers(EnergyPlusData &state)
    1924              : {
    1925              :     // Report the layers for each opaque construction in predefined tabular report
    1926              :     // J. Glazer March 2024
    1927          249 :     if (state.dataOutRptPredefined->pdchOpqConsLayCol.size() > 0) {
    1928          612 :         for (int i = 1; i <= this->TotLayers; ++i) {
    1929          382 :             int layerIndex = this->LayerPoint(i);
    1930          382 :             auto const *mat = state.dataMaterial->materials(layerIndex);
    1931          382 :             OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchOpqConsLayCol[i - 1], this->Name, mat->Name);
    1932              :         }
    1933              :     }
    1934          249 : }
    1935              : 
    1936            4 : bool ConstructionProps::isGlazingConstruction(EnergyPlusData &state) const
    1937              : {
    1938              :     // SUBROUTINE INFORMATION:
    1939              :     //       AUTHOR         Simon Vidanovic
    1940              :     //       DATE WRITTEN   September 2016
    1941              : 
    1942              :     // PURPOSE OF THIS SUBROUTINE:
    1943              :     // Commonly used routine in several places in EnergyPlus which examines if current
    1944              :     // construction is glazing construction
    1945            4 :     const Material::Group group = state.dataMaterial->materials(LayerPoint(1))->group;
    1946            0 :     return (group == Material::Group::Glass) || (group == Material::Group::Shade) || (group == Material::Group::Screen) ||
    1947            4 :            (group == Material::Group::Blind) || (group == Material::Group::GlassSimple);
    1948              : }
    1949              : 
    1950           14 : Real64 ConstructionProps::setThicknessPerpendicular(EnergyPlusData &state, Real64 userValue)
    1951              : {
    1952              :     // Limits set here are similar to limits used in HydronicSystemBaseData::sizeRadiantSystemTubeLength
    1953              :     // which is for autosizing tube length.  The limits are not as strictly enforced here because they are
    1954              :     // not being used for autosizing so more latitude with the values is desired.  Warnings will still alert
    1955              :     // the user if values seem outside of the ordinary range anticipated.
    1956           14 :     Real64 returnValue = userValue / 2.0; // Divide by two because internally the half distance will be used to calculate CTFs
    1957           14 :     if (returnValue <= 0.001) {           // lowest reasonable value for "half" the tube spacing in meters
    1958            4 :         ShowWarningError(state, "ConstructionProperty:InternalHeatSource has a tube spacing that is less than 2 mm.  This is not allowed.");
    1959            4 :         ShowContinueError(
    1960              :             state,
    1961            4 :             format("Construction={} has this problem.  The tube spacing has been reset to 0.15m (~6 inches) for this construction.", this->Name));
    1962            4 :         ShowContinueError(state, "As per the Input Output Reference, tube spacing is only used for 2-D solutions and autosizing.");
    1963            2 :         returnValue = 0.075;          // default "half" tube spacing in meters (roughly equivalent to 15cm or 6 inches of tube spacing)
    1964           12 :     } else if (returnValue < 0.005) { // below this value for "half" the tube spacing in meters throw a warning
    1965            2 :         ShowWarningError(state, "ConstructionProperty:InternalHeatSource has a tube spacing that is less than 1 cm (0.4 inch).");
    1966            1 :         ShowContinueError(state, format("Construction={} has this concern.  Please check this construction to make sure it is correct.", this->Name));
    1967            3 :         ShowContinueError(state, "As per the Input Output Reference, tube spacing is only used for 2-D solutions and autosizing.");
    1968           11 :     } else if (returnValue > 0.5) { // above this value for "half" the tube spacing in meters throw a warning
    1969            2 :         ShowWarningError(state, "ConstructionProperty:InternalHeatSource has a tube spacing that is greater than 1 meter (39.4 inches).");
    1970            1 :         ShowContinueError(state, format("Construction={} has this concern.  Please check this construction to make sure it is correct.", this->Name));
    1971            3 :         ShowContinueError(state, "As per the Input Output Reference, tube spacing is only used for 2-D solutions and autosizing.");
    1972              :     }
    1973           14 :     return returnValue;
    1974              : }
    1975              : 
    1976           12 : Real64 ConstructionProps::setUserTemperatureLocationPerpendicular(EnergyPlusData &state, Real64 userValue)
    1977              : {
    1978           12 :     if (userValue < 0.0) {
    1979            2 :         ShowWarningError(state, "ConstructionProperty:InternalHeatSource has a perpendicular temperature location parameter that is less than zero.");
    1980            1 :         ShowContinueError(state, format("Construction={} has this error.  The parameter has been reset to 0.", this->Name));
    1981            1 :         return 0.0;
    1982           11 :     } else if (userValue > 1.0) {
    1983            2 :         ShowWarningError(state,
    1984              :                          "ConstructionProperty:InternalHeatSource has a perpendicular temperature location parameter that is greater than one.");
    1985            1 :         ShowContinueError(state, format("Construction={} has this error.  The parameter has been reset to 1.", this->Name));
    1986            1 :         return 1.0;
    1987              :     } else { // Valid value between 0 and 1
    1988           10 :         return userValue;
    1989              :     }
    1990              : }
    1991              : 
    1992          150 : void ConstructionProps::setNodeSourceAndUserTemp(Array1D_int &Nodes)
    1993              : {
    1994          150 :     this->NodeSource = 0;
    1995          150 :     this->NodeUserTemp = 0;
    1996          150 :     if (!this->SourceSinkPresent) return;
    1997              : 
    1998           10 :     for (int Layer = 1; Layer <= this->SourceAfterLayer; ++Layer) {
    1999            7 :         this->NodeSource += Nodes(Layer);
    2000              :     }
    2001              : 
    2002            3 :     if ((this->NodeSource > 0) && (this->SolutionDimensions > 1)) this->NodeSource = (this->NodeSource - 1) * this->NumOfPerpendNodes + 1;
    2003              : 
    2004           13 :     for (int Layer = 1; Layer <= this->TempAfterLayer; ++Layer) {
    2005           10 :         this->NodeUserTemp += Nodes(Layer);
    2006              :     }
    2007              : 
    2008            3 :     if ((this->NodeUserTemp > 0) && (this->SolutionDimensions > 1))
    2009            2 :         this->NodeUserTemp = (this->NodeUserTemp - 1) * this->NumOfPerpendNodes +
    2010            2 :                              round(this->userTemperatureLocationPerpendicular * (this->NumOfPerpendNodes - 1)) + 1;
    2011              : }
    2012              : 
    2013          798 : void ConstructionProps::setArraysBasedOnMaxSolidWinLayers(EnergyPlusData &state)
    2014              : {
    2015          798 :     this->AbsDiff.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2016          798 :     this->AbsDiffBack.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2017          798 :     this->layerSlatBlindDfAbs.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2018          798 :     this->AbsBeamCoef.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2019          798 :     this->AbsBeamBackCoef.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2020          798 :     this->tBareSolCoef.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2021          798 :     this->tBareVisCoef.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2022          798 :     this->rfBareSolCoef.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2023          798 :     this->rfBareVisCoef.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2024          798 :     this->rbBareSolCoef.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2025          798 :     this->rbBareVisCoef.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2026          798 :     this->afBareSolCoef.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2027          798 :     this->abBareSolCoef.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2028              : 
    2029         3603 :     for (int Layer = 1; Layer <= state.dataHeatBal->MaxSolidWinLayers; ++Layer) {
    2030         2805 :         this->AbsDiff(Layer) = 0.0;
    2031         2805 :         this->AbsDiffBack(Layer) = 0.0;
    2032              :     }
    2033         3603 :     for (int Layer = 1; Layer <= state.dataHeatBal->MaxSolidWinLayers; ++Layer) {
    2034         2805 :         auto &slatBlindDfAbs = this->layerSlatBlindDfAbs(Layer);
    2035       510510 :         for (int iSlatAng = 0; iSlatAng < Material::MaxSlatAngs; ++iSlatAng) {
    2036       507705 :             auto &blindDfAbs = slatBlindDfAbs[iSlatAng];
    2037       507705 :             blindDfAbs.Sol.Ft.Df.Abs = 0.0;
    2038       507705 :             blindDfAbs.Sol.Ft.Df.AbsGnd = 0.0;
    2039       507705 :             blindDfAbs.Sol.Ft.Df.AbsGnd = 0.0;
    2040       507705 :             blindDfAbs.Sol.Bk.Df.Abs = 0.0;
    2041              :         }
    2042              :     }
    2043         3603 :     for (int Layer = 1; Layer <= state.dataHeatBal->MaxSolidWinLayers; ++Layer) {
    2044         2805 :         std::fill(this->AbsBeamCoef(Layer).begin(), this->AbsBeamCoef(Layer).end(), 0.0);
    2045         2805 :         std::fill(this->AbsBeamBackCoef(Layer).begin(), this->AbsBeamBackCoef(Layer).end(), 0.0);
    2046         2805 :         std::fill(this->tBareSolCoef(Layer).begin(), this->tBareSolCoef(Layer).end(), 0.0);
    2047         2805 :         std::fill(this->tBareVisCoef(Layer).begin(), this->tBareVisCoef(Layer).end(), 0.0);
    2048         2805 :         std::fill(this->rfBareSolCoef(Layer).begin(), this->rfBareSolCoef(Layer).end(), 0.0);
    2049         2805 :         std::fill(this->rfBareVisCoef(Layer).begin(), this->rfBareVisCoef(Layer).end(), 0.0);
    2050         2805 :         std::fill(this->rbBareSolCoef(Layer).begin(), this->rbBareSolCoef(Layer).end(), 0.0);
    2051         2805 :         std::fill(this->rbBareVisCoef(Layer).begin(), this->rbBareVisCoef(Layer).end(), 0.0);
    2052         2805 :         std::fill(this->afBareSolCoef(Layer).begin(), this->afBareSolCoef(Layer).end(), 0.0);
    2053         2805 :         std::fill(this->abBareSolCoef(Layer).begin(), this->abBareSolCoef(Layer).end(), 0.0);
    2054              :     }
    2055          798 : }
    2056              : 
    2057              : } // namespace EnergyPlus::Construction
        

Generated by: LCOV version 2.0-1