LCOV - code coverage report
Current view: top level - src/FFT - FFT.hpp (source / functions) Coverage Total Hit
Test: final_report.info Lines: 49.4 % 180 89
Test Date: 2025-07-17 08:40:11 Functions: 49.1 % 114 56

            Line data    Source code
       1              : //
       2              : // Class FFT
       3              : //   The FFT class performs complex-to-complex,
       4              : //   real-to-complex on IPPL Fields.
       5              : //   FFT is templated on the type of transform to be performed,
       6              : //   the dimensionality of the Field to transform, and the
       7              : //   floating-point precision type of the Field (float or double).
       8              : //   Currently, we use heffte for taking the transforms and the class FFT
       9              : //   serves as an interface between IPPL and heffte. In making this interface,
      10              : //   we have referred Cabana library.
      11              : //   https://github.com/ECP-copa/Cabana.
      12              : //
      13              : // Copyright (c) 2021, Sriramkrishnan Muralikrishnan,
      14              : // Paul Scherrer Institut, Villigen PSI, Switzerland
      15              : // All rights reserved
      16              : //
      17              : // This file is part of IPPL.
      18              : //
      19              : /**
      20              :    Implementations for FFT constructor/destructor and transforms
      21              : */
      22              : 
      23              : #include "Utility/IpplTimings.h"
      24              : 
      25              : #include "Field/BareField.h"
      26              : 
      27              : #include "FieldLayout/FieldLayout.h"
      28              : 
      29              : namespace ippl {
      30              : 
      31              :     template <typename Field, template <typename...> class FFT, typename Backend, typename T>
      32            4 :     FFTBase<Field, FFT, Backend, T>::FFTBase(const Layout_t& layout, const ParameterList& params) {
      33              :         std::array<long long, 3> low;
      34              :         std::array<long long, 3> high;
      35              : 
      36            4 :         const NDIndex<Dim> lDom = layout.getLocalNDIndex();
      37            4 :         domainToBounds(lDom, low, high);
      38              : 
      39            4 :         heffte::box3d<long long> inbox  = {low, high};
      40            4 :         heffte::box3d<long long> outbox = {low, high};
      41              : 
      42            4 :         setup(inbox, outbox, params);
      43            4 :     }
      44              : 
      45              :     template <typename Field, template <typename...> class FFT, typename Backend, typename T>
      46           12 :     void FFTBase<Field, FFT, Backend, T>::domainToBounds(const NDIndex<Dim>& domain,
      47              :                                                          std::array<long long, 3>& low,
      48              :                                                          std::array<long long, 3>& high) {
      49           12 :         low.fill(0);
      50           12 :         high.fill(0);
      51              : 
      52              :         /**
      53              :          * Static cast to detail::long long (uint64_t) is necessary, as heffte::box3d requires it
      54              :          * like that.
      55              :          */
      56           42 :         for (size_t d = 0; d < Dim; ++d) {
      57           30 :             low[d]  = static_cast<long long>(domain[d].first());
      58           30 :             high[d] = static_cast<long long>(domain[d].length() + domain[d].first() - 1);
      59              :         }
      60           12 :     }
      61              : 
      62              :     /**
      63              :            setup performs the initialization necessary.
      64              :     */
      65              :     template <typename Field, template <typename...> class FFT, typename Backend, typename T>
      66            8 :     void FFTBase<Field, FFT, Backend, T>::setup(const heffte::box3d<long long>& inbox,
      67              :                                                 const heffte::box3d<long long>& outbox,
      68              :                                                 const ParameterList& params) {
      69            8 :         heffte::plan_options heffteOptions = heffte::default_options<heffteBackend>();
      70              : 
      71           16 :         if (!params.get<bool>("use_heffte_defaults")) {
      72            0 :             heffteOptions.use_pencils = params.get<bool>("use_pencils");
      73            0 :             heffteOptions.use_reorder = params.get<bool>("use_reorder");
      74              : #ifdef Heffte_ENABLE_GPU
      75              :             heffteOptions.use_gpu_aware = params.get<bool>("use_gpu_aware");
      76              : #endif
      77              : 
      78            0 :             switch (params.get<int>("comm")) {
      79            0 :                 case a2a:
      80            0 :                     heffteOptions.algorithm = heffte::reshape_algorithm::alltoall;
      81            0 :                     break;
      82            0 :                 case a2av:
      83            0 :                     heffteOptions.algorithm = heffte::reshape_algorithm::alltoallv;
      84            0 :                     break;
      85            0 :                 case p2p:
      86            0 :                     heffteOptions.algorithm = heffte::reshape_algorithm::p2p;
      87            0 :                     break;
      88            0 :                 case p2p_pl:
      89            0 :                     heffteOptions.algorithm = heffte::reshape_algorithm::p2p_plined;
      90            0 :                     break;
      91            0 :                 default:
      92            0 :                     throw IpplException("FFT::setup", "Unrecognized heffte communication type");
      93              :             }
      94              :         }
      95              : 
      96              :         if constexpr (std::is_same_v<FFT<heffteBackend>, heffte::fft3d<heffteBackend>>) {
      97            4 :             heffte_m = std::make_shared<FFT<heffteBackend, long long>>(
      98            4 :                 inbox, outbox, Comm->getCommunicator(), heffteOptions);
      99              :         } else {
     100            4 :             heffte_m = std::make_shared<FFT<heffteBackend, long long>>(
     101           12 :                 inbox, outbox, params.get<int>("r2c_direction"), Comm->getCommunicator(),
     102              :                 heffteOptions);
     103              :         }
     104              : 
     105              :         // heffte::gpu::device_set(Comm->rank() % heffte::gpu::device_count());
     106            8 :         if (workspace_m.size() < heffte_m->size_workspace()) {
     107           16 :             workspace_m = workspace_t(heffte_m->size_workspace());
     108              :         }
     109            8 :     }
     110              : 
     111              :     template <typename ComplexField>
     112            0 :     void FFT<CCTransform, ComplexField>::warmup(ComplexField& f) {
     113            0 :         this->transform(FORWARD, f);
     114            0 :         this->transform(BACKWARD, f);
     115            0 :     }
     116              : 
     117              :     template <typename ComplexField>
     118            8 :     void FFT<CCTransform, ComplexField>::transform(TransformDirection direction, ComplexField& f) {
     119              :         static_assert(Dim == 2 || Dim == 3, "heFFTe only supports 2D and 3D");
     120              : 
     121            8 :         auto fview       = f.getView();
     122            8 :         const int nghost = f.getNghost();
     123              : 
     124              :         /**
     125              :          *This copy to a temporary Kokkos view is needed because of following
     126              :          *reasons:
     127              :          *1) heffte wants the input and output fields without ghost layers
     128              :          *2) heffte accepts data in layout left (by default) even though this
     129              :          *can be changed during heffte box creation
     130              :          */
     131            8 :         auto& tempField = this->tempField;
     132            8 :         if (tempField.size() != f.getOwned().size()) {
     133            8 :             tempField = detail::shrinkView("tempField", fview, nghost);
     134              :         }
     135              : 
     136              :         using index_array_type = typename RangePolicy<Dim>::index_array_type;
     137            8 :         ippl::parallel_for(
     138            8 :             "copy from Kokkos FFT", getRangePolicy(fview, nghost),
     139        36880 :             KOKKOS_LAMBDA(const index_array_type& args) {
     140        18432 :                 apply(tempField, args - nghost).real(apply(fview, args).real());
     141        18432 :                 apply(tempField, args - nghost).imag(apply(fview, args).imag());
     142              :             });
     143              : 
     144            8 :         if (direction == FORWARD) {
     145            4 :             this->heffte_m->forward(tempField.data(), tempField.data(), this->workspace_m.data(),
     146              :                                     heffte::scale::full);
     147            4 :         } else if (direction == BACKWARD) {
     148            4 :             this->heffte_m->backward(tempField.data(), tempField.data(), this->workspace_m.data(),
     149              :                                      heffte::scale::none);
     150              :         } else {
     151            0 :             throw std::logic_error("Only 1:forward and -1:backward are allowed as directions");
     152              :         }
     153              : 
     154            8 :         ippl::parallel_for(
     155            8 :             "copy to Kokkos FFT", getRangePolicy(fview, nghost),
     156        36880 :             KOKKOS_LAMBDA(const index_array_type& args) {
     157        18432 :                 apply(fview, args).real() = apply(tempField, args - nghost).real();
     158        18432 :                 apply(fview, args).imag() = apply(tempField, args - nghost).imag();
     159              :             });
     160            8 :     }
     161              : 
     162              :     //========================================================================
     163              :     // FFT RCTransform Constructors
     164              :     //========================================================================
     165              : 
     166              :     /**
     167              :      *Create a new FFT object of type RCTransform, with given input and output
     168              :      *layouts and heffte parameters.
     169              :      */
     170              : 
     171              :     template <typename RealField>
     172            4 :     FFT<RCTransform, RealField>::FFT(const Layout_t& layoutInput, const Layout_t& layoutOutput,
     173            4 :                                      const ParameterList& params) {
     174              :         /**
     175              :          * Heffte requires to pass a 3D array even for 2D and
     176              :          * 1D FFTs we just have to make the length in other
     177              :          * dimensions to be 1.
     178              :          */
     179              :         std::array<long long, 3> lowInput;
     180              :         std::array<long long, 3> highInput;
     181              :         std::array<long long, 3> lowOutput;
     182              :         std::array<long long, 3> highOutput;
     183              : 
     184            4 :         const NDIndex<Dim>& lDomInput  = layoutInput.getLocalNDIndex();
     185            4 :         const NDIndex<Dim>& lDomOutput = layoutOutput.getLocalNDIndex();
     186              : 
     187            4 :         this->domainToBounds(lDomInput, lowInput, highInput);
     188            4 :         this->domainToBounds(lDomOutput, lowOutput, highOutput);
     189              : 
     190            4 :         heffte::box3d<long long> inbox  = {lowInput, highInput};
     191            4 :         heffte::box3d<long long> outbox = {lowOutput, highOutput};
     192              : 
     193            4 :         this->setup(inbox, outbox, params);
     194            4 :     }
     195              : 
     196              :     template <typename RealField>
     197            0 :     void FFT<RCTransform, RealField>::warmup(RealField& f, ComplexField& g) {
     198            0 :         this->transform(FORWARD, f, g);
     199            0 :         this->transform(BACKWARD, f, g);
     200            0 :     }
     201              : 
     202              :     template <typename RealField>
     203            8 :     void FFT<RCTransform, RealField>::transform(TransformDirection direction, RealField& f,
     204              :                                                 ComplexField& g) {
     205              :         static_assert(Dim == 2 || Dim == 3, "heFFTe only supports 2D and 3D");
     206              : 
     207            8 :         auto fview        = f.getView();
     208            8 :         auto gview        = g.getView();
     209            8 :         const int nghostf = f.getNghost();
     210            8 :         const int nghostg = g.getNghost();
     211              : 
     212              :         /**
     213              :          *This copy to a temporary Kokkos view is needed because of following
     214              :          *reasons:
     215              :          *1) heffte wants the input and output fields without ghost layers
     216              :          *2) heffte accepts data in layout left (by default) eventhough this
     217              :          *can be changed during heffte box creation
     218              :          */
     219            8 :         auto& tempFieldf = this->tempField;
     220            8 :         auto& tempFieldg = this->tempFieldComplex;
     221            8 :         if (tempFieldf.size() != f.getOwned().size()) {
     222            8 :             tempFieldf = detail::shrinkView("tempFieldf", fview, nghostf);
     223              :         }
     224            8 :         if (tempFieldg.size() != g.getOwned().size()) {
     225            8 :             tempFieldg = detail::shrinkView("tempFieldg", gview, nghostg);
     226              :         }
     227              : 
     228              :         using index_array_type = typename RangePolicy<Dim>::index_array_type;
     229            8 :         ippl::parallel_for(
     230            8 :             "copy from Kokkos f field in FFT", getRangePolicy(fview, nghostf),
     231        36880 :             KOKKOS_LAMBDA(const index_array_type& args) {
     232        18432 :                 apply(tempFieldf, args - nghostf) = apply(fview, args);
     233              :             });
     234            8 :         ippl::parallel_for(
     235            8 :             "copy from Kokkos g field in FFT", getRangePolicy(gview, nghostg),
     236        19600 :             KOKKOS_LAMBDA(const index_array_type& args) {
     237         9792 :                 apply(tempFieldg, args - nghostg).real(apply(gview, args).real());
     238         9792 :                 apply(tempFieldg, args - nghostg).imag(apply(gview, args).imag());
     239              :             });
     240              : 
     241            8 :         if (direction == FORWARD) {
     242            4 :             this->heffte_m->forward(tempFieldf.data(), tempFieldg.data(), this->workspace_m.data(),
     243              :                                     heffte::scale::full);
     244            4 :         } else if (direction == BACKWARD) {
     245            4 :             this->heffte_m->backward(tempFieldg.data(), tempFieldf.data(), this->workspace_m.data(),
     246              :                                      heffte::scale::none);
     247              :         } else {
     248            0 :             throw std::logic_error("Only 1:forward and -1:backward are allowed as directions");
     249              :         }
     250              : 
     251            8 :         ippl::parallel_for(
     252            8 :             "copy to Kokkos f field FFT", getRangePolicy(fview, nghostf),
     253        36880 :             KOKKOS_LAMBDA(const index_array_type& args) {
     254        18432 :                 apply(fview, args) = apply(tempFieldf, args - nghostf);
     255              :             });
     256              : 
     257            8 :         ippl::parallel_for(
     258            8 :             "copy to Kokkos g field FFT", getRangePolicy(gview, nghostg),
     259        19600 :             KOKKOS_LAMBDA(const index_array_type& args) {
     260         9792 :                 apply(gview, args).real() = apply(tempFieldg, args - nghostg).real();
     261         9792 :                 apply(gview, args).imag() = apply(tempFieldg, args - nghostg).imag();
     262              :             });
     263            8 :     }
     264              : 
     265              :     template <typename Field>
     266              :     void FFT<SineTransform, Field>::warmup(Field& f) {
     267              :         this->transform(FORWARD, f);
     268              :         this->transform(BACKWARD, f);
     269              :     }
     270              : 
     271              :     template <typename Field>
     272            0 :     void FFT<SineTransform, Field>::transform(TransformDirection direction, Field& f) {
     273              :         static_assert(Dim == 2 || Dim == 3, "heFFTe only supports 2D and 3D");
     274              : #ifdef Heffte_ENABLE_FFTW
     275              :         if (direction == FORWARD) {
     276              :             f = f / 8.0;
     277              :         }
     278              : #endif
     279              : 
     280            0 :         auto fview       = f.getView();
     281            0 :         const int nghost = f.getNghost();
     282              : 
     283              :         /**
     284              :          *This copy to a temporary Kokkos view is needed because of following
     285              :          *reasons:
     286              :          *1) heffte wants the input and output fields without ghost layers
     287              :          *2) heffte accepts data in layout left (by default) eventhough this
     288              :          *can be changed during heffte box creation
     289              :          */
     290            0 :         auto& tempField = this->tempField;
     291            0 :         if (tempField.size() != f.getOwned().size()) {
     292            0 :             tempField = detail::shrinkView("tempField", fview, nghost);
     293              :         }
     294              : 
     295              :         using index_array_type = typename RangePolicy<Dim>::index_array_type;
     296            0 :         ippl::parallel_for(
     297            0 :             "copy from Kokkos FFT", getRangePolicy(fview, nghost),
     298            0 :             KOKKOS_LAMBDA(const index_array_type& args) {
     299            0 :                 apply(tempField, args - nghost) = apply(fview, args);
     300              :             });
     301              : 
     302            0 :         if (direction == FORWARD) {
     303            0 :             this->heffte_m->forward(tempField.data(), tempField.data(), this->workspace_m.data(),
     304              :                                     heffte::scale::full);
     305            0 :         } else if (direction == BACKWARD) {
     306            0 :             this->heffte_m->backward(tempField.data(), tempField.data(), this->workspace_m.data(),
     307              :                                      heffte::scale::none);
     308              :         } else {
     309            0 :             throw std::logic_error("Only 1:forward and -1:backward are allowed as directions");
     310              :         }
     311              : 
     312            0 :         ippl::parallel_for(
     313            0 :             "copy to Kokkos FFT", getRangePolicy(fview, nghost),
     314            0 :             KOKKOS_LAMBDA(const index_array_type& args) {
     315            0 :                 apply(fview, args) = apply(tempField, args - nghost);
     316              :             });
     317              : #ifdef Heffte_ENABLE_FFTW
     318              :         if (direction == BACKWARD) {
     319              :             f = f * 8.0;
     320              :         }
     321              : #endif
     322            0 :     }
     323              : 
     324              :     template <typename Field>
     325              :     void FFT<CosTransform, Field>::warmup(Field& f) {
     326              :         this->transform(FORWARD, f);
     327              :         this->transform(BACKWARD, f);
     328              :     }
     329              : 
     330              :     template <typename Field>
     331            0 :     void FFT<CosTransform, Field>::transform(TransformDirection direction, Field& f) {
     332              :         static_assert(Dim == 2 || Dim == 3, "heFFTe only supports 2D and 3D");
     333              : #ifdef Heffte_ENABLE_FFTW
     334              :         if (direction == FORWARD) {
     335              :             f = f / 8.0;
     336              :         }
     337              : #endif
     338              : 
     339            0 :         auto fview       = f.getView();
     340            0 :         const int nghost = f.getNghost();
     341              : 
     342              :         /**
     343              :          *This copy to a temporary Kokkos view is needed because of following
     344              :          *reasons:
     345              :          *1) heffte wants the input and output fields without ghost layers
     346              :          *2) heffte accepts data in layout left (by default) eventhough this
     347              :          *can be changed during heffte box creation
     348              :          */
     349            0 :         auto& tempField = this->tempField;
     350            0 :         if (tempField.size() != f.getOwned().size()) {
     351            0 :             tempField = detail::shrinkView("tempField", fview, nghost);
     352              :         }
     353              : 
     354              :         using index_array_type = typename RangePolicy<Dim>::index_array_type;
     355            0 :         ippl::parallel_for(
     356            0 :             "copy from Kokkos FFT", getRangePolicy(fview, nghost),
     357            0 :             KOKKOS_LAMBDA(const index_array_type& args) {
     358            0 :                 apply(tempField, args - nghost) = apply(fview, args);
     359              :             });
     360              : 
     361            0 :         if (direction == FORWARD) {
     362            0 :             this->heffte_m->forward(tempField.data(), tempField.data(), this->workspace_m.data(),
     363              :                                     heffte::scale::full);
     364            0 :         } else if (direction == BACKWARD) {
     365            0 :             this->heffte_m->backward(tempField.data(), tempField.data(), this->workspace_m.data(),
     366              :                                      heffte::scale::none);
     367              :         } else {
     368            0 :             throw std::logic_error("Only 1:forward and -1:backward are allowed as directions");
     369              :         }
     370              : 
     371            0 :         ippl::parallel_for(
     372            0 :             "copy to Kokkos FFT", getRangePolicy(fview, nghost),
     373            0 :             KOKKOS_LAMBDA(const index_array_type& args) {
     374            0 :                 apply(fview, args) = apply(tempField, args - nghost);
     375              :             });
     376              : #ifdef Heffte_ENABLE_FFTW
     377              :         if (direction == BACKWARD) {
     378              :             f = f * 8.0;
     379              :         }
     380              : #endif
     381            0 :     }
     382              : 
     383              :     template <typename Field>
     384            0 :     void FFT<Cos1Transform, Field>::warmup(Field& f) {
     385            0 :         this->transform(FORWARD, f);
     386            0 :         this->transform(BACKWARD, f);
     387            0 :     }
     388              : 
     389              :     template <typename Field>
     390            0 :     void FFT<Cos1Transform, Field>::transform(TransformDirection direction, Field& f) {
     391              :         static_assert(Dim == 2 || Dim == 3, "heFFTe only supports 2D and 3D");
     392              : 
     393              : /**
     394              :  * This rescaling is needed to match the normalization constant
     395              :  * between fftw and the other gpu interfaces. fftw rescales with an extra factor of 8.
     396              :  */
     397              : #ifdef Heffte_ENABLE_FFTW
     398              :         if (direction == FORWARD) {
     399              :             f = f / 8.0;
     400              :         }
     401              : #endif
     402              : 
     403            0 :         auto fview       = f.getView();
     404            0 :         const int nghost = f.getNghost();
     405              : 
     406              :         /**
     407              :          *This copy to a temporary Kokkos view is needed because of following
     408              :          *reasons:
     409              :          *1) heffte wants the input and output fields without ghost layers
     410              :          *2) heffte accepts data in layout left (by default) eventhough this
     411              :          *can be changed during heffte box creation
     412              :          */
     413            0 :         auto& tempField = this->tempField;
     414            0 :         if (tempField.size() != f.getOwned().size()) {
     415            0 :             tempField = detail::shrinkView("tempField", fview, nghost);
     416              :         }
     417              : 
     418              :         using index_array_type = typename RangePolicy<Dim>::index_array_type;
     419            0 :         ippl::parallel_for(
     420            0 :             "copy from Kokkos FFT", getRangePolicy(fview, nghost),
     421            0 :             KOKKOS_LAMBDA(const index_array_type& args) {
     422            0 :                 apply(tempField, args - nghost) = apply(fview, args);
     423              :             });
     424              : 
     425            0 :         if (direction == FORWARD) {
     426            0 :             this->heffte_m->forward(tempField.data(), tempField.data(), this->workspace_m.data(),
     427              :                                     heffte::scale::full);
     428            0 :         } else if (direction == BACKWARD) {
     429            0 :             this->heffte_m->backward(tempField.data(), tempField.data(), this->workspace_m.data(),
     430              :                                      heffte::scale::none);
     431              :         } else {
     432            0 :             throw std::logic_error("Only 1:forward and -1:backward are allowed as directions");
     433              :         }
     434              : 
     435            0 :         ippl::parallel_for(
     436            0 :             "copy to Kokkos FFT", getRangePolicy(fview, nghost),
     437            0 :             KOKKOS_LAMBDA(const index_array_type& args) {
     438            0 :                 apply(fview, args) = apply(tempField, args - nghost);
     439              :             });
     440              : 
     441              : /**
     442              :  * This rescaling is needed to match the normalization constant
     443              :  * between fftw and the other gpu interfaces. fftw rescales with an extra factor of 8.
     444              :  */
     445              : #ifdef Heffte_ENABLE_FFTW
     446              :         if (direction == BACKWARD) {
     447              :             f = f * 8.0;
     448              :         }
     449              : #endif
     450            0 :     }
     451              : 
     452              : }  // namespace ippl
     453              : 
     454              : // vi: set et ts=4 sw=4 sts=4:
     455              : // Local Variables:
     456              : // mode:c
     457              : // c-basic-offset: 4
     458              : // indent-tabs-mode: nil
     459              : // require-final-newline: nil
     460              : // End:
        

Generated by: LCOV version 2.0-1