LCOV - code coverage report
Current view: top level - src/Integrator - PhaseFieldMicrostructure.H (source / functions) Coverage Total Hit
Test: coverage_merged.info Lines: 74.8 % 143 107
Test Date: 2025-02-27 04:17:48 Functions: 22.2 % 27 6

            Line data    Source code
       1              : ///
       2              : /// \file PhaseFieldMicrostructure.H
       3              : ///
       4              : #ifndef INTEGRATOR_PHASEFIELDMICROSTRUCTURE_H
       5              : #define INTEGRATOR_PHASEFIELDMICROSTRUCTURE_H
       6              : 
       7              : #include <iostream>
       8              : #include <fstream>
       9              : #include <iomanip>
      10              : #include <random>
      11              : #include <chrono>
      12              : 
      13              : #include "AMReX.H"
      14              : #include "AMReX_ParmParse.H"
      15              : #include "AMReX_ParallelDescriptor.H"
      16              : #include <AMReX_MLMG.H>
      17              : 
      18              : #include "Integrator/Integrator.H"
      19              : #include "Integrator/Mechanics.H"
      20              : 
      21              : #include "BC/BC.H"
      22              : #include "BC/Constant.H"
      23              : #include "BC/Operator/Elastic/Constant.H"
      24              : #include "IC/Constant.H"
      25              : #include "IC/TabulatedInterface.H"
      26              : #include "IC/PerturbedInterface.H"
      27              : #include "IC/Voronoi.H"
      28              : #include "IC/Sphere.H"
      29              : #include "IC/Expression.H"
      30              : #include "IC/Ellipse.H"
      31              : #include "IC/Random.H"
      32              : 
      33              : #include "Model/Interface/GB/GB.H"
      34              : #include "Model/Interface/GB/Sin.H"
      35              : #include "Model/Interface/GB/AbsSin.H"
      36              : #include "Model/Interface/GB/Read.H"
      37              : #include "Model/Interface/GB/SH.H"
      38              : 
      39              : #include "Model/Solid/Linear/Cubic.H"
      40              : #include "Model/Solid/Affine/Cubic.H"
      41              : 
      42              : #include "Model/Defect/Disconnection.H"
      43              : 
      44              : #include "Operator/Elastic.H"
      45              : 
      46              : namespace Integrator
      47              : {
      48              : //using model_type = Model::Solid::Affine::Cubic;
      49              : 
      50              : enum RegularizationType{
      51              :     Wilmore = 1,
      52              :     K12 = 2};
      53              : 
      54              : ///
      55              : /// \class PhaseFieldMicrostructure
      56              : /// \brief Microstructure evolution with grain boundary anisotropy
      57              : ///
      58              : /// Solve the Allen-Cahn evolution equation for microstructure with parameters \f$\eta_1\ldots\eta_n\f$,
      59              : /// where n corresponds to the number of grains.
      60              : ///
      61              : template<class model_type>
      62              : class PhaseFieldMicrostructure : public Base::Mechanics<model_type>
      63              : {
      64              : public:
      65            0 :     PhaseFieldMicrostructure() : Integrator() {};
      66            9 :     PhaseFieldMicrostructure(IO::ParmParse &pp) : Integrator() 
      67           36 :     {pp_queryclass(*this);}
      68            6 :     virtual ~PhaseFieldMicrostructure()
      69              :     {
      70            2 :         delete boundary;
      71            3 :         delete ic;
      72            3 :         delete mybc;
      73            9 :     }
      74            9 :     static void Parse(PhaseFieldMicrostructure &value, IO::ParmParse &pp)
      75              :     {
      76              :         BL_PROFILE("PhaseFieldMicrostructure::Parse");
      77              :         
      78              :         // Number of grain fields (may be more if using different IC)
      79           54 :         pp_query_default("pf.number_of_grains", value.number_of_grains,2);     
      80           54 :         pp_query_required("pf.M", value.pf.M);                         // Mobility
      81              :         //pp_query_required("pf.mu", value.pf.mu);                       // Phase field :math:`\mu`
      82           54 :         pp_query_required("pf.gamma", value.pf.gamma);                 // Phase field :math:`\gamma`
      83           54 :         pp_query_required("pf.sigma0", value.pf.sigma0);               // Initial GB energy if not using  anisotropy
      84           54 :         pp_query_required("pf.l_gb", value.pf.l_gb);                   // Mobility
      85           54 :         pp_query_default("pf.elastic_df",value.pf.elastic_df,false);   // Determine whether to use elastic driving force
      86           54 :         pp_query_default("pf.elastic_mult",value.pf.elastic_mult,1.0); // Multiplier of elastic energy
      87              : 
      88           63 :         pp_query_default("pf.threshold.value",value.pf.threshold.value,0.0);          // Value used for thresholding kinetic relation
      89           54 :         pp_query_default("pf.threshold.chempot",value.pf.threshold.chempot,false);    // Whether to include chemical potential in threshold
      90           54 :         pp_query_default("pf.threshold.boundary",value.pf.threshold.boundary,false);  // Whether to include boundary energy in threshold
      91           54 :         pp_query_default("pf.threshold.corner",value.pf.threshold.corner,false);      // Whether to include corner regularization in threshold
      92           54 :         pp_query_default("pf.threshold.lagrange",value.pf.threshold.lagrange,false);  // Whether to include lagrange multiplier in threshold
      93           45 :         pp_query_default("pf.threshold.mechanics",value.pf.threshold.mechanics,false);// Whether to include mechanical driving force in threshold
      94              :         {
      95            9 :             value.pf.threshold.on =
      96            9 :                 value.pf.threshold.chempot || value.pf.threshold.boundary ||
      97           26 :                 value.pf.threshold.corner || value.pf.threshold.lagrange ||
      98            8 :                 value.pf.threshold.mechanics;
      99              : 
     100           18 :             std::string type = "continuous";
     101           63 :             pp_query_validate("pf.threshold.type",type,{"continuous","chop"}); // Type of thresholding to use
     102            9 :             if (type == "continuous") value.pf.threshold.type = ThresholdType::Continuous;
     103            1 :             else if (type == "chop") value.pf.threshold.type = ThresholdType::Chop;
     104            9 :         }
     105              :         
     106            9 :         value.pf.L = (4./3.)*value.pf.M / value.pf.l_gb;
     107              :     
     108           54 :         pp_query_required("amr.max_level", value.max_level);            // Maximum AMR level
     109           54 :         pp_query_default("amr.ref_threshold", value.ref_threshold, 0.1);    // Phase field refinement threshold
     110              : 
     111              :         // Elasticity
     112           45 :         pp_query_default("mechanics.tstart",value.mechanics.tstart, 0.0);
     113            9 :         value.mechanics.model.clear();
     114            9 :         value.mechanics.model.resize(value.number_of_grains,model_type::Zero());
     115           59 :         for (int i=0; i < value.number_of_grains; i++)
     116              :         {
     117              :             // By default, read in the model specified by "mechanics.model"
     118          100 :             if (pp.getEntries("mechanics.model").size())
     119          120 :             pp_queryclass("mechanics.model", value.mechanics.model[i]);
     120              : 
     121              :             // If individual models are specified, read those in and overwrite
     122           50 :             std::string name = "mechanics.model" + std::to_string(i+1);
     123           50 :             if (pp.getEntries(name).size())
     124           12 :                 pp_queryclass(std::string(name.data()), value.mechanics.model[i]);
     125              :         }
     126              :         // Mixing order 
     127           72 :         pp_query_validate("mechanics.mix_order",value.mechanics.model_mix_order,{1,2});
     128              :         // Force Neumann BCs on the model
     129           45 :         pp_query_default("mechanics.model_neuman_boundary",value.mechanics.model_neumann_boundary,false);
     130              : 
     131            9 :         value.m_type = Base::Mechanics<model_type>::Disable; // Turn mechanics off by default
     132           45 :         pp.queryclass<Base::Mechanics<model_type>>("mechanics",value);
     133            9 :         if (value.m_type == Base::Mechanics<model_type>::Type::Static) 
     134            3 :             value.number_of_ghost_cells = std::max(value.number_of_ghost_cells,3);
     135              : 
     136              : 
     137              :         // Lagrange multiplier method for enforcing volumes
     138           45 :         pp_query_default("lagrange.on", value.lagrange.on,false);
     139            9 :         if (value.lagrange.on)
     140              :         {
     141           12 :             pp_query_required("lagrange.lambda", value.lagrange.lambda);      // Lagrange multiplier value
     142           12 :             pp_query_required("lagrange.vol0", value.lagrange.vol0);          // Prescribed volume
     143           10 :             pp_query_default("lagrange.tstart", value.lagrange.tstart,0.0);   // Lagrange multipler start time
     144            2 :             value.SetThermoInt(1);
     145              :         }
     146              : 
     147           45 :         pp_query_default("sdf.on",value.sdf.on,false); // synthetic driving force (SDF)
     148            9 :         if(value.sdf.on)
     149              :         {
     150            0 :             std::vector<std::string> vals;
     151            0 :             pp_queryarr("sdf.val",vals);  // value of SDF for each grain
     152            0 :             int nvals = static_cast<int>(vals.size());
     153            0 :             if (nvals == 1)
     154            0 :                 for (int i = 0; i < value.number_of_grains; i++)
     155            0 :                     value.sdf.val.push_back(Numeric::Interpolator::Linear<Set::Scalar>(vals[0]));
     156            0 :             else if (nvals == value.number_of_grains)
     157            0 :                 for (int i = 0; i < value.number_of_grains; i++)
     158            0 :                     value.sdf.val.push_back(Numeric::Interpolator::Linear<Set::Scalar>(vals[i]));
     159              :             else
     160            0 :                 Util::Abort(INFO,"sdf.val received ", vals.size(), " but requires 1 or ", value.number_of_grains);
     161              : 
     162            0 :             pp_query_default("sdf.tstart",value.sdf.tstart,0.0); // time to begin applying SDF
     163            0 :         }
     164              : 
     165              :         // Anisotropic grain boundary energy parameters
     166              : 
     167           45 :         pp_query_default("anisotropy.on", value.anisotropy.on,false);              // Turn on
     168            9 :         if (value.anisotropy.on)
     169              :         {
     170              :             // Regularization para m
     171           30 :             pp_query_required("anisotropy.beta", value.anisotropy.beta);
     172              :             // Time to turn on anisotropy
     173           25 :             pp_query_required("anisotropy.tstart", value.anisotropy.tstart);
     174            5 :             value.anisotropy.timestep = value.timestep;
     175              :             // Modify timestep when turned on
     176           25 :             pp_query_required("anisotropy.timestep", value.anisotropy.timestep);
     177            5 :             value.anisotropy.plot_int = value.plot_int;
     178              :             // Modify plot_int when turned on
     179           25 :             pp_query_default("anisotropy.plot_int", value.anisotropy.plot_int, -1);
     180            5 :             value.anisotropy.plot_dt = value.plot_dt;
     181              :             // Modify plot_dt when turned on
     182           30 :             pp_query_default("anisotropy.plot_dt", value.anisotropy.plot_dt, -1.0);
     183              :             // Modify thermo int when turned on
     184           30 :             pp_query_default("anisotropy.thermo_int", value.anisotropy.thermo_int, -1);
     185              :             // Modify thermo plot int when turned on
     186           30 :             pp_query_default("anisotropy.thermo_plot_int", value.anisotropy.thermo_plot_int, -1);
     187              :             // Frequency of elastic calculation
     188           25 :             pp_query_default("anisotropy.elastic_int",value.anisotropy.elastic_int, -1);           
     189            5 :             if (value.anisotropy.on) 
     190            5 :                 value.number_of_ghost_cells = std::max(value.number_of_ghost_cells,2);
     191              : 
     192              :             // Determine the kind of regularization to use
     193            5 :             std::map<std::string, RegularizationType> regularization_type;
     194           10 :             regularization_type["wilmore"] = RegularizationType::Wilmore;
     195            5 :             regularization_type["k12"] = RegularizationType::K12;
     196              : 
     197            5 :             std::string regularization_type_input;
     198              :             // Type of regularization to use  
     199           35 :             pp_query_validate("anisotropy.regularization", regularization_type_input,{"k12","wilmore"});    
     200            5 :             value.regularization = regularization_type[regularization_type_input];
     201              : 
     202           35 :             pp_forbid("anisotropy.gb_type"," --> anisotropy.type");
     203              :             // Type of GB to use
     204           10 :             pp.select<Model::Interface::GB::AbsSin, Model::Interface::GB::Sin, Model::Interface::GB::Read, Model::Interface::GB::SH>("anisotropy",value.boundary); 
     205            5 :         }
     206              :         // Thermal fluctuations
     207            9 :         pp.query("fluctuation.on",value.fluctuation.on);
     208            9 :         if (value.fluctuation.on)
     209              :         {
     210            0 :             pp.query("fluctuation.amp",value.fluctuation.amp); // fluctuation amplitude
     211            0 :             pp.query("fluctuation.sd",value.fluctuation.sd); // fluctuation stadard deviation
     212            0 :             pp.query("fluctuation.tstart", value.fluctuation.tstart); // time to start applying fluctuation
     213            0 :             value.fluctuation.norm_dist = std::normal_distribution<double>(0.0,value.fluctuation.sd);
     214              :         }
     215              : 
     216              :         // Disconnection generation
     217            9 :         pp.query("disconnection.on",value.disconnection.on);
     218            9 :         if (value.disconnection.on)
     219              :         {
     220              :             // Read in nucleation parameters from disconnection class
     221            0 :             pp.queryclass<Model::Defect::Disconnection>("disconnection",value.disconnection.model);
     222              :         }
     223              : 
     224              :         // Shear coupling matrices
     225           45 :         pp_query_default("shearcouple.on",value.shearcouple.on,false);
     226            9 :         if (value.shearcouple.on)
     227              :         {
     228            0 :             Util::AssertException(INFO,TEST(value.m_time_evolving == true), " mechanics.time_evolving must be true when using shearcouple");
     229            0 :             Util::AssertException(INFO,TEST(value.mechanics.model_mix_order == 2), " mechanics.model.mix_order must be 2 when using shearcouple");
     230              :             
     231            0 :             value.shearcouple.Fgb.resize(value.number_of_grains * value.number_of_grains, Set::Matrix::Zero());
     232            0 :             for (int i = 0 ; i < value.number_of_grains ; i++)
     233            0 :                 for (int j = 0 ; j < value.number_of_grains ; j++)
     234              :                 {
     235            0 :                     std::string name    = "shearcouple.Fgb."+std::to_string(i)+"."+std::to_string(j);
     236            0 :                     std::string namerev = "shearcouple.Fgb."+std::to_string(j)+"."+std::to_string(i);
     237            0 :                     if ( i==j && pp.contains(name.data())) 
     238            0 :                         Util::Abort(INFO,"Cannot specify self FGB ", name);
     239            0 :                     if (pp.contains(name.data()) && pp.contains(namerev.data())) 
     240            0 :                         Util::Abort(INFO,"Cannot specify both ",name," and ",namerev);
     241            0 :                     if (pp.contains(name.data())) 
     242              :                     {
     243            0 :                         pp.queryarr(name.data(),value.shearcouple.Fgb[i*value.number_of_grains + j]);
     244            0 :                         value.shearcouple.Fgb[j*value.number_of_grains + i] = - value.shearcouple.Fgb[i*value.number_of_grains + j];
     245              :                     }
     246              :                 }
     247              :         }
     248              : 
     249              :         // Boundary condition for eta
     250           18 :         pp.select<BC::Constant>("bc.eta",value.mybc,value.number_of_grains);
     251              : 
     252              :         // Initial condition for the order parameter eta
     253           27 :         pp.select<IC::Constant,IC::PerturbedInterface,IC::Voronoi,IC::Expression,IC::Sphere,IC::Ellipse,IC::Random>("ic",value.ic,value.geom);
     254              : 
     255              :         // Anisotropic mobility
     256           45 :         pp_query_default("anisotropic_kinetics.on",value.anisotropic_kinetics.on, 0);
     257            9 :         if (value.anisotropic_kinetics.on)
     258              :         {
     259              :             // simulation time when anisotropic kinetics is activated
     260            5 :             pp_query_default("anisotropic_kinetics.tstart",value.anisotropic_kinetics.tstart, 0.0);
     261            1 :             std::string mobility_filename, threshold_filename;
     262              :             // file containing anisotropic mobility data
     263            5 :             pp_query_file("anisotropic_kinetics.mobility",mobility_filename); 
     264            1 :             value.anisotropic_kinetics.mobility = Numeric::Interpolator::Linear<Set::Scalar>::Read(mobility_filename);
     265              :             // file containing anisotropic mobility data
     266            5 :             pp_query_file("anisotropic_kinetics.threshold",threshold_filename); 
     267            1 :             value.anisotropic_kinetics.threshold = Numeric::Interpolator::Linear<Set::Scalar>::Read(threshold_filename);
     268            3 :             value.RegisterNewFab(value.anisotropic_kinetics.L_mf, value.mybc, value.number_of_grains, 0, "mobility",true);
     269            3 :             value.RegisterNewFab(value.anisotropic_kinetics.threshold_mf, value.mybc, value.number_of_grains, 0, "theshold",true);
     270            1 :         }
     271              : 
     272              : 
     273              : 
     274              : 
     275           27 :         value.RegisterNewFab(value.eta_mf, value.mybc, value.number_of_grains, value.number_of_ghost_cells, "Eta",true);
     276           27 :         value.RegisterNewFab(value.eta_old_mf, value.mybc, value.number_of_grains, value.number_of_ghost_cells, "EtaOld",false);
     277              :         //value.RegisterNewFab(value.driving_force_mf, value.mybc, value.number_of_grains, value.number_of_ghost_cells, "DrivingForce",false);
     278              :         // if (value.pf.threshold.on)
     279              :         //     value.RegisterNewFab(value.driving_force_threshold_mf, value.mybc, value.number_of_grains, value.number_of_ghost_cells, "DrivingForceThreshold",false);
     280              :         // if (value.disconnection.on)
     281              :         //     value.RegisterNewFab(value.disc_mf, new BC::Nothing(), 1, value.number_of_ghost_cells, "disc",true);  // see box
     282              : 
     283           18 :         value.RegisterIntegratedVariable(&value.volume, "volume");
     284           18 :         value.RegisterIntegratedVariable(&value.area, "area");
     285           18 :         value.RegisterIntegratedVariable(&value.gbenergy, "gbenergy");
     286           18 :         value.RegisterIntegratedVariable(&value.realgbenergy, "realgbenergy");
     287           18 :         value.RegisterIntegratedVariable(&value.regenergy, "regenergy");
     288              : 
     289            9 :     }
     290              : 
     291              :     
     292              : protected:
     293              : 
     294              :     /// \fn    Advance
     295              :     /// \brief Evolve phase field in time
     296              :     void Advance (int lev, Real time, Real dt) override;
     297              :     void Initialize (int lev) override;
     298              : 
     299              :     void TagCellsForRefinement (int lev, amrex::TagBoxArray& tags, amrex::Real time, int ngrow) override;
     300              : 
     301              :     virtual void TimeStepBegin(amrex::Real time, int iter) override;
     302              :     virtual void TimeStepComplete(amrex::Real time, int iter) override;
     303              :     void Integrate(int amrlev, Set::Scalar time, int step,
     304              :                     const amrex::MFIter &mfi, const amrex::Box &box) override;
     305              : 
     306              :     virtual void UpdateEigenstrain(int lev);
     307            0 :     virtual void UpdateEigenstrain()
     308              :     {
     309            0 :         for (int lev = 0; lev <= this->max_level; lev++)
     310            0 :             UpdateEigenstrain(lev);
     311            0 :     }
     312              : 
     313              :     virtual void UpdateModel(int /*a_step*/, Set::Scalar /*a_time*/) override;
     314              : 
     315              : private:
     316              : 
     317              :     int number_of_grains = -1;
     318              :     int number_of_ghost_cells = 1;
     319              :     Set::Scalar ref_threshold = 0.1;
     320              : 
     321              :     // Cell fab
     322              :     Set::Field<Set::Scalar> eta_mf; // Multicomponent field variable storing \t$\eta_i\t$ for the __current__ timestep
     323              :     Set::Field<Set::Scalar> eta_old_mf; 
     324              :     Set::Field<Set::Scalar> driving_force_mf;
     325              :     Set::Field<Set::Scalar> driving_force_threshold_mf;
     326              :     Set::Field<Set::Scalar> fluct_mf;
     327              :     //Set::Field<Set::Scalar> disc_mf; //see box
     328              :     // Node fab
     329              :     //Set::Field<Set::Scalar> elasticdf_mf;
     330              :     Set::Field<Set::Scalar> totaldf_mf;
     331              : 
     332              :     BC::BC<Set::Scalar> *mybc = nullptr;
     333              : 
     334              :     //amrex::Real M, mu, gamma, sigma0, l_gb, beta;
     335              :     RegularizationType regularization = RegularizationType::K12;
     336              :     enum ThresholdType {
     337              :         Continuous = 0, Chop = 1
     338              :     };
     339              :     struct {
     340              :         Set::Scalar M = NAN;
     341              :         Set::Scalar L = NAN;
     342              :         Set::Scalar mu = NAN;
     343              :         Set::Scalar gamma = NAN;
     344              :         Set::Scalar sigma0 = NAN;
     345              :         Set::Scalar l_gb = NAN;
     346              :         bool elastic_df = false;
     347              :         Set::Scalar elastic_mult = NAN;
     348              :         struct {
     349              :             bool on = false;
     350              :             bool chempot = false;
     351              :             bool boundary = false;
     352              :             bool corner = false;
     353              :             bool lagrange = false;
     354              :             bool mechanics = false;
     355              :             bool sdf = false;
     356              :             Set::Scalar value = NAN;
     357              :             ThresholdType type = ThresholdType::Continuous;
     358              :         } threshold;
     359              :     } pf;
     360              : 
     361              :     struct {
     362              :         int on = 0;
     363              :         Set::Scalar tstart = 0.0;
     364              :         Numeric::Interpolator::Linear<Set::Scalar> mobility;
     365              :         Numeric::Interpolator::Linear<Set::Scalar> threshold;
     366              :         Set::Field<Set::Scalar> L_mf;
     367              :         Set::Field<Set::Scalar> threshold_mf;
     368              :     } anisotropic_kinetics;
     369              : 
     370              :     struct {
     371              :         int on = 0;
     372              :         Set::Scalar beta;
     373              :         Set::Scalar timestep;
     374              :         Set::Scalar tstart;
     375              :         int plot_int = -1;
     376              :         Set::Scalar plot_dt = -1.0;
     377              :         int thermo_int = -1, thermo_plot_int = -1;
     378              :         Set::Scalar theta0,sigma0,sigma1;
     379              :         Set::Scalar phi0 = 0.0;
     380              :         int elastic_int = -1;
     381              :     } anisotropy;
     382              :     
     383              :     struct { 
     384              :         bool on = 0;
     385              :         Set::Scalar tstart = NAN;
     386              :         Set::Scalar vol0 = NAN;
     387              :         Set::Scalar lambda = NAN;
     388              :     } lagrange;
     389              : 
     390              :     struct {
     391              :         int on = 0;
     392              :         std::vector<Numeric::Interpolator::Linear<Set::Scalar>> val;
     393              :         Set::Scalar tstart = 0.0;
     394              :     } sdf;
     395              : 
     396              :     struct {
     397              :         int on = 0;
     398              :         Set::Scalar amp = 0.0;
     399              :         Set::Scalar sd = 0.0;
     400              :         Set::Scalar tstart = 0.0;
     401              :         std::normal_distribution<double> norm_dist;
     402              :         std::default_random_engine rand_num_gen;
     403              :     } fluctuation;
     404              : 
     405              :     struct {
     406              :         int on = 0;
     407              :         Model::Defect::Disconnection model;
     408              :     } disconnection;
     409              : 
     410              :     struct {
     411              :         int on = 0;
     412              :         std::vector<Set::Matrix> Fgb;
     413              :     } shearcouple;
     414              : 
     415              :     std::string gb_type, filename;
     416              : 
     417              :     Model::Interface::GB::GB *boundary = nullptr;
     418              : 
     419              :     IC::IC<Set::Scalar> *ic = nullptr;
     420              : 
     421              :     Set::Scalar volume = 5;
     422              :     Set::Scalar area = 0.0;
     423              :     Set::Scalar gbenergy = 0.0;
     424              :     Set::Scalar realgbenergy = 0.0;
     425              :     Set::Scalar regenergy = 0.0;
     426              : 
     427              :     struct
     428              :     {
     429              :         Set::Scalar tstart = 0.0;
     430              :         std::vector<model_type> model;
     431              :         int model_mix_order;
     432              :         bool model_neumann_boundary = false;
     433              :     } mechanics;
     434              :     
     435              :     using Base::Mechanics<model_type>::model_mf;
     436              :     using Base::Mechanics<model_type>::stress_mf;
     437              : 
     438              : };
     439              : }
     440              : #endif
        

Generated by: LCOV version 2.0-1