LCOV - code coverage report
Current view: top level - src/FieldLayout - FieldLayout.h (source / functions) Coverage Total Hit
Test: final_report.info Lines: 36.8 % 19 7
Test Date: 2025-07-17 08:40:11 Functions: 39.4 % 33 13

            Line data    Source code
       1              : //
       2              : // Class FieldLayout
       3              : //   FieldLayout describes how a given index space (represented by an NDIndex
       4              : //   object) is distributed among MPI ranks. It performs the initial
       5              : //   partitioning. The user may request that a particular dimension not be
       6              : //   partitioned by flagging that axis as 'SERIAL' (instead of 'PARALLEL').
       7              : //
       8              : #ifndef IPPL_FIELD_LAYOUT_H
       9              : #define IPPL_FIELD_LAYOUT_H
      10              : 
      11              : #include <array>
      12              : #include <iostream>
      13              : #include <map>
      14              : #include <vector>
      15              : 
      16              : #include "Types/ViewTypes.h"
      17              : 
      18              : #include "Communicate/Communicator.h"
      19              : #include "Index/NDIndex.h"
      20              : #include "Partition/Partitioner.h"
      21              : 
      22              : namespace ippl {
      23              : 
      24              :     template <unsigned Dim>
      25              :     class FieldLayout;
      26              : 
      27              :     template <unsigned Dim>
      28              :     std::ostream& operator<<(std::ostream&, const FieldLayout<Dim>&);
      29              : 
      30              :     // enumeration used to describe a hypercube's relation to
      31              :     // a particular axis in a given bounded domain
      32              :     enum e_cube_tag {
      33              :         UPPER       = 0,
      34              :         LOWER       = 1,
      35              :         IS_PARALLEL = 2
      36              :     };
      37              : 
      38              :     namespace detail {
      39              :         /*!
      40              :          * Counts the hypercubes in a given dimension
      41              :          * @param dim the dimension
      42              :          * @return 3^n
      43              :          */
      44         4368 :         constexpr unsigned int countHypercubes(unsigned int dim) {
      45         4368 :             unsigned int ret = 1;
      46        28428 :             for (unsigned int d = 0; d < dim; d++) {
      47        24060 :                 ret *= 3;
      48              :             }
      49         4368 :             return ret;
      50              :         }
      51              : 
      52              :         /*!
      53              :          * Compile-time evaluation of x!
      54              :          * @param x input value
      55              :          * @return x factorial
      56              :          */
      57              :         constexpr unsigned int factorial(unsigned x) {
      58              :             return x == 0 ? 1 : x * factorial(x - 1);
      59              :         }
      60              : 
      61              :         /*!
      62              :          * Compile-time evaluation of binomial coefficients, aka
      63              :          * elements of Pascal's triangle, aka choices from combinatorics, etc
      64              :          * @param a number of options
      65              :          * @param b number of choices
      66              :          * @return a choose b
      67              :          */
      68              :         constexpr unsigned int binomialCoefficient(unsigned a, unsigned b) {
      69              :             return factorial(a) / (factorial(b) * factorial(a - b));
      70              :         }
      71              : 
      72              :         /*!
      73              :          * Compile-time evaluation of the number of hypercubes of dimension m in a hypercube
      74              :          * of dimension Dim
      75              :          * @tparam Dim parent hypercube dimension
      76              :          * @param m sub-hypercube dimension
      77              :          * @return The number of m-cubes in an n-cube
      78              :          */
      79              :         template <unsigned Dim>
      80              :         constexpr unsigned int countCubes(unsigned m) {
      81              :             return (1 << (Dim - m)) * binomialCoefficient(Dim, m);
      82              :         }
      83              : 
      84              :         /*!
      85              :          * Determines whether a facet is on the upper boundary
      86              :          * of its domain. For lower dimension hypercubes, determines
      87              :          * whether the component is on the upper boundary of
      88              :          * the domain along at least one axis
      89              :          * @param face the hypercube's index
      90              :          * @return Whether it touches any upper boundary
      91              :          */
      92              :         bool isUpper(unsigned int face);
      93              : 
      94              :         /*!
      95              :          * Determine the axis perpendicular to a given facet
      96              :          * (throws an exception if the index does not correspond
      97              :          * to a facet)
      98              :          * @param face the facet's index
      99              :          * @return The index of the axis perpendicular to that facet
     100              :          */
     101              :         unsigned int getFaceDim(unsigned int face);
     102              : 
     103              :         /*!
     104              :          * Converts between ternary encoding and face set indexing
     105              :          * @tparam Dim the number of dimensions
     106              :          * @param index the ternary-encoded index of a facet in [0, 3^Dim)
     107              :          * @return The index of that facet in a set of faces in [0, 2*Dim)
     108              :          */
     109              :         template <unsigned Dim>
     110              :         unsigned int indexToFace(unsigned int index) {
     111              :             // facets are group low/high by axis
     112              :             unsigned int axis = index / 2;
     113              :             // the digit to subtract is determined by whether the index describes an upper
     114              :             // face (even index) or lower face (odd index) and that digit's position is
     115              :             // determined by the axis of the face
     116              :             unsigned int toRemove = (2 - index % 2) * countHypercubes(axis);
     117              :             // start with all 2s (in base 3) and change the correct digit to get the encoded face
     118              :             return countHypercubes(Dim) - 1 - toRemove;
     119              :         }
     120              : 
     121              :         /*!
     122              :          * Computes the ternary-encoded index of a hypercube
     123              :          * @tparam Dim the number of dimensions in the full hypercube
     124              :          * @tparam CubeTags... variadic argument list, must be all e_cube_tag
     125              :          * @param tag(s...) the tags describing the hypercube of interest
     126              :          * @return The index of the desired hypercube
     127              :          */
     128              :         template <
     129              :             unsigned Dim, typename... CubeTags,
     130              :             typename = std::enable_if_t<sizeof...(CubeTags) == Dim - 1>,
     131              :             typename = std::enable_if_t<std::conjunction_v<std::is_same<e_cube_tag, CubeTags>...>>>
     132            0 :         unsigned int getCube(e_cube_tag tag, CubeTags... tags) {
     133              :             if constexpr (Dim == 1) {
     134            0 :                 return tag;
     135              :             } else {
     136            0 :                 return tag + 3 * getCube<Dim - 1>(tags...);
     137              :             }
     138              :         }
     139              : 
     140              :         /*!
     141              :          * Utility function for getFace
     142              :          */
     143              :         template <size_t... Idx>
     144              :         unsigned int getFace_impl(const std::array<e_cube_tag, sizeof...(Idx)>& args,
     145              :                                   const std::index_sequence<Idx...>&) {
     146              :             return getCube<sizeof...(Idx)>(args[Idx]...);
     147              :         }
     148              : 
     149              :         /*!
     150              :          * Convenience alias for getCube for getting facets
     151              :          * @tparam Dim the number of dimensions in the parent hypercube
     152              :          * @param axis the axis perpendicular to the facet
     153              :          * @param side whether the facet is an upper or lower facet
     154              :          * @return The index of the facet
     155              :          */
     156              :         template <unsigned Dim>
     157              :         unsigned int getFace(unsigned int axis, e_cube_tag side) {
     158              :             std::array<e_cube_tag, Dim> args;
     159              :             args.fill(IS_PARALLEL);
     160              :             args[axis] = side;
     161              :             return getFace_impl(args, std::make_index_sequence<Dim>{});
     162              :         }
     163              :     }  // namespace detail
     164              : 
     165              :     template <unsigned Dim>
     166              :     class FieldLayout {
     167              :     public:
     168              :         using NDIndex_t        = NDIndex<Dim>;
     169              :         using view_type        = typename detail::ViewType<NDIndex_t, 1>::view_type;
     170              :         using host_mirror_type = typename view_type::host_mirror_type;
     171              : 
     172              :         struct bound_type {
     173              :             // lower bounds (ordering: x, y, z, ...)
     174              :             std::array<long, Dim> lo;
     175              :             // upper bounds (ordering: x, y, z, ...)
     176              :             std::array<long, Dim> hi;
     177              : 
     178              :             /*!
     179              :              * Compute the size of the region described by the bounds
     180              :              * @return Product of the axial dimensions of the region
     181              :              */
     182            0 :             long size() const {
     183            0 :                 long total = 1;
     184            0 :                 for (unsigned d = 0; d < Dim; d++) {
     185            0 :                     total *= hi[d] - lo[d];
     186              :                 }
     187            0 :                 return total;
     188              :             }
     189              :         };
     190              : 
     191              :         using rank_list   = std::vector<int>;
     192              :         using bounds_list = std::vector<bound_type>;
     193              : 
     194              :         using neighbor_list       = std::array<rank_list, detail::countHypercubes(Dim) - 1>;
     195              :         using neighbor_range_list = std::array<bounds_list, detail::countHypercubes(Dim) - 1>;
     196              : 
     197              :         /*!
     198              :          * Default constructor, which should only be used if you are going to
     199              :          * call 'initialize' soon after (before using in any context)
     200              :          */
     201              :         FieldLayout(const mpi::Communicator& = MPI_COMM_WORLD);
     202              : 
     203              :         FieldLayout(mpi::Communicator, const NDIndex<Dim>& domain, std::array<bool, Dim> decomp,
     204              :                     bool isAllPeriodic = false);
     205              : 
     206              :         // Destructor: Everything deletes itself automatically ... the base
     207              :         // class destructors inform all the FieldLayoutUser's we're going away.
     208          996 :         virtual ~FieldLayout() = default;
     209              : 
     210              :         // Initialization functions, only to be called by the user of FieldLayout
     211              :         // objects when the FieldLayout was created using the default constructor;
     212              :         // otherwise these are only called internally by the various non-default
     213              :         // FieldLayout constructors:
     214              : 
     215              :         void initialize(const NDIndex<Dim>& domain, std::array<bool, Dim> decomp,
     216              :                         bool isAllPeriodic = false);
     217              : 
     218              :         // Return the domain.
     219         1722 :         const NDIndex<Dim>& getDomain() const { return gDomain_m; }
     220              : 
     221              :         // Compare FieldLayouts to see if they represent the same domain; if
     222              :         // dimensionalities are different, the NDIndex operator==() will return
     223              :         // false:
     224              :         template <unsigned Dim2>
     225              :         bool operator==(const FieldLayout<Dim2>& x) const {
     226              :             return gDomain_m == x.getDomain();
     227              :         }
     228              : 
     229              :         bool operator==(const FieldLayout<Dim>& x) const {
     230              :             for (unsigned int i = 0; i < Dim; ++i) {
     231              :                 if (hLocalDomains_m(comm.rank())[i] != x.getLocalNDIndex()[i]) {
     232              :                     return false;
     233              :                 }
     234              :             }
     235              :             return true;
     236              :         }
     237              : 
     238              :         // for the requested dimension, report if the distribution is
     239              :         // SERIAL or PARALLEL
     240              :         bool getDistribution(unsigned int d) const {
     241              :             return minWidth_m[d] == (unsigned int)gDomain_m[d].length();
     242              :         }
     243              : 
     244              :         // for the requested dimension, report if the distribution was requested to
     245              :         // be SERIAL or PARALLEL
     246            0 :         std::array<bool, Dim> isParallel() const { return isParallelDim_m; }
     247              : 
     248              :         const NDIndex_t& getLocalNDIndex(int rank = -1) const;
     249              : 
     250              :         const host_mirror_type getHostLocalDomains() const;
     251              : 
     252              :         const view_type getDeviceLocalDomains() const;
     253              : 
     254              :         /*!
     255              :          * Get a list of all the neighbors, arranged by ternary encoding
     256              :          * of the hypercubes
     257              :          * @return List of list of neighbor ranks touching each boundary component
     258              :          */
     259              :         const neighbor_list& getNeighbors() const;
     260              : 
     261              :         /*!
     262              :          * Get the domain ranges corresponding to regions that should be sent
     263              :          * to neighbor ranks
     264              :          * @return Ranges to send
     265              :          */
     266              :         const neighbor_range_list& getNeighborsSendRange() const;
     267              : 
     268              :         /*!
     269              :          * Get the domain ranges corresponding to regions that should be received
     270              :          * from neighbor ranks
     271              :          * @return Ranges to receive
     272              :          */
     273              :         const neighbor_range_list& getNeighborsRecvRange() const;
     274              : 
     275              :         /*!
     276              :          * Given the index of a hypercube, find the index of the opposite hypercube,
     277              :          * i.e. the component with the same codimension belonging to a neighboring domain
     278              :          * that touches the hypercube with the given index, as determined by the
     279              :          * ternary encoding for hypercubes.
     280              :          *
     281              :          * For neighbor communication, the opposite component is the one that receives
     282              :          * sent data or sends us data to receive for a given component.
     283              :          *
     284              :          * The matching index is given by swapping alls 1s for 0s and vice versa in
     285              :          * the ternary encoding, while keeping the 2s unchanged. This can be understood
     286              :          * from the fact that if the local component is on the upper boundary of the local
     287              :          * domain, the neighbor component must be on the lower boundary of its local domain,
     288              :          * and vice versa. The 2s are unchanged because both the local component and the
     289              :          * neighbor component must be parallel to the same axes, otherwise their intersection
     290              :          * would have lower or higher dimension than the components themselves.
     291              :          * @param index index of the known component
     292              :          * @return Index of the matching component
     293              :          */
     294              :         static int getMatchingIndex(int index);
     295              : 
     296              :         /*!
     297              :          * Recursively finds neighbor ranks for layouts with all periodic boundary
     298              :          * conditions
     299              :          * @param nghost number of ghost cells
     300              :          * @param localDomain the rank's local domain
     301              :          * @param grown the local domain, grown by the number of ghost cells
     302              :          * @param neighborDomain a candidate neighbor rank's domain
     303              :          * @param rank the candidate neighbor's rank
     304              :          * @param offsets a dictionary containing offsets along different dimensions
     305              :          * @param d0 the dimension from which to start checking (default 0)
     306              :          * @param codim the codimension of overlapping regions to check (default 0)
     307              :          */
     308              :         void findPeriodicNeighbors(const int nghost, const NDIndex<Dim>& localDomain,
     309              :                                    NDIndex<Dim>& grown, NDIndex<Dim>& neighborDomain,
     310              :                                    const int rank, std::map<unsigned int, int>& offsets,
     311              :                                    unsigned d0 = 0, unsigned codim = 0);
     312              : 
     313              :         /*!
     314              :          * Finds all neighboring ranks based on the field layout
     315              :          * @param nghost number of ghost cells (default 1)
     316              :          */
     317              :         void findNeighbors(int nghost = 1);
     318              : 
     319              :         /*!
     320              :          * Adds a neighbor to the neighbor list
     321              :          * @param gnd the local domain, including ghost cells
     322              :          * @param nd the local domain
     323              :          * @param ndNeighbor the neighbor rank's domain
     324              :          * @param intersect the intersection of the domains
     325              :          * @param nghost number of ghost cells
     326              :          * @param rank the neighbor's rank
     327              :          */
     328              :         void addNeighbors(const NDIndex_t& gnd, const NDIndex_t& nd, const NDIndex_t& ndNeighbor,
     329              :                           const NDIndex_t& intersect, int nghost, int rank);
     330              : 
     331              :         void write(std::ostream& = std::cout) const;
     332              : 
     333              :         void updateLayout(const std::vector<NDIndex_t>& domains);
     334              : 
     335              :         bool isAllPeriodic_m;
     336              : 
     337              :         mpi::Communicator comm;
     338              : 
     339              :     private:
     340              :         /*!
     341              :          * Obtain the bounds to send / receive. The second domain, i.e.,
     342              :          * nd2, is grown by nghost cells in each dimension in order to
     343              :          * figure out the intersecting cells.
     344              :          * @param nd1 either remote or owned domain
     345              :          * @param nd2 either remote or owned domain
     346              :          * @param offset to map global to local grid point
     347              :          * @param nghost number of ghost cells per dimension
     348              :          */
     349              :         bound_type getBounds(const NDIndex_t& nd1, const NDIndex_t& nd2, const NDIndex_t& offset,
     350              :                              int nghost);
     351              : 
     352              :         int getPeriodicOffset(const NDIndex_t& nd, const unsigned int d, const int k);
     353              : 
     354              :     private:
     355              :         //! Global domain
     356              :         NDIndex_t gDomain_m;
     357              : 
     358              :         //! Local domains (device view)
     359              :         view_type dLocalDomains_m;
     360              : 
     361              :         //! Local domains (host mirror view)
     362              :         host_mirror_type hLocalDomains_m;
     363              : 
     364              :         std::array<bool, Dim> isParallelDim_m;
     365              : 
     366              :         unsigned int minWidth_m[Dim];
     367              : 
     368              :         neighbor_list neighbors_m;
     369              :         neighbor_range_list neighborsSendRange_m, neighborsRecvRange_m;
     370              : 
     371              :         void calcWidths();
     372              :     };
     373              : 
     374              :     template <unsigned Dim>
     375            0 :     inline std::ostream& operator<<(std::ostream& out, const FieldLayout<Dim>& f) {
     376            0 :         f.write(out);
     377            0 :         return out;
     378              :     }
     379              : }  // namespace ippl
     380              : 
     381              : #include "FieldLayout/FieldLayout.hpp"
     382              : 
     383              : #endif
        

Generated by: LCOV version 2.0-1