LCOV - code coverage report
Current view: top level - src/Integrator - HeatConduction.H (source / functions) Coverage Total Hit
Test: coverage_merged.info Lines: 92.8 % 69 64
Test Date: 2025-12-05 17:30:01 Functions: 92.3 % 13 12

            Line data    Source code
       1              : //
       2              : // This implements a basic heat conduction method in Alamo.
       3              : // The partial differential equation to be solved is
       4              : //
       5              : // .. math::
       6              : //
       7              : //    \frac{\partial T}{\partial t} = \alpha\,\Delta T
       8              : //
       9              : // where :math:`T` is temperature, :math:`t` is time, and :math:`alpha` 
      10              : // is the thermal diffusivity.
      11              : // Integration is performed explicitly in time using forward Euler, and
      12              : // differentiation is performed using the finite difference method.
      13              : //
      14              : 
      15              : #ifndef INTEGRATOR_HEATCONDUCTION_H // Include guards
      16              : #define INTEGRATOR_HEATCONDUCTION_H // 
      17              : 
      18              : // AMReX Includes
      19              : #include "AMReX_Array4.H"
      20              : #include "AMReX_GpuComplex.H"
      21              : #include "AMReX_MFIter.H"
      22              : #include <AMReX.H>
      23              : #include <AMReX_ParallelDescriptor.H>
      24              : #include <AMReX_ParmParse.H>
      25              : #include <AMReX_TimeIntegrator.H>
      26              : #ifdef ALAMO_FFT
      27              : #include <AMReX_FFT.H>
      28              : #endif
      29              : 
      30              : // Alamo Includes
      31              : #include "Set/Base.H"
      32              : #include "Integrator.H"
      33              : #include "IO/ParmParse.H"
      34              : #include "Integrator/Integrator.H"
      35              : #include "BC/Constant.H"
      36              : #include "BC/Expression.H"
      37              : #include "IC/IC.H"
      38              : #include "IC/Sphere.H"
      39              : #include "IC/Constant.H"
      40              : #include "IC/Expression.H"
      41              : #include "Numeric/Stencil.H"
      42              : 
      43              : namespace Integrator
      44              : {
      45              : class HeatConduction : virtual public Integrator
      46              : {
      47              : public:
      48              :     static constexpr const char* name = "heatconduction";
      49              : 
      50              : 
      51              :     // Empty constructor
      52            4 :     HeatConduction(int a_nghost = 2) : 
      53              :         Integrator(),
      54            4 :         number_of_ghost_cells(a_nghost)
      55            4 :     {}
      56              : 
      57              :     // Constructor that triggers parse
      58            4 :     HeatConduction(IO::ParmParse& pp) : HeatConduction()
      59              :     {
      60            4 :         Parse(*this, pp);
      61            4 :     }
      62              : 
      63            8 :     virtual ~HeatConduction()
      64            4 :     {
      65            4 :         delete ic;
      66            4 :         delete bc;
      67            8 :     }
      68              : 
      69              :     // The Parse function initializes the HeatConduction object using
      70              :     // a parser, pp. 
      71              :     // Note that this is a static function, which means it does not have
      72              :     // direct access to member variables. Instead, it initializes the variables
      73              :     // inside the argument, "value", and so all references to member items are
      74              :     // prefixed by "value."
      75            4 :     static void Parse(HeatConduction& value, IO::ParmParse& pp)
      76              :     {
      77              :         // Diffusion coefficient :math:`\alpha`.
      78              :         //   *This is an example of a required input variable -
      79              :         //    - program will terminate unless it is provided.*
      80            8 :         pp.query_required(  "heat.alpha", value.alpha,
      81              :                             Unit::ThermalDiffusivity());
      82              :         
      83              :         // Criterion for mesh refinement.
      84              :         //   *This is an example of a default input variable.
      85              :         //    The default value is provided here, not in the 
      86              :         //    definition of the variable.*
      87           16 :         pp.query_default(   "heat.refinement_threshold", value.refinement_threshold, "0.01_K", 
      88              :                             Unit::Temperature());
      89              : 
      90              :         // Initial condition type.
      91            8 :         pp.select_default<IC::Constant,IC::Sphere,IC::Expression>(  "ic",value.ic,value.geom, 
      92            4 :                                                                     Unit::Temperature());
      93              : 
      94              :         // Select BC object for temperature
      95            8 :         pp.select_default<BC::Constant,BC::Expression>( "bc.temp",value.bc,1,
      96            4 :                                                         Unit::Temperature());
      97              : 
      98              :         // Select between using a realspace solve or the spectral method
      99           16 :         pp.query_validate("method",value.method,{"realspace","spectral"});
     100              : 
     101              :         // Register the temperature and old temperature fields.
     102              :         // temp_mf and temp_old_mf are defined near the bottom of this Header file.
     103           16 :         value.RegisterNewFab(   value.temp_mf, value.bc, value.number_of_components, 
     104            4 :                                 value.number_of_ghost_cells, "Temp", true);
     105            4 :         if (value.method == "realspace")
     106              :         {
     107           16 :             value.RegisterNewFab(   value.temp_old_mf, value.bc, value.number_of_components, 
     108            4 :                                     value.number_of_ghost_cells, "Temp_old", false);
     109              :         }
     110            4 :     }
     111              : 
     112              : protected:
     113              : 
     114              :     // Use the ic object to initialize the temperature field
     115           16 :     void Initialize(int lev)
     116              :     {
     117           16 :         ic->Initialize(lev, temp_mf);
     118           16 :         if (method == "realspace") ic->Initialize(lev, temp_old_mf);
     119           16 :     }
     120              : 
     121              :     // Integrate the heat equation
     122        18000 :     void Advance(int lev, Set::Scalar time, Set::Scalar dt)
     123              :     {
     124              :         // If we are solving using the spectral method, go there instead.
     125        18000 :         if (method == "spectral")
     126              :         {
     127            0 :             AdvanceSpectral(lev,time,dt);
     128            0 :             return;
     129              :         }
     130              : 
     131              :         // Swap the old temp fab and the new temp fab so we use
     132              :         // the new one.
     133        18000 :         std::swap(*temp_mf[lev], *temp_old_mf[lev]);
     134              : 
     135              :         // Create the time integrator object
     136        18000 :         amrex::TimeIntegrator timeintegrator(*temp_old_mf[lev], time);
     137              :         
     138              :         // Calculate the "RHS" of the function.
     139              :         // If using forward Euler, the RHS would be
     140              :         //    T_new = T_old + dt * RHS
     141        18000 :         timeintegrator.set_rhs([&](amrex::MultiFab & rhs_mf, amrex::MultiFab & temp_mf, const Set::Scalar /*time*/)
     142              :         {
     143              :             // Get the cell size corresponding to this level
     144        25500 :             const Set::Scalar* DX = geom[lev].CellSize();
     145              : 
     146              :             // Iterate over all of the patches on this level
     147       412860 :             for (amrex::MFIter mfi(temp_mf, amrex::TilingIfNotGPU()); mfi.isValid(); ++mfi)
     148              :             {
     149              :                 // Get the box (index dimensions) for this patch
     150       387360 :                 const amrex::Box &bx = mfi.tilebox();
     151              : 
     152              :                 // Get an array-accessible handle to the data on this patch.
     153       387360 :                 Set::Patch<const Set::Scalar>  temp     = temp_mf.array(mfi);
     154       387360 :                 Set::Patch<Set::Scalar>        rhs      = rhs_mf.array(mfi);
     155              : 
     156              :                 // Iterate over the grid on this patch
     157       387360 :                 amrex::ParallelFor(bx, [=] AMREX_GPU_DEVICE(int i, int j, int k)
     158              :                 {
     159              :                     // Do the physics!
     160              :                     // Note that Numeric::Laplacian is an inlined function so there is no overhead.
     161              :                     // You can calculate the derivatives yourself if you want.
     162     93146880 :                     rhs(i, j, k) = alpha * Numeric::Laplacian(temp, i, j, k, 0, DX);
     163     46573440 :                 });
     164        25500 :             }
     165        25500 :         });
     166              :         
     167              :         // We must update the boundaries here and apply boundary conditions 
     168        18000 :         timeintegrator.set_post_stage_action([&](amrex::MultiFab & stage_mf, Set::Scalar time) 
     169              :         {
     170         7500 :             bc->FillBoundary(stage_mf,0,1,time,0);   
     171         7500 :             stage_mf.FillBoundary(true);
     172         7500 :         });
     173              : 
     174              :         // Do the update
     175        18000 :         timeintegrator.advance(*temp_old_mf[lev], *temp_mf[lev], time, dt);
     176        18000 :     }
     177              : 
     178              : 
     179              : #ifdef ALAMO_FFT
     180              :     void
     181              :     AdvanceSpectral(int lev, Set::Scalar /*time*/, Set::Scalar dt)
     182              :     {
     183              :         Util::Assert(INFO,TEST(lev == 0), "Only single level currently supported");
     184              :         
     185              :         amrex::Box const & domain = this->geom[lev].Domain();
     186              : 
     187              :         amrex::FFT::R2C my_fft(this->geom[lev].Domain());
     188              :         auto const& [cba, cdm] = my_fft.getSpectralDataLayout();
     189              :         amrex::FabArray<amrex::BaseFab<amrex::GpuComplex<Set::Scalar> > > Temp_hat(cba, cdm, 1, 0);
     190              :         my_fft.forward(*temp_mf[lev], Temp_hat);
     191              : 
     192              :         const Set::Scalar* DX = geom[lev].CellSize();
     193              : 
     194              :         Set::Scalar
     195              :             AMREX_D_DECL(
     196              :                 pi_Lx = 2.0 * Set::Constant::Pi / geom[lev].Domain().length(0) / DX[0],
     197              :                 pi_Ly = 2.0 * Set::Constant::Pi / geom[lev].Domain().length(1) / DX[1],
     198              :                 pi_Lz = 2.0 * Set::Constant::Pi / geom[lev].Domain().length(2) / DX[2]);
     199              : 
     200              :         Set::Scalar scaling = 1.0 / geom[lev].Domain().d_numPts();
     201              : 
     202              :         for (amrex::MFIter mfi(Temp_hat, amrex::TilingIfNotGPU()); mfi.isValid(); ++mfi)
     203              :         {
     204              :             const amrex::Box &bx = mfi.tilebox();
     205              :             amrex::Array4<amrex::GpuComplex<Set::Scalar>> const & T_hat =  Temp_hat.array(mfi);
     206              :             amrex::ParallelFor(bx, [=] AMREX_GPU_DEVICE(int m, int n, int p) {
     207              : 
     208              :                 AMREX_D_TERM(
     209              :                     Set::Scalar k1 = m * pi_Lx;,
     210              :                     Set::Scalar k2 = (n < domain.length(1)/2 ? n * pi_Ly : (n - domain.length(1)) * pi_Ly);,
     211              :                     Set::Scalar k3 = (p < domain.length(2)/2 ? p * pi_Lz : (p - domain.length(2)) * pi_Lz););
     212              : 
     213              :                 Set::Scalar lap = AMREX_D_TERM(k1 * k1, + k2 * k2, + k3*k3);
     214              : 
     215              :                 Set::Scalar factor = exp( - alpha * dt * lap); 
     216              :                 T_hat(m,n,p) *= factor*scaling;
     217              :             });
     218              :         }
     219              : 
     220              :         my_fft.backward(Temp_hat, *temp_mf[lev]);
     221              :     }
     222              : #else
     223              :     void
     224            0 :     AdvanceSpectral(int, Set::Scalar, Set::Scalar)
     225              :     {
     226            0 :         Util::Abort(INFO,"Alamo must be configured with --fft");
     227            0 :     }
     228              : #endif
     229              : 
     230              : 
     231              :     // Tag cells for mesh refinement based on temperature gradient
     232         1344 :     void TagCellsForRefinement(int lev, amrex::TagBoxArray& a_tags, Set::Scalar /*time*/, int /*ngrow*/)
     233              :     {
     234         1344 :         if (method=="spectral") return;
     235              : 
     236              :         // Get cell dimensions as done above.
     237         1344 :         const Set::Scalar* DX = geom[lev].CellSize();
     238              :         // Calculate the diagonal.
     239         1344 :         Set::Scalar dr = sqrt(AMREX_D_TERM(DX[0] * DX[0], +DX[1] * DX[1], +DX[2] * DX[2]));
     240              : 
     241              :         // Iterate over the patches on this level
     242        12478 :         for (amrex::MFIter mfi(*temp_mf[lev], amrex::TilingIfNotGPU()); mfi.isValid(); ++mfi)
     243              :         {
     244              :             // Get the box and handles as done above.
     245        11134 :             const amrex::Box& bx = mfi.tilebox();
     246        11134 :             amrex::Array4<char>         const& tags = a_tags.array(mfi);
     247        11134 :             amrex::Array4<Set::Scalar>  const& temp = (*temp_mf[lev]).array(mfi);
     248              : 
     249              :             // Iterate over the grid as done above.
     250        11134 :             amrex::ParallelFor(bx, [=] AMREX_GPU_DEVICE(int i, int j, int k)
     251              :             {
     252              :                 // Calculate the temperature gradient.
     253       784036 :                 Set::Vector grad = Numeric::Gradient(temp, i, j, k, 0, DX);
     254              : 
     255              :                 // Is the gradient * cell_size too big? If so, then
     256              :                 // mark this cell as needing refinement.
     257       784036 :                 if (grad.lpNorm<2>() * dr > refinement_threshold)
     258      1329706 :                     tags(i, j, k) = amrex::TagBox::SET;
     259       784036 :             });
     260         1344 :         }
     261              :     }
     262              : 
     263              : protected:
     264              :     Set::Field<Set::Scalar> temp_mf;         // Temperature field variable (current timestep)
     265              :     Set::Field<Set::Scalar> temp_old_mf;     // Temperature field variable (previous timestep)
     266              : 
     267              :     std::string method; // determine whether to use realspace or spectral method
     268              : 
     269              : private:
     270              : 
     271              :     //
     272              :     // Definition of parameters set only at instantiation by
     273              :     // constructors. 
     274              :     //
     275              :     const int number_of_components = 1;      // Number of components
     276              :     const int number_of_ghost_cells = 2;     // Number of ghost cells
     277              : 
     278              :     //
     279              :     // Definition of user-determined variables.
     280              :     //
     281              :     // Instantiate all variables to NAN if possible.
     282              :     // Default values may be set in Parse using query_default.
     283              :     //
     284              : 
     285              :     Set::Scalar alpha = NAN;                 // Thermal diffusivity
     286              :     Set::Scalar refinement_threshold = NAN ; // Criterion for cell refinement
     287              : 
     288              :     //
     289              :     // Definition of user-determined pointer variables.
     290              :     //
     291              :     // These should be set to nullptr. Make sure that they are deleted
     292              :     // in the ~HeatConduction destructor.
     293              :     //
     294              : 
     295              :     IC::IC<Set::Scalar>* ic = nullptr;                    // Object used to initialize temperature field
     296              :     BC::BC<Set::Scalar>* bc = nullptr;       // Object used to update temp field boundary ghost cells
     297              : };
     298              : } // namespace Integrator
     299              : #endif
        

Generated by: LCOV version 2.0-1