LCOV - code coverage report
Current view: top level - EnergyPlus - Construction.cc (source / functions) Hit Total Coverage
Test: lcov.output.filtered Lines: 766 863 88.8 %
Date: 2024-08-24 18:31:18 Functions: 12 12 100.0 %

          Line data    Source code
       1             : // EnergyPlus, Copyright (c) 1996-2024, The Board of Trustees of the University of Illinois,
       2             : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
       3             : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
       4             : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
       5             : // contributors. All rights reserved.
       6             : //
       7             : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
       8             : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
       9             : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
      10             : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
      11             : // derivative works, and perform publicly and display publicly, and to permit others to do so.
      12             : //
      13             : // Redistribution and use in source and binary forms, with or without modification, are permitted
      14             : // provided that the following conditions are met:
      15             : //
      16             : // (1) Redistributions of source code must retain the above copyright notice, this list of
      17             : //     conditions and the following disclaimer.
      18             : //
      19             : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
      20             : //     conditions and the following disclaimer in the documentation and/or other materials
      21             : //     provided with the distribution.
      22             : //
      23             : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
      24             : //     the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
      25             : //     used to endorse or promote products derived from this software without specific prior
      26             : //     written permission.
      27             : //
      28             : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
      29             : //     without changes from the version obtained under this License, or (ii) Licensee makes a
      30             : //     reference solely to the software portion of its product, Licensee must refer to the
      31             : //     software as "EnergyPlus version X" software, where "X" is the version number Licensee
      32             : //     obtained under this License and may not use a different name for the software. Except as
      33             : //     specifically required in this Section (4), Licensee shall not use in a company name, a
      34             : //     product name, in advertising, publicity, or other promotional activities any name, trade
      35             : //     name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
      36             : //     similar designation, without the U.S. Department of Energy's prior written consent.
      37             : //
      38             : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
      39             : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
      40             : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
      41             : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
      42             : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
      43             : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
      44             : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
      45             : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
      46             : // POSSIBILITY OF SUCH DAMAGE.
      47             : 
      48             : // 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        5992 : 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        5992 :     constexpr Real64 PhysPropLimit(1.0e-6); // Physical properties limit.
     118             :     // This is more or less the traditional value from BLAST.
     119             : 
     120        5992 :     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        5992 :     constexpr int MinNodes(6); // Minimum number of state space nodes
     127             :     // per layer.  This value was chosen based on experience with IBLAST.
     128             : 
     129        5992 :     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        5992 :     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        5992 :     this->CTFCross.fill(0.0);
     147        5992 :     this->CTFFlux.fill(0.0);
     148        5992 :     this->CTFInside.fill(0.0);
     149        5992 :     this->CTFOutside.fill(0.0);
     150        5992 :     this->CTFSourceIn.fill(0.0);
     151        5992 :     this->CTFSourceOut.fill(0.0);
     152        5992 :     this->CTFTimeStep = 0.0;
     153        5992 :     this->CTFTSourceOut.fill(0.0);
     154        5992 :     this->CTFTSourceIn.fill(0.0);
     155        5992 :     this->CTFTSourceQ.fill(0.0);
     156        5992 :     this->CTFTUserOut.fill(0.0);
     157        5992 :     this->CTFTUserIn.fill(0.0);
     158        5992 :     this->CTFTUserSource.fill(0.0);
     159        5992 :     this->NumHistories = 0;
     160        5992 :     this->NumCTFTerms = 0;
     161        5992 :     this->UValue = 0.0;
     162             : 
     163        5992 :     if (!this->IsUsedCTF) {
     164        1844 :         return;
     165             :     }
     166             : 
     167        4148 :     Array1D<Real64> cp(Construction::MaxLayersInConstruct);              // Specific heat of a material layer
     168        4148 :     Array1D<Real64> dl(Construction::MaxLayersInConstruct);              // Thickness of a material layer
     169        4148 :     Array1D<Real64> dx(Construction::MaxLayersInConstruct);              // Distance between nodes in a particular material layer
     170        4148 :     Array1D<Real64> lr(Construction::MaxLayersInConstruct);              // R value of a material layer
     171        4148 :     Array1D_int Nodes(Construction::MaxLayersInConstruct);               // Array containing the number of nodes per layer
     172        4148 :     Array1D_bool ResLayer(Construction::MaxLayersInConstruct);           // Set true if the layer must be handled as a resistive
     173        4148 :     Array1D<Real64> rho(Construction::MaxLayersInConstruct);             // Density of a material layer
     174        4148 :     Array1D<Real64> rk(Construction::MaxLayersInConstruct);              // Thermal conductivity of a material layer
     175        4148 :     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        4148 :     this->CTFTimeStep = state.dataGlobal->TimeStepZone;
     195        4148 :     Real64 rs = 0.0;
     196        4148 :     int LayersInConstruct = 0;
     197        4148 :     int NumResLayers = 0;
     198        4148 :     ResLayer = false;
     199        4148 :     AdjacentResLayerNum = 0; // Zero this out for each construct
     200             : 
     201       14387 :     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       10239 :         int CurrentLayer = this->LayerPoint(Layer);
     208             : 
     209       10239 :         ++LayersInConstruct;
     210             : 
     211             :         // Obtain thermal properties from the Material derived type
     212             : 
     213       10239 :         auto *thisMaterial = dynamic_cast<Material::MaterialChild *>(state.dataMaterial->Material(CurrentLayer));
     214       10239 :         assert(thisMaterial != nullptr);
     215             : 
     216       10239 :         dl(Layer) = thisMaterial->Thickness;
     217       10239 :         rk(Layer) = thisMaterial->Conductivity;
     218       10239 :         rho(Layer) = thisMaterial->Density;
     219       10239 :         cp(Layer) = thisMaterial->SpecHeat; // Must convert
     220             :         // from kJ/kg-K to J/kg-k due to rk units
     221             : 
     222       10239 :         if (this->SourceSinkPresent && !thisMaterial->WarnedForHighDiffusivity) {
     223             :             // check for materials that are too conductive or thin
     224         182 :             if ((rho(Layer) * cp(Layer)) > 0.0) {
     225         178 :                 Real64 Alpha = rk(Layer) / (rho(Layer) * cp(Layer));
     226         178 :                 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       10239 :         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       10239 :         if (rk(Layer) <= PhysPropLimit) { // Thermal conductivity too small,
     261             :             // thus this must be handled as a resistive layer
     262        1480 :             ResLayer(Layer) = true;
     263             :         } else {
     264        8759 :             lr(Layer) = dl(Layer) / rk(Layer);
     265        8759 :             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       10239 :         if (ResLayer(Layer)) {                    // Resistive layer-check for R-value, etc.
     272        1480 :             ++NumResLayers;                       // Increment number of resistive layers
     273        1480 :             lr(Layer) = thisMaterial->Resistance; // User defined thermal resistivity
     274        1480 :             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        1480 :                 if ((Layer == 1) || (Layer == this->TotLayers) || (!state.dataMaterial->Material(this->LayerPoint(Layer))->ROnly)) {
     293         969 :                     cp(Layer) = 1.007;
     294         969 :                     rho(Layer) = 1.1614;
     295         969 :                     rk(Layer) = 0.0263;
     296         969 :                     dl(Layer) = rk(Layer) * lr(Layer);
     297             :                 } else {
     298         511 :                     cp(Layer) = 0.0;
     299         511 :                     rho(Layer) = 0.0;
     300         511 :                     rk(Layer) = 1.0;
     301         511 :                     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        4148 :     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        4148 :     if ((LayersInConstruct > 3) && (NumResLayers > 1)) {
     319          82 :         int NumAdjResLayers = 0;
     320         254 :         for (int Layer = 2; Layer <= LayersInConstruct - 2; ++Layer) {
     321         172 :             if ((ResLayer(Layer)) && (ResLayer(Layer + 1))) {
     322          27 :                 ++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          27 :                 AdjacentResLayerNum(NumAdjResLayers) = Layer + 1 - NumAdjResLayers;
     326             :             }
     327             :         }
     328         109 :         for (int AdjLayer = 1; AdjLayer <= NumAdjResLayers; ++AdjLayer) {
     329          27 :             int Layer = AdjacentResLayerNum(AdjLayer);
     330             :             // Double check to make sure we are in the right place...
     331          27 :             if ((ResLayer(Layer)) && (ResLayer(Layer + 1))) {
     332             :                 // Shift layers forward after combining two adjacent layers.  Then
     333             :                 // restart the do loop.
     334          27 :                 cp(Layer) = 0.0;
     335          27 :                 rho(Layer) = 0.0;
     336          27 :                 rk(Layer) = 1.0;
     337          27 :                 lr(Layer) += lr(Layer + 1);
     338          27 :                 dl(Layer) = lr(Layer);
     339          27 :                 --NumResLayers; // Combining layers so decrease number of resistive layers
     340          68 :                 for (int Layer1 = Layer + 1; Layer1 <= LayersInConstruct - 1; ++Layer1) {
     341          41 :                     lr(Layer1) = lr(Layer1 + 1);
     342          41 :                     dl(Layer1) = dl(Layer1 + 1);
     343          41 :                     rk(Layer1) = rk(Layer1 + 1);
     344          41 :                     rho(Layer1) = rho(Layer1 + 1);
     345          41 :                     cp(Layer1) = cp(Layer1 + 1);
     346          41 :                     ResLayer(Layer1) = ResLayer(Layer1 + 1);
     347             :                 }
     348             :                 // Then zero out the layer that got shifted forward
     349          27 :                 cp(LayersInConstruct) = 0.0;
     350          27 :                 rho(LayersInConstruct) = 0.0;
     351          27 :                 rk(LayersInConstruct) = 0.0;
     352          27 :                 lr(LayersInConstruct) = 0.0;
     353          27 :                 dl(LayersInConstruct) = 0.0;
     354             :                 // Now reduce the number of layers in construct since merger is complete
     355          27 :                 --LayersInConstruct;
     356             :                 // Also adjust layers with source/sinks if two layers are merged
     357          27 :                 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       14360 :     for (int Layer = 1; Layer <= LayersInConstruct; ++Layer) { // Begin units conversion loop ...
     374             : 
     375       10212 :         lr(Layer) *= DataConversions::CFU;
     376       10212 :         dl(Layer) /= DataConversions::CFL;
     377       10212 :         rk(Layer) /= DataConversions::CFK;
     378       10212 :         rho(Layer) /= DataConversions::CFD;
     379       10212 :         cp(Layer) /= (DataConversions::CFC * 1000.0);
     380             : 
     381             :     } // ... end of layer loop for units conversion.
     382             : 
     383        4148 :     if (this->SolutionDimensions == 1) {
     384        4146 :         dyn = 0.0;
     385             :     } else {
     386           2 :         dyn = (this->ThicknessPerpend / DataConversions::CFL) / double(NumOfPerpendNodes - 1);
     387             :     }
     388             : 
     389             :     // Compute total construct conductivity and resistivity.
     390             : 
     391       14360 :     for (int Layer = 1; Layer <= LayersInConstruct; ++Layer) {
     392       10212 :         rs += lr(Layer); // Resistances in series sum algebraically
     393             :     }
     394             : 
     395        4148 :     cnd = 1.0 / rs; // Conductivity is the inverse of resistivity
     396             : 
     397        4148 :     bool RevConst = false;
     398             : 
     399        4148 :     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       23815 :         for (auto &otherConstruction : state.dataConstruction->Construct) {
     412       23815 :             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       20215 :             if (this->SourceSinkPresent) break; // Constr DO loop
     418             : 
     419       20178 :             if (this->TotLayers == otherConstruction.TotLayers) { // Same number of layers--now | check for reversed construct.
     420             : 
     421        6780 :                 RevConst = true;
     422             : 
     423        7653 :                 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        7485 :                     int OppositeLayer = this->TotLayers - Layer + 1;
     430             : 
     431        7485 :                     if (this->LayerPoint(Layer) != otherConstruction.LayerPoint(OppositeLayer)) {
     432             : 
     433        6612 :                         RevConst = false;
     434        6612 :                         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        6780 :                 if (RevConst && !otherConstruction.IsUsedCTF) {
     442           2 :                     RevConst = false;
     443             :                 }
     444             : 
     445        6780 :                 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         166 :                     this->CTFTimeStep = otherConstruction.CTFTimeStep;
     451         166 :                     this->NumHistories = otherConstruction.NumHistories;
     452         166 :                     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        1423 :                     for (int HistTerm = 0; HistTerm <= this->NumCTFTerms; ++HistTerm) {
     457             : 
     458        1257 :                         this->CTFInside[HistTerm] = otherConstruction.CTFOutside[HistTerm];
     459        1257 :                         this->CTFCross[HistTerm] = otherConstruction.CTFCross[HistTerm];
     460        1257 :                         this->CTFOutside[HistTerm] = otherConstruction.CTFInside[HistTerm];
     461        1257 :                         if (HistTerm != 0) this->CTFFlux[HistTerm] = otherConstruction.CTFFlux[HistTerm];
     462             : 
     463             :                     } // ... end of CTF history terms loop.
     464             : 
     465         166 :                     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        3803 :         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       13148 :             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        9511 :                 if ((ResLayer(Layer)) && (Layer > 1) && (Layer < LayersInConstruct)) {
     489         467 :                     Nodes(Layer) = 1;
     490         467 :                     dx(Layer) = dl(Layer);
     491             :                 } else {
     492        9044 :                     dxn = std::sqrt(2.0 * (rk(Layer) / rho(Layer) / cp(Layer)) * this->CTFTimeStep);
     493             : 
     494        9044 :                     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        9044 :                     if (ipts1 > Construction::MaxCTFTerms) { // Too many nodes
     500          22 :                         Nodes(Layer) = Construction::MaxCTFTerms;
     501        9022 :                     } else if (ipts1 < MinNodes) { // Too few nodes
     502        8210 :                         Nodes(Layer) = MinNodes;
     503             :                     } else { // Calculated number of nodes ok
     504         812 :                         Nodes(Layer) = ipts1;
     505             :                     }
     506             : 
     507        9044 :                     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        3637 :             this->rcmax = 0;
     515       13148 :             for (int Layer = 1; Layer <= LayersInConstruct; ++Layer) {
     516        9511 :                 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        3637 :             --this->rcmax;
     524        3637 :             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        3637 :             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        3637 :             dtn = 0.0;
     571        3637 :             this->CTFTimeStep = 0.0;
     572       13148 :             for (int Layer = 1; Layer <= LayersInConstruct; ++Layer) {
     573        9511 :                 if (Nodes(Layer) >= Construction::MaxCTFTerms) {
     574          25 :                     if (this->SolutionDimensions == 1) {
     575          25 :                         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          25 :                     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        3637 :             if (std::abs((state.dataGlobal->TimeStepZone - this->CTFTimeStep) / state.dataGlobal->TimeStepZone) > 0.1) {
     588             : 
     589        3637 :                 if (this->CTFTimeStep > state.dataGlobal->TimeStepZone) {
     590             : 
     591             :                     // CTFTimeStep larger than TimeStepZone:  Make sure TimeStepZone
     592             :                     // divides evenly into CTFTimeStep
     593          23 :                     this->NumHistories = int((this->CTFTimeStep / state.dataGlobal->TimeStepZone) + 0.5);
     594          23 :                     this->CTFTimeStep = state.dataGlobal->TimeStepZone * double(this->NumHistories);
     595             : 
     596             :                 } else {
     597             : 
     598             :                     // CTFTimeStep smaller than TimeStepZone:  Set to TimeStepZone
     599        3614 :                     this->CTFTimeStep = state.dataGlobal->TimeStepZone;
     600        3614 :                     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        3637 :             bool CTFConvrg = false;
     621             : 
     622        3637 :             this->AExp.allocate(this->rcmax, this->rcmax);
     623        3637 :             this->AExp = 0.0;
     624        3637 :             this->AMat.allocate(this->rcmax, this->rcmax);
     625        3637 :             this->AMat = 0.0;
     626        3637 :             this->AInv.allocate(this->rcmax, this->rcmax);
     627        3637 :             this->AInv = 0.0;
     628        3637 :             this->IdenMatrix.allocate(this->rcmax, this->rcmax);
     629        3637 :             this->IdenMatrix = 0.0;
     630       56538 :             for (int ir = 1; ir <= this->rcmax; ++ir) {
     631       52901 :                 this->IdenMatrix(ir, ir) = 1.0;
     632             :             }
     633        3637 :             this->e.dimension(this->rcmax, 0.0);
     634        3637 :             this->Gamma1.allocate(3, this->rcmax);
     635        3637 :             this->Gamma1 = 0.0;
     636        3637 :             this->Gamma2.allocate(3, this->rcmax);
     637        3637 :             this->Gamma2 = 0.0;
     638        3637 :             this->s.allocate(3, 4, this->rcmax);
     639        3637 :             this->s = 0.0;
     640             : 
     641        7376 :             while (!CTFConvrg) { // Begin CTF calculation loop ...
     642             : 
     643        3739 :                 this->BMat(3) = 0.0;
     644             : 
     645        3739 :                 if (this->SolutionDimensions == 1) {
     646             : 
     647             :                     // Set up intermediate calculations for the first layer.
     648        3735 :                     cap = rho(1) * cp(1) * dx(1);
     649        3735 :                     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        3735 :                     dxtmp = 1.0 / dx(1) / cap;
     654             : 
     655        3735 :                     this->AMat(1, 1) = -2.0 * rk(1) * dxtmp; // Assign the matrix values for the
     656        3735 :                     this->AMat(2, 1) = rk(1) * dxtmp;        // first node.
     657        3735 :                     this->BMat(1) = rk(1) * dxtmp;           // Assign non-zero value of BMat.
     658             : 
     659        3735 :                     int Layer = 1; // Initialize the "layer" counter
     660             : 
     661        3735 :                     int NodeInLayer = 2; // Initialize the node (in a layer) counter (already
     662             :                     // on the second node for the first layer
     663             : 
     664       51277 :                     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       47542 :                         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        6072 :                             cap = (rho(Layer) * cp(Layer) * dx(Layer) + rho(Layer + 1) * cp(Layer + 1) * dx(Layer + 1)) * 0.5;
     673             : 
     674        6072 :                             this->AMat(Node - 1, Node) = rk(Layer) / dx(Layer) / cap;                                      // Assign matrix
     675        6072 :                             this->AMat(Node, Node) = -1.0 * (rk(Layer) / dx(Layer) + rk(Layer + 1) / dx(Layer + 1)) / cap; // values for | the current
     676        6072 :                             this->AMat(Node + 1, Node) = rk(Layer + 1) / dx(Layer + 1) / cap;                              // node.
     677             : 
     678        6072 :                             NodeInLayer = 0; // At an interface, reset nodes in layer counter
     679        6072 :                             ++Layer;         // Also increment the layer counter
     680             : 
     681             :                         } else { // Standard node within any layer
     682             : 
     683       41470 :                             cap = rho(Layer) * cp(Layer) * dx(Layer);          // Intermediate
     684       41470 :                             dxtmp = 1.0 / dx(Layer) / cap;                     // calculations.
     685       41470 :                             this->AMat(Node - 1, Node) = rk(Layer) * dxtmp;    // Assign matrix
     686       41470 :                             this->AMat(Node, Node) = -2.0 * rk(Layer) * dxtmp; // values for the
     687       41470 :                             this->AMat(Node + 1, Node) = rk(Layer) * dxtmp;    // current node.
     688             :                         }
     689             : 
     690       47542 :                         ++NodeInLayer; // Increment nodes in layer counter
     691       47542 :                         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        3735 :                     cap = rho(LayersInConstruct) * cp(LayersInConstruct) * dx(LayersInConstruct);
     697        3735 :                     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        3735 :                     dxtmp = 1.0 / dx(LayersInConstruct) / cap;
     702             : 
     703        3735 :                     this->AMat(this->rcmax, this->rcmax) = -2.0 * rk(LayersInConstruct) * dxtmp; // Assign matrix
     704        3735 :                     this->AMat(this->rcmax - 1, this->rcmax) = rk(LayersInConstruct) * dxtmp;    // values for the
     705        3735 :                     this->BMat(2) = rk(LayersInConstruct) * dxtmp;                               // last node.
     706             : 
     707        3735 :                     this->CMat(1) = -rk(1) / dx(1);                                 // Compute the necessary elements
     708        3735 :                     this->CMat(2) = rk(LayersInConstruct) / dx(LayersInConstruct);  // of all other
     709        3735 :                     this->DMat(1) = rk(1) / dx(1);                                  // matrices for the state
     710        3735 :                     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           4 :                     amatx = rk(1) / (1.5 * rho(1) * cp(1) * dx(1) * dx(1));
     719           4 :                     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           4 :                     this->AMat(1, 1) = -2.0 * (amatx + amaty);
     728           4 :                     this->AMat(2, 1) = 2.0 * amaty;
     729           4 :                     this->AMat(this->NumOfPerpendNodes + 1, 1) = amatx;
     730             : 
     731          24 :                     for (int Node = 2; Node <= this->NumOfPerpendNodes - 1; ++Node) {
     732          20 :                         this->AMat(Node - 1, Node) = amaty;
     733          20 :                         this->AMat(Node, Node) = -2.0 * (amatx + amaty);
     734          20 :                         this->AMat(Node + 1, Node) = amaty;
     735          20 :                         this->AMat(Node + this->NumOfPerpendNodes, Node) = amatx;
     736             :                     }
     737             : 
     738           4 :                     this->AMat(this->NumOfPerpendNodes, this->NumOfPerpendNodes) = -2.0 * (amatx + amaty);
     739           4 :                     this->AMat(this->NumOfPerpendNodes - 1, this->NumOfPerpendNodes) = 2.0 * amaty;
     740           4 :                     this->AMat(this->NumOfPerpendNodes + this->NumOfPerpendNodes, this->NumOfPerpendNodes) = amatx;
     741             : 
     742           4 :                     BMat(1) = amatx;
     743             : 
     744           4 :                     int Layer = 1;
     745           4 :                     int NodeInLayer = 2;
     746           4 :                     amatx = rk(1) / (rho(1) * cp(1) * dx(1) * dx(1)); // Reset these to the normal capacitance
     747           4 :                     amaty = rk(1) / (rho(1) * cp(1) * dyn * dyn);     // Reset these to the normal capacitance
     748           4 :                     assert(this->NumOfPerpendNodes > 0);              // Autodesk:F2C++ Loop setup assumption
     749           4 :                     int const Node_stop(this->rcmax + 1 - 2 * this->NumOfPerpendNodes);
     750         112 :                     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         108 :                         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          92 :                             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          16 :                                 amatx = rk(Layer) / (rho(Layer) * cp(Layer) * dx(Layer) * dx(Layer));
     760          16 :                                 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          92 :                             this->AMat(Node, Node) = -2.0 * (amatx + amaty);
     767          92 :                             this->AMat(Node + 1, Node) = 2.0 * amaty;
     768          92 :                             this->AMat(Node - this->NumOfPerpendNodes, Node) = amatx;
     769          92 :                             this->AMat(Node + this->NumOfPerpendNodes, Node) = amatx;
     770             : 
     771         552 :                             for (int NodeInRow = 2; NodeInRow <= this->NumOfPerpendNodes - 1; ++NodeInRow) {
     772         460 :                                 int Node2 = Node + NodeInRow - 1;
     773         460 :                                 this->AMat(Node2 - 1, Node2) = amaty;
     774         460 :                                 this->AMat(Node2, Node2) = -2.0 * (amatx + amaty);
     775         460 :                                 this->AMat(Node2 + 1, Node2) = amaty;
     776         460 :                                 this->AMat(Node2 - this->NumOfPerpendNodes, Node2) = amatx;
     777         460 :                                 this->AMat(Node2 + this->NumOfPerpendNodes, Node2) = amatx;
     778             :                             }
     779             : 
     780          92 :                             int Node2 = Node - 1 + this->NumOfPerpendNodes;
     781          92 :                             this->AMat(Node2, Node2) = -2.0 * (amatx + amaty);
     782          92 :                             this->AMat(Node2 - 1, Node2) = 2.0 * amaty;
     783          92 :                             this->AMat(Node2 - this->NumOfPerpendNodes, Node2) = amatx;
     784          92 :                             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          16 :                             capavg = 0.5 * (rho(Layer) * cp(Layer) * dx(Layer) + rho(Layer + 1) * cp(Layer + 1) * dx(Layer + 1));
     789          16 :                             amatx = rk(Layer) / (capavg * dx(Layer));
     790          16 :                             amatxx = rk(Layer + 1) / (capavg * dx(Layer + 1));
     791          16 :                             amaty = (rk(Layer) * dx(Layer) + rk(Layer + 1) * dx(Layer + 1)) / (capavg * dyn * dyn);
     792             : 
     793          16 :                             this->AMat(Node, Node) = -amatx - amatxx - 2.0 * amaty;
     794          16 :                             this->AMat(Node + 1, Node) = 2.0 * amaty;
     795          16 :                             this->AMat(Node - this->NumOfPerpendNodes, Node) = amatx;
     796          16 :                             this->AMat(Node + this->NumOfPerpendNodes, Node) = amatxx;
     797             : 
     798          96 :                             for (int NodeInRow = 2; NodeInRow <= this->NumOfPerpendNodes - 1; ++NodeInRow) {
     799          80 :                                 int Node2 = Node + NodeInRow - 1;
     800          80 :                                 this->AMat(Node2 - 1, Node2) = amaty;
     801          80 :                                 this->AMat(Node2, Node2) = -amatx - amatxx - 2.0 * amaty;
     802          80 :                                 this->AMat(Node2 + 1, Node2) = amaty;
     803          80 :                                 this->AMat(Node2 - this->NumOfPerpendNodes, Node2) = amatx;
     804          80 :                                 this->AMat(Node2 + this->NumOfPerpendNodes, Node2) = amatxx;
     805             :                             }
     806             : 
     807          16 :                             int Node2 = Node - 1 + this->NumOfPerpendNodes;
     808          16 :                             this->AMat(Node2, Node2) = -amatx - amatxx - 2.0 * amaty;
     809          16 :                             this->AMat(Node2 - 1, Node2) = 2.0 * amaty;
     810          16 :                             this->AMat(Node2 - this->NumOfPerpendNodes, Node2) = amatx;
     811          16 :                             this->AMat(Node2 + this->NumOfPerpendNodes, Node2) = amatxx;
     812             : 
     813          16 :                             if (Node == this->NodeSource) BMat(3) = 2.0 * double(this->NumOfPerpendNodes - 1) / capavg;
     814          16 :                             NodeInLayer = 0;
     815          16 :                             ++Layer;
     816             :                         }
     817         108 :                         ++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           4 :                     amatx /= 1.5;
     828           4 :                     amaty /= 1.5;
     829             : 
     830           4 :                     int Node = this->rcmax + 1 - this->NumOfPerpendNodes;
     831           4 :                     this->AMat(Node, Node) = -2.0 * (amatx + amaty);
     832           4 :                     this->AMat(Node + 1, Node) = 2.0 * amaty;
     833           4 :                     this->AMat(Node - this->NumOfPerpendNodes, Node) = amatx;
     834             : 
     835          24 :                     for (int thisNode = this->rcmax + 2 - this->NumOfPerpendNodes; thisNode <= this->rcmax - 1; ++thisNode) {
     836          20 :                         this->AMat(thisNode - 1, thisNode) = amaty;
     837          20 :                         this->AMat(thisNode, thisNode) = -2.0 * (amatx + amaty);
     838          20 :                         this->AMat(thisNode + 1, thisNode) = amaty;
     839          20 :                         this->AMat(thisNode - this->NumOfPerpendNodes, thisNode) = amatx;
     840             :                     }
     841             : 
     842           4 :                     this->AMat(this->rcmax, this->rcmax) = -2.0 * (amatx + amaty);
     843           4 :                     this->AMat(this->rcmax - 1, this->rcmax) = 2.0 * amaty;
     844           4 :                     this->AMat(this->rcmax - this->NumOfPerpendNodes, this->rcmax) = amatx;
     845             : 
     846           4 :                     this->BMat(2) = amatx;
     847             : 
     848           4 :                     this->CMat(1) = -rk(1) / dx(1) / double(this->NumOfPerpendNodes - 1);
     849           4 :                     this->CMat(2) = rk(LayersInConstruct) / dx(LayersInConstruct) / double(this->NumOfPerpendNodes - 1);
     850             : 
     851           4 :                     this->DMat(1) = rk(1) / dx(1) / double(this->NumOfPerpendNodes - 1);
     852           4 :                     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        3739 :                 DisplayString(state, "Calculating CTFs for \"" + this->Name + "\"");
     862             : 
     863             :                 //          CALL DisplayNumberAndString(ConstrNum,'Matrix exponential for Construction #')
     864        3739 :                 this->calculateExponentialMatrix(); // Compute exponential of AMat
     865             : 
     866             :                 //          CALL DisplayNumberAndString(ConstrNum,'Invert Matrix for Construction #')
     867        3739 :                 this->calculateInverseMatrix(); // Compute inverse of AMat
     868             : 
     869             :                 //          CALL DisplayNumberAndString(ConstrNum,'Gamma calculation for Construction #')
     870        3739 :                 this->calculateGammas();
     871             :                 // Compute "gamma"s from AMat, AExp, and AInv
     872             : 
     873             :                 //          CALL DisplayNumberAndString(ConstrNum,'Compute CTFs for Construction #')
     874        3739 :                 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        3739 :                 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        3739 :                 if (this->NumCTFTerms > (Construction::MaxCTFTerms - 1)) {
     889          42 :                     ++this->NumHistories;
     890          42 :                     this->CTFTimeStep += state.dataGlobal->TimeStepZone;
     891          42 :                     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        3739 :                 if (CTFConvrg) {
     899        3697 :                     SumXi = this->s0(2, 2);
     900        3697 :                     SumYi = this->s0(1, 2);
     901        3697 :                     SumZi = this->s0(1, 1);
     902       29877 :                     for (int HistTerm = 1; HistTerm <= this->NumCTFTerms; ++HistTerm) {
     903       26180 :                         SumXi += this->s(2, 2, HistTerm);
     904       26180 :                         SumYi += this->s(1, 2, HistTerm);
     905       26180 :                         SumZi += this->s(1, 1, HistTerm);
     906             :                     }
     907        3697 :                     SumXi = std::abs(SumXi);
     908        3697 :                     SumYi = std::abs(SumYi);
     909        3697 :                     SumZi = std::abs(SumZi);
     910        3697 :                     BiggestSum = max(SumXi, SumYi, SumZi);
     911        3697 :                     if (BiggestSum > 0.0) {
     912        7335 :                         if (((std::abs(SumXi - SumYi) / BiggestSum) > MaxAllowedCTFSumError) ||
     913        3638 :                             ((std::abs(SumZi - SumYi) / BiggestSum) > MaxAllowedCTFSumError)) {
     914          60 :                             ++this->NumHistories;
     915          60 :                             this->CTFTimeStep += state.dataGlobal->TimeStepZone;
     916          60 :                             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        3739 :                 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->Material(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->Material(this->LayerPoint(Layer))->Name));
     935             :                         } else {
     936           0 :                             ShowContinueError(state, format("(inside)=\"{}\"", state.dataMaterial->Material(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         345 :         this->CTFTimeStep = state.dataGlobal->TimeStepZone;
     970         345 :         this->NumHistories = 1;
     971         345 :         this->NumCTFTerms = 1;
     972             : 
     973         345 :         this->s0(1, 1) = cnd;  // CTFs for current time
     974         345 :         this->s0(2, 1) = -cnd; // step are set to the
     975         345 :         this->s0(1, 2) = cnd;  // overall conductance
     976         345 :         this->s0(2, 2) = -cnd; // of the construction.
     977             : 
     978         345 :         this->e.allocate(1);
     979         345 :         this->e = 0.0;
     980         345 :         this->s.allocate(2, 2, 1);
     981         345 :         this->s = 0.0;
     982         345 :         this->s(1, 1, 1) = 0.0; // CTF temperature
     983         345 :         this->s(2, 1, 1) = 0.0; // and flux
     984         345 :         this->s(1, 2, 1) = 0.0; // history terms
     985         345 :         this->s(2, 2, 1) = 0.0; // are all
     986         345 :         this->e(1) = 0.0;       // zero.
     987             : 
     988         345 :         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         345 :         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        4148 :     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        3982 :         this->CTFOutside[0] = this->s0(1, 1) * DataConversions::CFU;
    1010        3982 :         this->CTFCross[0] = this->s0(1, 2) * DataConversions::CFU;
    1011        3982 :         this->CTFInside[0] = -this->s0(2, 2) * DataConversions::CFU;
    1012        3982 :         if (this->SourceSinkPresent) {
    1013             :             // QTFs...
    1014          39 :             this->CTFSourceOut[0] = this->s0(3, 1);
    1015          39 :             this->CTFSourceIn[0] = this->s0(3, 2);
    1016             :             // QTFs for temperature calculation at source/sink location
    1017          39 :             this->CTFTSourceOut[0] = this->s0(1, 3);
    1018          39 :             this->CTFTSourceIn[0] = this->s0(2, 3);
    1019          39 :             this->CTFTSourceQ[0] = this->s0(3, 3) / DataConversions::CFU;
    1020          39 :             if (this->TempAfterLayer != 0) {
    1021             :                 // QTFs for user specified interior temperature calculations...
    1022          39 :                 this->CTFTUserOut[0] = this->s0(1, 4);
    1023          39 :                 this->CTFTUserIn[0] = this->s0(2, 4);
    1024          39 :                 this->CTFTUserSource[0] = this->s0(3, 4) / DataConversions::CFU;
    1025             :             }
    1026             :         }
    1027             : 
    1028       29803 :         for (int HistTerm = 1; HistTerm <= this->NumCTFTerms; ++HistTerm) {
    1029             :             // "REGULAR" CTFs...
    1030       25821 :             this->CTFOutside[HistTerm] = this->s(1, 1, HistTerm) * DataConversions::CFU;
    1031       25821 :             this->CTFCross[HistTerm] = this->s(1, 2, HistTerm) * DataConversions::CFU;
    1032       25821 :             this->CTFInside[HistTerm] = -this->s(2, 2, HistTerm) * DataConversions::CFU;
    1033       25821 :             if (HistTerm != 0) this->CTFFlux[HistTerm] = -e(HistTerm);
    1034       25821 :             if (this->SourceSinkPresent) {
    1035             :                 // QTFs...
    1036         379 :                 this->CTFSourceOut[HistTerm] = this->s(3, 1, HistTerm);
    1037         379 :                 this->CTFSourceIn[HistTerm] = this->s(3, 2, HistTerm);
    1038             :                 // QTFs for temperature calculation at source/sink location
    1039         379 :                 this->CTFTSourceOut[HistTerm] = this->s(1, 3, HistTerm);
    1040         379 :                 this->CTFTSourceIn[HistTerm] = this->s(2, 3, HistTerm);
    1041         379 :                 this->CTFTSourceQ[HistTerm] = this->s(3, 3, HistTerm) / DataConversions::CFU;
    1042         379 :                 if (this->TempAfterLayer != 0) {
    1043             :                     // QTFs for user specified interior temperature calculations...
    1044         379 :                     this->CTFTUserOut[HistTerm] = this->s(1, 4, HistTerm);
    1045         379 :                     this->CTFTUserIn[HistTerm] = this->s(2, 4, HistTerm);
    1046         379 :                     this->CTFTUserSource[HistTerm] = this->s(3, 4, HistTerm) / DataConversions::CFU;
    1047             :                 }
    1048             :             }
    1049             :         }
    1050             : 
    1051             :     } // ... end of the reversed construction IF block.
    1052             : 
    1053        4148 :     this->UValue = cnd * DataConversions::CFU;
    1054             : 
    1055        4148 :     if (allocated(this->AExp)) this->AExp.deallocate();
    1056        4148 :     if (allocated(this->AMat)) AMat.deallocate();
    1057        4148 :     if (allocated(this->AInv)) this->AInv.deallocate();
    1058        4148 :     if (allocated(this->IdenMatrix)) this->IdenMatrix.deallocate();
    1059        4148 :     if (allocated(this->e)) this->e.deallocate();
    1060        4148 :     if (allocated(this->Gamma1)) this->Gamma1.deallocate();
    1061        4148 :     if (allocated(this->Gamma2)) this->Gamma2.deallocate();
    1062        4148 :     if (allocated(this->s)) this->s.deallocate();
    1063        4148 : }
    1064             : 
    1065        3739 : 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        3739 :     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        3739 :     Array2D<Real64> AMat1; // AMat factored by (delt/2^k)
    1121        3739 :     Array2D<Real64> AMato; // AMat raised to the previous power (power of AMat1-1)
    1122        3739 :     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        3739 :     AMat1.allocate(this->rcmax, this->rcmax);
    1140        3739 :     AMato.allocate(this->rcmax, this->rcmax);
    1141        3739 :     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        3739 :     AMat1 = AMat;
    1146             : 
    1147             :     //  Other arrays are initialized to zero.
    1148        3739 :     this->AExp = 0.0;
    1149        3739 :     AMato = 0.0;
    1150        3739 :     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        3739 :     AMatRowNormMax = 0.0; // Start of Step 1 ...
    1160             : 
    1161       59563 :     for (i = 1; i <= this->rcmax; ++i) {
    1162             : 
    1163       55824 :         AMatRowNorm = 0.0;
    1164     1227166 :         for (j = 1; j <= this->rcmax; ++j) {
    1165     1171342 :             AMatRowNorm += std::abs(AMat1(j, i));
    1166             :         }
    1167             : 
    1168       55824 :         AMatRowNorm *= this->CTFTimeStep;
    1169             : 
    1170       55824 :         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        3739 :     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        3739 :     fact = this->CTFTimeStep / std::pow(2.0, k); // Start of Step 3 ...
    1184        3739 :     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        3739 :     CheckVal = min(3.0 * AMatRowNormMax + 6.0, 100.0);
    1209        3739 :     l = int(CheckVal);
    1210             : 
    1211             :     // Step 5, page 128:  Calculate the exponential.  First, add the
    1212             :     // linear term to the identity matrix.
    1213        3739 :     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        3739 :     AMato = AMat1;
    1221             : 
    1222        3739 :     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      283357 :     while (i < l) { // Begin power raising loop ...
    1227             : 
    1228      279618 :         ++i;                     // Increment the loop counter
    1229      279618 :         bool SigFigLimit = true; // Set the significant factor limit flag
    1230             : 
    1231     5214625 :         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   116151338 :             for (ic = 1; ic <= this->rcmax; ++ic) {
    1239   111216331 :                 AMatN(ic, ir) = 0.0;
    1240  5469208604 :                 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  5357992273 :                     if (std::abs(AMat1(ic, ict)) > Constant::rTinyValue) {
    1245   350789347 :                         if (std::abs(AMato(ict, ir)) > std::abs(double(i) * Constant::rTinyValue / AMat1(ic, ict)))
    1246    10664468 :                             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      279618 :         AMato = AMatN;
    1254      279618 :         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      283948 :         for (ir = 1; ir <= this->rcmax; ++ir) {
    1259     2151395 :             for (ic = 1; ic <= this->rcmax; ++ic) {
    1260             :                 // Test of limit criteria:
    1261     2147065 :                 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     1906191 :                     if (std::abs(AMato(ic, ir) / this->AExp(ic, ir)) > DPLimit) {
    1270       38097 :                         SigFigLimit = false;
    1271       38097 :                         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      240874 :                     SigFigLimit = false;
    1278      240874 :                     break; // DO loop (anytime SigFigLimit is false, AMat must continue to be raised another power)
    1279             :                 }
    1280             :             }
    1281      283301 :             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      279618 :         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       40782 :     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       37083 :         AMato = this->AExp;
    1301       37083 :         bool Backup = true; // Used when numerics get to small in Exponentiation
    1302       37083 :         this->AExp = 0.0;
    1303             : 
    1304             :         // Multiply the old value of AExp (AMato) by itself and store in AExp.
    1305      637249 :         for (ir = 1; ir <= this->rcmax; ++ir) {
    1306    13679802 :             for (ic = 1; ic <= this->rcmax; ++ic) {
    1307   665972238 :                 for (idm = 1; idm <= this->rcmax; ++idm) {
    1308   652892602 :                     if (std::abs(AMato(idm, ir) * AMato(ic, idm)) > Constant::rTinyValue) {
    1309   208966055 :                         this->AExp(ic, ir) += AMato(idm, ir) * AMato(ic, idm);
    1310   208966055 :                         Backup = false;
    1311             :                     }
    1312             :                 }
    1313             :             }
    1314             :         }
    1315             :         // Backup is true when every item of AExp didnt pass the TinyLimit test
    1316       37083 :         if (Backup) {
    1317          40 :             this->AExp = AMato;
    1318          40 :             break;
    1319             :         }
    1320             : 
    1321             :     } // ... end of squaring loop and Step 6.
    1322             : 
    1323        3739 :     AMat1.deallocate();
    1324        3739 :     AMato.deallocate();
    1325        3739 :     AMatN.deallocate();
    1326        3739 : }
    1327             : 
    1328        3739 : 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        3739 :     Array2D<Real64> AMat1; // Intermediate calculation matrix equivalent at first to AMat
    1355             : 
    1356             :     // Subroutine initializations ...
    1357        3739 :     AMat1.allocate(this->rcmax, this->rcmax);
    1358             : 
    1359        3739 :     AMat1 = AMat;                  // Set AMat1 = AMat to avoid AMat changes
    1360        3739 :     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       55824 :     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      609844 :         for (int ic = ir + 1; ic <= this->rcmax; ++ic) {
    1376      557759 :             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      609844 :         for (int ic = 1; ic <= ir; ++ic) {
    1382      557759 :             this->AInv(ic, ir) /= AMat1(ir, ir);
    1383             :         }
    1384             : 
    1385       52085 :         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      609844 :         for (int irr = ir + 1; irr <= this->rcmax; ++irr) { // Start of row reduction loop...
    1391             : 
    1392    18189746 :             for (int ic = ir + 1; ic <= this->rcmax; ++ic) {
    1393    17631987 :                 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     9652632 :             for (int ic = 1; ic <= ir; ++ic) {
    1401     9094873 :                 this->AInv(ic, irr) -= AMat1(ir, irr) * this->AInv(ic, ir);
    1402             :             }
    1403             : 
    1404      557759 :             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       59563 :     for (int ic = 1; ic <= this->rcmax; ++ic) {
    1417       55824 :         this->AInv(ic, this->rcmax) /= AMat1(this->rcmax, this->rcmax);
    1418             :     }
    1419        3739 :     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       55824 :     for (int ir = this->rcmax; ir >= 2; --ir) { // Begin reverse elimination loop ...
    1435      609844 :         for (int irr = 1; irr <= ir - 1; ++irr) {
    1436    27284619 :             for (int ic = 1; ic <= this->rcmax; ++ic) {
    1437    26726860 :                 this->AInv(ic, irr) -= AMat1(ir, irr) * this->AInv(ic, ir);
    1438             :             }
    1439      557759 :             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        3739 :     AMat1.deallocate();
    1447        3739 : }
    1448             : 
    1449        3739 : 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        3739 :     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        3739 :     ATemp.allocate(this->rcmax, this->rcmax);
    1483        3739 :     ATemp = this->AExp - this->IdenMatrix;
    1484        3739 :     Gamma1 = 0.0;
    1485             : 
    1486       59563 :     for (int i = 1; i <= this->rcmax; ++i) {
    1487             : 
    1488     1227166 :         for (int is1 = 1; is1 <= this->rcmax; ++is1) {
    1489             : 
    1490     1171342 :             if (this->SolutionDimensions == 1) {
    1491     1006506 :                 this->Gamma1(1, i) += this->AInv(is1, i) * ATemp(1, is1) * this->BMat(1);
    1492     1006506 :                 this->Gamma1(2, i) += this->AInv(is1, i) * ATemp(this->rcmax, is1) * this->BMat(2);
    1493             :             } else { // SolutionDimensions = 2
    1494     1318688 :                 for (int SurfNode = 1; SurfNode <= this->NumOfPerpendNodes; ++SurfNode) {
    1495     1153852 :                     this->Gamma1(1, i) += this->AInv(is1, i) * ATemp(SurfNode, is1) * this->BMat(1);
    1496     1153852 :                     this->Gamma1(2, i) += this->AInv(is1, i) * ATemp(this->rcmax + 1 - SurfNode, is1) * this->BMat(2);
    1497             :                 }
    1498             :             }
    1499             : 
    1500     1171342 :             if (this->NodeSource > 0) {
    1501      192113 :                 this->Gamma1(3, i) += this->AInv(is1, i) * ATemp(this->NodeSource, is1) * this->BMat(3);
    1502             :             }
    1503             :         }
    1504             :     }
    1505             : 
    1506        3739 :     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        3739 :     Gamma2 = 0.0;
    1511             : 
    1512       59563 :     for (int i = 1; i <= this->rcmax; ++i) {
    1513             : 
    1514      223296 :         for (int j = 1; j <= 3; ++j) {
    1515             : 
    1516     3681498 :             for (int is1 = 1; is1 <= this->rcmax; ++is1) {
    1517             : 
    1518     3514026 :                 if (this->SolutionDimensions == 1) {
    1519     3019518 :                     if ((j == 1) && (is1 == 1)) {
    1520       55012 :                         this->Gamma2(j, i) += this->AInv(is1, i) * (this->Gamma1(j, is1) / this->CTFTimeStep - this->BMat(1));
    1521     2964506 :                     } else if ((j == 2) && (is1 == this->rcmax)) {
    1522       55012 :                         this->Gamma2(j, i) += this->AInv(is1, i) * (this->Gamma1(j, is1) / this->CTFTimeStep - this->BMat(2));
    1523     2909494 :                     } else if ((j == 3) && (is1 == this->NodeSource)) {
    1524         995 :                         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     2908499 :                         this->Gamma2(j, i) += this->AInv(is1, i) * (this->Gamma1(j, is1) / this->CTFTimeStep);
    1527             :                     }
    1528             :                 } else { // SolutionDimensions = 2
    1529      494508 :                     if ((j == 1) && ((is1 >= 1) && (is1 <= this->NumOfPerpendNodes))) {
    1530        5684 :                         this->Gamma2(j, i) += this->AInv(is1, i) * (this->Gamma1(j, is1) / this->CTFTimeStep - this->BMat(1));
    1531      488824 :                     } else if ((j == 2) && ((is1 <= this->rcmax) && (is1 >= this->rcmax + 1 - this->NumOfPerpendNodes))) {
    1532        5684 :                         this->Gamma2(j, i) += this->AInv(is1, i) * (this->Gamma1(j, is1) / this->CTFTimeStep - this->BMat(2));
    1533      483140 :                     } else if ((j == 3) && (is1 == this->NodeSource)) {
    1534         812 :                         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      482328 :                         this->Gamma2(j, i) += this->AInv(is1, i) * (this->Gamma1(j, is1) / this->CTFTimeStep);
    1537             :                     }
    1538             :                 }
    1539             :             }
    1540             :         }
    1541             :     }
    1542        3739 : }
    1543             : 
    1544        3739 : 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        3739 :     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        3739 :     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        3739 :     Array2D<Real64> Rnew;  // Current R matrix
    1583        3739 :     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        3739 :     PhiR0.allocate(this->rcmax, this->rcmax);
    1589        3739 :     Rnew.allocate(this->rcmax, this->rcmax);
    1590        3739 :     Rold.allocate(this->rcmax, this->rcmax);
    1591        3739 :     PhiR0 = 0.0;
    1592        3739 :     Rold = 0.0;
    1593             : 
    1594        3739 :     this->s0 = 0.0;
    1595        3739 :     this->s = 0.0;
    1596        3739 :     this->e = 0.0;
    1597        3739 :     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       59563 :     for (int i = 1; i <= this->rcmax; ++i) {
    1603      223296 :         for (int j = 1; j <= 3; ++j) {
    1604      167472 :             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        3739 :     if (this->SolutionDimensions == 1) {
    1614        3735 :         this->s0(1, 1) = this->CMat(1) * this->Gamma2(1, 1) + this->DMat(1);
    1615        3735 :         this->s0(2, 1) = this->CMat(1) * this->Gamma2(2, 1);
    1616        3735 :         this->s0(3, 1) = this->CMat(1) * this->Gamma2(3, 1);
    1617        3735 :         this->s0(1, 2) = this->CMat(2) * this->Gamma2(1, this->rcmax);
    1618        3735 :         this->s0(2, 2) = this->CMat(2) * this->Gamma2(2, this->rcmax) + this->DMat(2);
    1619        3735 :         this->s0(3, 2) = this->CMat(2) * this->Gamma2(3, this->rcmax);
    1620             :     } else { // SolutionDimensions = 2
    1621          32 :         for (int SurfNode = 1; SurfNode <= this->NumOfPerpendNodes; ++SurfNode) {
    1622          28 :             if ((SurfNode == 1) || (SurfNode == this->NumOfPerpendNodes)) {
    1623           8 :                 SurfNodeFac = 0.5;
    1624             :             } else {
    1625          20 :                 SurfNodeFac = 1.0;
    1626             :             }
    1627          28 :             this->s0(1, 1) += SurfNodeFac * this->CMat(1) * this->Gamma2(1, SurfNode);
    1628          28 :             this->s0(2, 1) += SurfNodeFac * this->CMat(1) * this->Gamma2(2, SurfNode);
    1629          28 :             this->s0(3, 1) += SurfNodeFac * this->CMat(1) * this->Gamma2(3, SurfNode);
    1630          28 :             this->s0(1, 2) += SurfNodeFac * this->CMat(2) * this->Gamma2(1, this->rcmax + 1 - SurfNode);
    1631          28 :             this->s0(2, 2) += SurfNodeFac * this->CMat(2) * this->Gamma2(2, this->rcmax + 1 - SurfNode);
    1632          28 :             this->s0(3, 2) += SurfNodeFac * this->CMat(2) * this->Gamma2(3, this->rcmax + 1 - SurfNode);
    1633             :         }
    1634           4 :         this->s0(1, 1) += double(this->NumOfPerpendNodes - 1) * this->DMat(1);
    1635           4 :         this->s0(2, 2) += double(this->NumOfPerpendNodes - 1) * this->DMat(2);
    1636             :     }
    1637             : 
    1638        3739 :     if (this->NodeSource > 0) {
    1639          41 :         this->s0(1, 3) = this->Gamma2(1, this->NodeSource);
    1640          41 :         this->s0(2, 3) = this->Gamma2(2, this->NodeSource);
    1641          41 :         this->s0(3, 3) = this->Gamma2(3, this->NodeSource);
    1642             :     }
    1643        3739 :     if (this->NodeUserTemp > 0) {
    1644          41 :         this->s0(1, 4) = this->Gamma2(1, this->NodeUserTemp);
    1645          41 :         this->s0(2, 4) = this->Gamma2(2, this->NodeUserTemp);
    1646          41 :         this->s0(3, 4) = this->Gamma2(3, this->NodeUserTemp);
    1647             :     }
    1648             : 
    1649             :     // Check for and enforce symmetry in the cross term (Y)
    1650        3739 :     if (std::abs(this->s0(2, 1)) != std::abs(this->s0(1, 2))) {
    1651        3692 :         avg = (std::abs(this->s0(2, 1)) + std::abs(this->s0(1, 2))) * 0.5;
    1652        3692 :         this->s0(2, 1) *= avg / std::abs(this->s0(2, 1));
    1653        3692 :         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        3739 :     int inum = 1;      // Set history term counter
    1659        3739 :     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       30033 :     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       26294 :         trace = 0.0;
    1671             : 
    1672      484589 :         for (int ir = 1; ir <= this->rcmax; ++ir) {
    1673             : 
    1674    12134968 :             for (int ic = 1; ic <= this->rcmax; ++ic) {
    1675    11676673 :                 PhiR0(ic, ir) = 0.0;
    1676   768204754 :                 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   756528081 :                     if (std::abs(Rnew(ic, is)) > Constant::rTinyValue) {
    1680   683956943 :                         if (std::abs(this->AExp(is, ir)) > std::abs(Constant::rTinyValue / Rnew(ic, is)))
    1681   549222215 :                             PhiR0(ic, ir) += this->AExp(is, ir) * Rnew(ic, is);
    1682             :                     }
    1683             :                 }
    1684             :             }
    1685             : 
    1686      458295 :             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       26294 :         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      484589 :         for (int ir = 1; ir <= this->rcmax; ++ir) {
    1698    12134968 :             for (int ic = 1; ic <= this->rcmax; ++ic) {
    1699    11676673 :                 Rold(ic, ir) = Rnew(ic, ir);
    1700    11676673 :                 Rnew(ic, ir) = PhiR0(ic, ir);
    1701             :             }
    1702      458295 :             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       26294 :         if (this->SolutionDimensions == 1) {
    1709      104912 :             for (int j = 1; j <= 3; ++j) {
    1710     1413375 :                 for (int is2 = 1; is2 <= this->rcmax; ++is2) {
    1711     1334691 :                     this->s(j, 1, inum) += this->CMat(1) * (Rold(is2, 1) * this->Gamma1(j, is2) + Rnew(is2, 1) * this->Gamma2(j, is2));
    1712     2669382 :                     this->s(j, 2, inum) +=
    1713     1334691 :                         this->CMat(2) * (Rold(is2, this->rcmax) * this->Gamma1(j, is2) + Rnew(is2, this->rcmax) * this->Gamma2(j, is2));
    1714     1334691 :                     if (this->NodeSource > 0) {
    1715       29181 :                         this->s(j, 3, inum) +=
    1716       29181 :                             (Rold(is2, this->NodeSource) * this->Gamma1(j, is2) + Rnew(is2, this->NodeSource) * this->Gamma2(j, is2));
    1717             :                     }
    1718     1334691 :                     if (this->NodeUserTemp > 0) {
    1719       29181 :                         this->s(j, 4, inum) +=
    1720       29181 :                             (Rold(is2, this->NodeUserTemp) * this->Gamma1(j, is2) + Rnew(is2, this->NodeUserTemp) * this->Gamma2(j, is2));
    1721             :                     }
    1722             :                 }
    1723       78684 :                 if (j != 3) this->s(j, j, inum) += this->e(inum) * this->DMat(j);
    1724             :             }
    1725             :         } else { // SolutionDimensions = 2
    1726         264 :             for (int j = 1; j <= 3; ++j) {
    1727       40392 :                 for (int is2 = 1; is2 <= this->rcmax; ++is2) {
    1728      321552 :                     for (int SurfNode = 1; SurfNode <= this->NumOfPerpendNodes; ++SurfNode) {
    1729      281358 :                         if ((SurfNode == 1) || (SurfNode == this->NumOfPerpendNodes)) {
    1730       80388 :                             SurfNodeFac = 0.5;
    1731             :                         } else {
    1732      200970 :                             SurfNodeFac = 1.0;
    1733             :                         }
    1734      562716 :                         this->s(j, 1, inum) +=
    1735      281358 :                             SurfNodeFac * this->CMat(1) * (Rold(is2, SurfNode) * this->Gamma1(j, is2) + Rnew(is2, SurfNode) * this->Gamma2(j, is2));
    1736      562716 :                         this->s(j, 2, inum) += SurfNodeFac * this->CMat(2) *
    1737      281358 :                                                (Rold(is2, this->rcmax + 1 - SurfNode) * this->Gamma1(j, is2) +
    1738      281358 :                                                 Rnew(is2, this->rcmax + 1 - SurfNode) * this->Gamma2(j, is2));
    1739             :                     }
    1740       40194 :                     if (this->NodeSource > 0) {
    1741       40194 :                         this->s(j, 3, inum) +=
    1742       40194 :                             (Rold(is2, this->NodeSource) * this->Gamma1(j, is2) + Rnew(is2, this->NodeSource) * this->Gamma2(j, is2));
    1743             :                     }
    1744       40194 :                     if (this->NodeUserTemp > 0) {
    1745       40194 :                         this->s(j, 4, inum) +=
    1746       40194 :                             (Rold(is2, this->NodeUserTemp) * this->Gamma1(j, is2) + Rnew(is2, this->NodeUserTemp) * this->Gamma2(j, is2));
    1747             :                     }
    1748             :                 }
    1749             :             }
    1750          66 :             this->s(1, 1, inum) += this->e(inum) * this->DMat(1) * double(this->NumOfPerpendNodes - 1);
    1751          66 :             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       26294 :         if (std::abs(s(2, 1, inum)) != std::abs(s(1, 2, inum))) {
    1756       26222 :             avg = (std::abs(s(2, 1, inum)) + std::abs(s(1, 2, inum))) * 0.5;
    1757       26222 :             this->s(2, 1, inum) *= avg / std::abs(s(2, 1, inum));
    1758       26222 :             this->s(1, 2, inum) *= avg / std::abs(s(1, 2, inum));
    1759             :         }
    1760             : 
    1761             :         // Check for convergence of the CTFs.
    1762       26294 :         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       26294 :             rat = std::abs(e(inum) / this->e(1));
    1771             : 
    1772       26294 :             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        2988 :                 this->NumCTFTerms = inum;
    1777        2988 :                 CTFConvrg = true; // CTF calculations have converged--set logical.
    1778             :             }
    1779             :         } // ... end of convergence check block.
    1780             : 
    1781       26294 :         ++inum;
    1782             : 
    1783             :     } // ... end of CTF calculation loop.
    1784             :     // Continue to the next coefficient if the solution has not converged
    1785        3739 :     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         751 :         trace = 0.0;
    1791             : 
    1792        5094 :         for (int ir = 1; ir <= this->rcmax; ++ir) {
    1793       32760 :             for (int is = 1; is <= this->rcmax; ++is) {
    1794       28417 :                 trace += this->AExp(is, ir) * Rnew(ir, is);
    1795             :             }
    1796             :         }
    1797             : 
    1798         751 :         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         751 :         if (this->SolutionDimensions == 1) {
    1804        3004 :             for (int j = 1; j <= 3; ++j) {
    1805       15282 :                 for (int is2 = 1; is2 <= this->rcmax; ++is2) {
    1806       13029 :                     this->s(j, 1, this->rcmax) += this->CMat(1) * Rnew(is2, 1) * this->Gamma1(j, is2);
    1807       13029 :                     this->s(j, 2, this->rcmax) += this->CMat(2) * Rnew(is2, this->rcmax) * this->Gamma1(j, is2);
    1808       13029 :                     if (this->NodeSource > 0) {
    1809           0 :                         this->s(j, 3, this->rcmax) += Rnew(is2, this->NodeSource) * this->Gamma1(j, is2);
    1810             :                     }
    1811       13029 :                     if (this->NodeUserTemp > 0) {
    1812           0 :                         this->s(j, 4, this->rcmax) += Rnew(is2, this->NodeUserTemp) * this->Gamma1(j, is2);
    1813             :                     }
    1814             :                 }
    1815             :             }
    1816         751 :             this->s(1, 1, this->rcmax) += this->e(this->rcmax) * this->DMat(1);
    1817         751 :             this->s(2, 2, this->rcmax) += this->e(this->rcmax) * this->DMat(2);
    1818         751 :             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         751 :         if (std::abs(s(2, 1, this->rcmax)) != std::abs(s(1, 2, this->rcmax))) {
    1846         751 :             avg = (std::abs(s(2, 1, this->rcmax)) + std::abs(s(1, 2, this->rcmax))) * 0.5;
    1847         751 :             this->s(2, 1, this->rcmax) *= avg / std::abs(s(2, 1, this->rcmax));
    1848         751 :             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        3739 :     PhiR0.deallocate();
    1854        3739 :     Rnew.deallocate();
    1855        3739 :     Rold.deallocate();
    1856        3739 : }
    1857             : 
    1858        1873 : 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        1873 :     print(state.files.eio,
    1863             :           Format_700,
    1864        1873 :           this->Name,
    1865             :           cCounter,
    1866        1873 :           this->TotLayers,
    1867        1873 :           this->NumCTFTerms,
    1868        1873 :           this->CTFTimeStep,
    1869        1873 :           this->UValue,
    1870        1873 :           this->OutsideAbsorpThermal,
    1871        1873 :           this->InsideAbsorpThermal,
    1872        1873 :           this->OutsideAbsorpSolar,
    1873        1873 :           this->InsideAbsorpSolar,
    1874        1873 :           Material::surfaceRoughnessNames[(int)this->OutsideRoughness]);
    1875             : 
    1876        6267 :     for (int I = 1; I <= this->TotLayers; ++I) {
    1877        4394 :         int Layer = this->LayerPoint(I);
    1878        4394 :         auto const *thisMaterial = state.dataMaterial->Material(Layer);
    1879        4394 :         switch (thisMaterial->group) {
    1880         124 :         case Material::Group::Air: {
    1881             :             static constexpr std::string_view Format_702(" Material:Air,{},{:12.4N}\n");
    1882         124 :             print(state.files.eio, Format_702, thisMaterial->Name, thisMaterial->Resistance);
    1883         124 :         } break;
    1884        4270 :         default: {
    1885             :             static constexpr std::string_view Format_701(" Material CTF Summary,{},{:8.4F},{:14.3F},{:11.3F},{:13.3F},{:12.4N}\n");
    1886        4270 :             Material::MaterialBase const *mp = thisMaterial;
    1887        4270 :             print(state.files.eio, Format_701, mp->Name, mp->Thickness, mp->Conductivity, mp->Density, mp->SpecHeat, mp->Resistance);
    1888        4270 :         } break;
    1889             :         }
    1890             :     }
    1891             : 
    1892       16854 :     for (int I = this->NumCTFTerms; I >= 0; --I) {
    1893       14981 :         if (I != 0) {
    1894             :             static constexpr std::string_view Format_703(" CTF,{:4},{:20.8N},{:20.8N},{:20.8N},{:20.8N}\n");
    1895       13108 :             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        1873 :             print(state.files.eio, Format_704, I, this->CTFOutside[I], this->CTFCross[I], this->CTFInside[I]);
    1899             :         }
    1900             :     }
    1901             : 
    1902        1873 :     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        1873 : }
    1922             : 
    1923        4148 : void ConstructionProps::reportLayers(EnergyPlusData &state)
    1924             : {
    1925             :     // Report the layers for each opaque construction in predefined tabular report
    1926             :     // J. Glazer March 2024
    1927        4148 :     if (state.dataOutRptPredefined->pdchOpqConsLayCol.size() > 0) {
    1928       14387 :         for (int i = 1; i <= this->TotLayers; ++i) {
    1929       10239 :             int layerIndex = this->LayerPoint(i);
    1930       10239 :             auto &thisMaterial = state.dataMaterial->Material(layerIndex);
    1931       10239 :             OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchOpqConsLayCol[i - 1], this->Name, thisMaterial->Name);
    1932             :         }
    1933             :     }
    1934        4148 : }
    1935             : 
    1936          20 : 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          20 :     const Material::Group MaterialGroup = state.dataMaterial->Material(LayerPoint(1))->group;
    1946          15 :     return (MaterialGroup == Material::Group::WindowGlass) || (MaterialGroup == Material::Group::Shade) ||
    1947          35 :            (MaterialGroup == Material::Group::Screen) || (MaterialGroup == Material::Group::WindowBlind) ||
    1948          20 :            (MaterialGroup == Material::Group::WindowSimpleGlazing);
    1949             : }
    1950             : 
    1951          44 : Real64 ConstructionProps::setThicknessPerpendicular(EnergyPlusData &state, Real64 userValue)
    1952             : {
    1953             :     // Limits set here are similar to limits used in HydronicSystemBaseData::sizeRadiantSystemTubeLength
    1954             :     // which is for autosizing tube length.  The limits are not as strictly enforced here because they are
    1955             :     // not being used for autosizing so more latitude with the values is desired.  Warnings will still alert
    1956             :     // the user if values seem outside of the ordinary range anticipated.
    1957          44 :     Real64 returnValue = userValue / 2.0; // Divide by two because internally the half distance will be used to calculate CTFs
    1958          44 :     if (returnValue <= 0.001) {           // lowest reasonable value for "half" the tube spacing in meters
    1959           0 :         ShowWarningError(state, "ConstructionProperty:InternalHeatSource has a tube spacing that is less than 2 mm.  This is not allowed.");
    1960           0 :         ShowContinueError(
    1961             :             state,
    1962           0 :             format("Construction={} has this problem.  The tube spacing has been reset to 0.15m (~6 inches) for this construction.", this->Name));
    1963           0 :         ShowContinueError(state, "As per the Input Output Reference, tube spacing is only used for 2-D solutions and autosizing.");
    1964           0 :         returnValue = 0.075;          // default "half" tube spacing in meters (roughly equivalent to 15cm or 6 inches of tube spacing)
    1965          44 :     } else if (returnValue < 0.005) { // below this value for "half" the tube spacing in meters throw a warning
    1966           0 :         ShowWarningError(state, "ConstructionProperty:InternalHeatSource has a tube spacing that is less than 1 cm (0.4 inch).");
    1967           0 :         ShowContinueError(state, format("Construction={} has this concern.  Please check this construction to make sure it is correct.", this->Name));
    1968           0 :         ShowContinueError(state, "As per the Input Output Reference, tube spacing is only used for 2-D solutions and autosizing.");
    1969          44 :     } else if (returnValue > 0.5) { // above this value for "half" the tube spacing in meters throw a warning
    1970           0 :         ShowWarningError(state, "ConstructionProperty:InternalHeatSource has a tube spacing that is greater than 1 meter (39.4 inches).");
    1971           0 :         ShowContinueError(state, format("Construction={} has this concern.  Please check this construction to make sure it is correct.", this->Name));
    1972           0 :         ShowContinueError(state, "As per the Input Output Reference, tube spacing is only used for 2-D solutions and autosizing.");
    1973             :     }
    1974          44 :     return returnValue;
    1975             : }
    1976             : 
    1977          44 : Real64 ConstructionProps::setUserTemperatureLocationPerpendicular(EnergyPlusData &state, Real64 userValue)
    1978             : {
    1979          44 :     if (userValue < 0.0) {
    1980           0 :         ShowWarningError(state, "ConstructionProperty:InternalHeatSource has a perpendicular temperature location parameter that is less than zero.");
    1981           0 :         ShowContinueError(state, format("Construction={} has this error.  The parameter has been reset to 0.", this->Name));
    1982           0 :         return 0.0;
    1983          44 :     } else if (userValue > 1.0) {
    1984           0 :         ShowWarningError(state,
    1985             :                          "ConstructionProperty:InternalHeatSource has a perpendicular temperature location parameter that is greater than one.");
    1986           0 :         ShowContinueError(state, format("Construction={} has this error.  The parameter has been reset to 1.", this->Name));
    1987           0 :         return 1.0;
    1988             :     } else { // Valid value between 0 and 1
    1989          44 :         return userValue;
    1990             :     }
    1991             : }
    1992             : 
    1993        3637 : void ConstructionProps::setNodeSourceAndUserTemp(Array1D_int &Nodes)
    1994             : {
    1995        3637 :     this->NodeSource = 0;
    1996        3637 :     this->NodeUserTemp = 0;
    1997        3637 :     if (!this->SourceSinkPresent) return;
    1998             : 
    1999         164 :     for (int Layer = 1; Layer <= this->SourceAfterLayer; ++Layer) {
    2000         125 :         this->NodeSource += Nodes(Layer);
    2001             :     }
    2002             : 
    2003          39 :     if ((this->NodeSource > 0) && (this->SolutionDimensions > 1)) this->NodeSource = (this->NodeSource - 1) * this->NumOfPerpendNodes + 1;
    2004             : 
    2005         165 :     for (int Layer = 1; Layer <= this->TempAfterLayer; ++Layer) {
    2006         126 :         this->NodeUserTemp += Nodes(Layer);
    2007             :     }
    2008             : 
    2009          39 :     if ((this->NodeUserTemp > 0) && (this->SolutionDimensions > 1))
    2010           2 :         this->NodeUserTemp = (this->NodeUserTemp - 1) * this->NumOfPerpendNodes +
    2011           2 :                              round(this->userTemperatureLocationPerpendicular * (this->NumOfPerpendNodes - 1)) + 1;
    2012             : }
    2013             : 
    2014        6023 : void ConstructionProps::setArraysBasedOnMaxSolidWinLayers(EnergyPlusData &state)
    2015             : {
    2016        6023 :     this->AbsDiff.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2017        6023 :     this->AbsDiffBack.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2018        6023 :     this->BlAbsDiff.allocate(Material::MaxSlatAngs, state.dataHeatBal->MaxSolidWinLayers);
    2019        6023 :     this->BlAbsDiffGnd.allocate(Material::MaxSlatAngs, state.dataHeatBal->MaxSolidWinLayers);
    2020        6023 :     this->BlAbsDiffSky.allocate(Material::MaxSlatAngs, state.dataHeatBal->MaxSolidWinLayers);
    2021        6023 :     this->BlAbsDiffBack.allocate(Material::MaxSlatAngs, state.dataHeatBal->MaxSolidWinLayers);
    2022        6023 :     this->AbsBeamCoef.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2023        6023 :     this->AbsBeamBackCoef.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2024        6023 :     this->tBareSolCoef.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2025        6023 :     this->tBareVisCoef.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2026        6023 :     this->rfBareSolCoef.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2027        6023 :     this->rfBareVisCoef.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2028        6023 :     this->rbBareSolCoef.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2029        6023 :     this->rbBareVisCoef.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2030        6023 :     this->afBareSolCoef.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2031        6023 :     this->abBareSolCoef.allocate(state.dataHeatBal->MaxSolidWinLayers);
    2032       48413 :     for (int Layer = 1; Layer <= state.dataHeatBal->MaxSolidWinLayers; ++Layer) {
    2033       42390 :         this->AbsBeamCoef(Layer).allocate(DataSurfaces::MaxPolyCoeff);
    2034       42390 :         this->AbsBeamBackCoef(Layer).allocate(DataSurfaces::MaxPolyCoeff);
    2035       42390 :         this->tBareSolCoef(Layer).allocate(DataSurfaces::MaxPolyCoeff);
    2036       42390 :         this->tBareVisCoef(Layer).allocate(DataSurfaces::MaxPolyCoeff);
    2037       42390 :         this->rfBareSolCoef(Layer).allocate(DataSurfaces::MaxPolyCoeff);
    2038       42390 :         this->rfBareVisCoef(Layer).allocate(DataSurfaces::MaxPolyCoeff);
    2039       42390 :         this->rbBareSolCoef(Layer).allocate(DataSurfaces::MaxPolyCoeff);
    2040       42390 :         this->rbBareVisCoef(Layer).allocate(DataSurfaces::MaxPolyCoeff);
    2041       42390 :         this->afBareSolCoef(Layer).allocate(DataSurfaces::MaxPolyCoeff);
    2042       42390 :         this->abBareSolCoef(Layer).allocate(DataSurfaces::MaxPolyCoeff);
    2043             :     }
    2044             : 
    2045       48413 :     for (int Layer = 1; Layer <= state.dataHeatBal->MaxSolidWinLayers; ++Layer) {
    2046       42390 :         this->AbsDiff(Layer) = 0.0;
    2047       42390 :         this->AbsDiffBack(Layer) = 0.0;
    2048             :     }
    2049     1096186 :     for (int Index = 1; Index <= Material::MaxSlatAngs; ++Index) {
    2050     8762753 :         for (int Layer = 1; Layer <= state.dataHeatBal->MaxSolidWinLayers; ++Layer) {
    2051     7672590 :             this->BlAbsDiff(Index, Layer) = 0.0;
    2052     7672590 :             this->BlAbsDiffGnd(Index, Layer) = 0.0;
    2053     7672590 :             this->BlAbsDiffSky(Index, Layer) = 0.0;
    2054     7672590 :             this->BlAbsDiffBack(Index, Layer) = 0.0;
    2055             :         }
    2056             :     }
    2057       48413 :     for (int Layer = 1; Layer <= state.dataHeatBal->MaxSolidWinLayers; ++Layer) {
    2058      296730 :         for (int Index = 1; Index <= DataSurfaces::MaxPolyCoeff; ++Index) {
    2059      254340 :             this->AbsBeamCoef(Layer)(Index) = 0.0;
    2060      254340 :             this->AbsBeamBackCoef(Layer)(Index) = 0.0;
    2061      254340 :             this->tBareSolCoef(Layer)(Index) = 0.0;
    2062      254340 :             this->tBareVisCoef(Layer)(Index) = 0.0;
    2063      254340 :             this->rfBareSolCoef(Layer)(Index) = 0.0;
    2064      254340 :             this->rfBareVisCoef(Layer)(Index) = 0.0;
    2065      254340 :             this->rbBareSolCoef(Layer)(Index) = 0.0;
    2066      254340 :             this->rbBareVisCoef(Layer)(Index) = 0.0;
    2067      254340 :             this->afBareSolCoef(Layer)(Index) = 0.0;
    2068      254340 :             this->abBareSolCoef(Layer)(Index) = 0.0;
    2069             :         }
    2070             :     }
    2071        6023 : }
    2072             : 
    2073             : } // namespace EnergyPlus::Construction

Generated by: LCOV version 1.14