LCOV - code coverage report
Current view: top level - include/utils - MultiIndex.h (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 8601ad Lines: 156 160 97.5 %
Date: 2025-07-18 13:27:08 Functions: 52 52 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #pragma once
      11             : 
      12             : #include "Moose.h"
      13             : #include "MooseError.h"
      14             : #include "DataIO.h"
      15             : #include <algorithm>
      16             : 
      17             : /**
      18             :  * Implements a container class for multi-indexed objects
      19             :  * with an arbitrary number of indices.
      20             :  */
      21             : template <class T>
      22             : class MultiIndex
      23             : {
      24             : public:
      25             :   /// MultiIndex container iterator
      26             :   template <bool is_const = false>
      27             :   class const_noconst_iterator;
      28             : 
      29             :   ///@{ container related types and categories
      30             :   typedef T value_type;
      31             :   typedef std::vector<unsigned int> size_type;
      32             :   using iterator = const_noconst_iterator<false>;
      33             :   using const_iterator = const_noconst_iterator<true>;
      34             :   ///@}
      35             : 
      36             :   /// construct zero initialized container of a given shape
      37             :   MultiIndex(const size_type & shape);
      38             : 
      39             :   /// construct container of a given shape initialized from a flat data blob
      40             :   MultiIndex(const size_type & shape, const std::vector<T> & data);
      41             : 
      42             :   ///@{ element access operators
      43             :   T & operator()(const size_type & indices);
      44             :   const T & operator()(const size_type & indices) const;
      45             :   ///@}
      46             : 
      47             :   ///@{ direct data access via bracket operator
      48           8 :   T & operator[](unsigned int j) { return _data[j]; }
      49       21305 :   const T & operator[](unsigned int j) const { return _data[j]; }
      50           1 :   T & at(unsigned int j) { return _data.at(j); }
      51             :   ///@}
      52             : 
      53             :   ///@{ accesses a slice of the multi index object
      54             :   MultiIndex<T> slice(unsigned int dimension, unsigned int index) const;
      55             :   MultiIndex<T> slice(size_type dimension, size_type index) const;
      56             :   ///@}
      57             : 
      58             :   /// container size as dim dimensional vector
      59        5361 :   const size_type & size() const { return _shape; }
      60             : 
      61             :   /// dimension of the container
      62       32301 :   unsigned int dim() const { return _shape.size(); }
      63             : 
      64             :   /// total number of values stored in the container
      65           1 :   unsigned int nEntries() const { return _nentries; }
      66             : 
      67             :   /// get the raw data vector
      68             :   std::vector<T> getRawData() const { return _data; }
      69             : 
      70             :   /// Resize container. Must keep dimensionality constant.
      71             :   void resize(const size_type & shape);
      72             : 
      73             :   /// Reshape container arbitrarily and initialize with value
      74             :   void assign(const size_type & shape, T value);
      75             : 
      76             :   ///@{ access the stride
      77             :   unsigned int stride(unsigned int j) const;
      78        2664 :   const size_type & stride() const { return _stride; }
      79             :   ///@}
      80             : 
      81             :   ///@{ Implement loadHelper and storeHelper for easier data (de)serialization
      82             :   void dataStore(std::ostream & stream, void * context);
      83             :   void dataLoad(std::istream & stream, void * context);
      84             :   ///@}
      85             : 
      86             :   ///@{ iterators for begin and end of this container
      87        2678 :   iterator begin() { return const_noconst_iterator<false>(*this, 0); }
      88        2668 :   iterator end() { return const_noconst_iterator<false>(*this, _nentries); }
      89             :   const_iterator begin() const { return const_noconst_iterator<true>(*this, 0); }
      90             :   const_iterator end() const { return const_noconst_iterator<true>(*this, _nentries); }
      91             :   ///@}
      92             : 
      93             :   /// compute the flat index for a size_type index
      94             :   unsigned int flatIndex(const size_type & indices) const;
      95             : 
      96             : protected:
      97             :   /// given a flat index computes the vector of indices i0, i1, i2, ...
      98             :   void findIndexVector(unsigned int flat_index, size_type & indices) const;
      99             : 
     100             :   /// the size along each index
     101             :   size_type _shape;
     102             : 
     103             :   /// the number of dimensions TODO: get rid of this -> _shape.size()
     104             :   unsigned int _dim;
     105             : 
     106             :   /// the number of entries TODO: get rid of this -> _data.size()
     107             :   unsigned int _nentries;
     108             : 
     109             :   /// stride for each index, e.g. if you know {i, j, k} -> flat_index,  {i, j + 1, k} = flat_index + stride[1]
     110             :   size_type _stride;
     111             : 
     112             :   /// the data unrolled into a vector
     113             :   std::vector<T> _data;
     114             : 
     115             : private:
     116             :   /// build accumulated shape vector for flat index calculation
     117             :   void buildAccumulatedShape();
     118             : 
     119             :   /// change the container shape and reset meta data
     120             :   void reshape(const size_type & shape);
     121             : };
     122             : 
     123             : /**
     124             :  * Nested iterator class for MultiIndex containers
     125             :  */
     126             : template <class T>
     127             : template <bool is_const>
     128             : class MultiIndex<T>::const_noconst_iterator
     129             : {
     130             : public:
     131             :   typedef typename std::conditional<is_const, const MultiIndex<T> &, MultiIndex<T> &>::type
     132             :       reference_type;
     133             : 
     134        5346 :   const_noconst_iterator(reference_type multi_index, unsigned int position)
     135        5346 :     : _multi_index(multi_index), _flat_index(position), _shape(multi_index.size())
     136             :   {
     137        5346 :     _multi_index.findIndexVector(position, _indices);
     138        5346 :   }
     139             : 
     140             :   // Simple data getters
     141       24457 :   unsigned int flatIndex() const { return _flat_index; }
     142        2673 :   reference_type getMultiIndexObject() const { return _multi_index; }
     143             : 
     144             :   // assignment =
     145           4 :   const_noconst_iterator & operator=(const const_noconst_iterator & other)
     146             :   {
     147           4 :     _multi_index = other.getMultiIndexObject();
     148           4 :     _flat_index = other.flatIndex();
     149           4 :     _multi_index.findIndexVector(_flat_index, _indices);
     150           4 :     return *this;
     151             :   }
     152             : 
     153             :   // Compiler generated copy constructor
     154             :   const_noconst_iterator(const const_noconst_iterator &) = default;
     155             : 
     156             :   // prefix ++
     157       22288 :   const_noconst_iterator & operator++()
     158             :   {
     159       22288 :     ++_flat_index;
     160             :     // increment indices
     161       41209 :     for (unsigned int j = 0; j < _indices.size(); ++j)
     162             :     {
     163       38530 :       _indices[_indices.size() - j - 1] =
     164       38530 :           (_indices[_indices.size() - j - 1] + 1) % _shape[_indices.size() - j - 1];
     165       38530 :       if (_indices[_indices.size() - j - 1] != 0)
     166       19609 :         break;
     167             :     }
     168       22288 :     return *this;
     169             :   }
     170             : 
     171             :   // postfix ++
     172             :   const_noconst_iterator & operator++(int)
     173             :   {
     174             :     const_noconst_iterator clone(*this);
     175             :     ++_flat_index;
     176             :     // increment indices
     177             :     for (unsigned int j = 0; j < _indices.size(); ++j)
     178             :     {
     179             :       _indices[_indices.size() - j - 1] =
     180             :           (_indices[_indices.size() - j - 1] + 1) % _shape[_indices.size() - j - 1];
     181             :       if (_indices[_indices.size() - j - 1] != 0)
     182             :         break;
     183             :     }
     184             :     return clone;
     185             :   }
     186             : 
     187             :   // prefix --
     188          24 :   const_noconst_iterator & operator--()
     189             :   {
     190          24 :     --_flat_index;
     191             :     // decrement indices
     192          34 :     for (unsigned int j = 0; j < _indices.size(); ++j)
     193             :     {
     194          33 :       if (_indices[_indices.size() - j - 1] == 0)
     195          10 :         _indices[_indices.size() - j - 1] = _shape[_indices.size() - j - 1] - 1;
     196             :       else
     197             :       {
     198          23 :         --_indices[_indices.size() - j - 1];
     199          23 :         break;
     200             :       }
     201             :     }
     202          24 :     return *this;
     203             :   }
     204             : 
     205             :   // postfix --
     206             :   const_noconst_iterator & operator--(int)
     207             :   {
     208             :     const_noconst_iterator clone(*this);
     209             :     --_flat_index;
     210             :     // decrement indices
     211             :     for (unsigned int j = 0; j < _indices.size(); ++j)
     212             :     {
     213             :       if (_indices[_indices.size() - j - 1] == 0)
     214             :         _indices[_indices.size() - j - 1] = _shape[_indices.size() - j - 1] - 1;
     215             :       else
     216             :       {
     217             :         --_indices[_indices.size() - j - 1];
     218             :         break;
     219             :       }
     220             :     }
     221             :     return clone;
     222             :   }
     223             : 
     224             :   /// to be equal both iterators must hold a reference to the same MultiIndexObject and be at the same _flat_index
     225       24453 :   bool operator==(const const_noconst_iterator & other) const
     226             :   {
     227       24453 :     return _flat_index == other.flatIndex() && &_multi_index == &other.getMultiIndexObject();
     228             :   }
     229       24453 :   bool operator!=(const const_noconst_iterator & other) const { return !(*this == other); }
     230             : 
     231             :   /// dereferencing operator
     232       22840 :   std::pair<const size_type &, T &> operator*()
     233             :   {
     234       22840 :     return std::pair<const size_type &, T &>(_indices, _multi_index._data[_flat_index]);
     235             :   }
     236             :   std::pair<const size_type &, const T &> operator*() const
     237             :   {
     238             :     return std::pair<const size_type &, const T &>(_indices, _multi_index._data[_flat_index]);
     239             :   }
     240             : 
     241             : protected:
     242             :   reference_type _multi_index;
     243             :   unsigned int _flat_index;
     244             :   size_type _shape;
     245             :   size_type _indices;
     246             : };
     247             : 
     248             : template <class T>
     249        2684 : MultiIndex<T>::MultiIndex(const size_type & shape)
     250             : {
     251        2684 :   reshape(shape);
     252        2684 :   _data.resize(_nentries);
     253        2684 : }
     254             : 
     255             : template <class T>
     256           2 : MultiIndex<T>::MultiIndex(const size_type & shape, const std::vector<T> & data)
     257             : {
     258           2 :   reshape(shape);
     259             : 
     260           2 :   if (data.size() != _nentries)
     261           0 :     mooseError("shape and data arguments' sizes are inconsistent.");
     262           2 :   _data = data;
     263           2 : }
     264             : 
     265             : template <class T>
     266             : T &
     267         690 : MultiIndex<T>::operator()(const size_type & indices)
     268             : {
     269         690 :   return _data[flatIndex(indices)];
     270             : }
     271             : 
     272             : template <class T>
     273             : const T &
     274             : MultiIndex<T>::operator()(const size_type & indices) const
     275             : {
     276             :   return _data[flatIndex(indices)];
     277             : }
     278             : 
     279             : template <class T>
     280             : MultiIndex<T>
     281           1 : MultiIndex<T>::slice(unsigned int dimension, unsigned int index) const
     282             : {
     283           1 :   size_type dim(1, dimension);
     284           1 :   size_type ind(1, index);
     285           2 :   return slice(dim, ind);
     286           1 : }
     287             : 
     288             : template <class T>
     289             : MultiIndex<T>
     290           8 : MultiIndex<T>::slice(size_type dimension, size_type index) const
     291             : {
     292             :   // input checks
     293           8 :   if (dimension.size() != index.size())
     294           0 :     mooseError("dimension and index must have the same size.");
     295           8 :   if (dimension.size() > _dim - 1)
     296           0 :     mooseError("The result of slice must be at least of dimension 1.");
     297             : 
     298             : #if DEBUG
     299             :   for (unsigned int d = 0; d < dimension.size(); ++d)
     300             :   {
     301             :     if (dimension[d] >= _dim)
     302             :       mooseError("dimension is set to ", dimension[d], " which is larger than _dim ", _dim);
     303             :     if (index[d] >= _shape[dimension[d]])
     304             :       mooseError("index= ",
     305             :                  index[d],
     306             :                  " at dimension=",
     307             :                  dimension[d],
     308             :                  " is larger than ",
     309             :                  _shape[dimension[d]]);
     310             :   }
     311             : #endif // DEBUG
     312             : 
     313             :   // create a MultiIndex object with new dim = dim - dimension.size()
     314           8 :   size_type new_shape;
     315          37 :   for (unsigned int j = 0; j < _dim; ++j)
     316          29 :     if (std::find(dimension.begin(), dimension.end(), j) == dimension.end())
     317          23 :       new_shape.push_back(_shape[j]);
     318           8 :   MultiIndex<T> multi_index = MultiIndex(new_shape);
     319             : 
     320             :   // copy the data
     321           8 :   MultiIndex<T>::iterator it = multi_index.begin();
     322        1388 :   for (unsigned int n = 0; n < _nentries; ++n)
     323             :   {
     324        1380 :     size_type indices;
     325        1380 :     findIndexVector(n, indices);
     326        1380 :     bool addTo = true;
     327        1728 :     for (unsigned int d = 0; d < dimension.size(); ++d)
     328        1272 :       if (indices[dimension[d]] != index[d])
     329             :       {
     330         924 :         addTo = false;
     331         924 :         break;
     332             :       }
     333             : 
     334        1380 :     if (addTo)
     335             :     {
     336         456 :       (*it).second = _data[n];
     337         456 :       ++it;
     338             :     }
     339             :   }
     340          16 :   return multi_index;
     341           8 : }
     342             : 
     343             : template <class T>
     344             : void
     345           1 : MultiIndex<T>::resize(const size_type & shape)
     346             : {
     347           1 :   if (shape.size() != _shape.size())
     348           0 :     mooseError("resize cannot change the dimensionality of MultiIndex.");
     349             : 
     350             :   // first copy the old shape and data
     351           1 :   size_type old_shape = _shape;
     352           1 :   size_type old_stride = _stride;
     353           1 :   std::vector<T> old_data = _data;
     354             : 
     355             :   // reset _shape & recompute meta data
     356           1 :   reshape(shape);
     357             : 
     358             :   // fill in _data to all possible indices
     359           1 :   _data.assign(_nentries, T(0));
     360          31 :   for (unsigned int j = 0; j < _nentries; ++j)
     361             :   {
     362          30 :     size_type indices;
     363          30 :     findIndexVector(j, indices);
     364             : 
     365             :     // check if indices existed in the old version of _data
     366          30 :     bool existed_in_old = true;
     367          96 :     for (unsigned int d = 0; d < _dim; ++d)
     368          80 :       if (indices[d] >= old_shape[d])
     369             :       {
     370          14 :         existed_in_old = false;
     371          14 :         break;
     372             :       }
     373             : 
     374             :     // find the corresponding old_j
     375          30 :     if (existed_in_old)
     376             :     {
     377          16 :       unsigned int old_j = 0;
     378          64 :       for (unsigned int d = 0; d < _dim; ++d)
     379          48 :         old_j += indices[d] * old_stride[d];
     380             : 
     381             :       // finally set the data entry
     382          16 :       _data[j] = old_data[old_j];
     383             :     }
     384             :   }
     385           1 : }
     386             : 
     387             : template <class T>
     388             : unsigned int
     389           3 : MultiIndex<T>::stride(unsigned int j) const
     390             : {
     391             :   mooseAssert(j < _dim, "Dimension is" << _dim << ", stride(j) called with j = " << j);
     392           3 :   return _stride[j];
     393             : }
     394             : 
     395             : template <class T>
     396             : void
     397           1 : MultiIndex<T>::assign(const size_type & shape, T value)
     398             : {
     399           1 :   reshape(shape);
     400           1 :   _data.assign(_nentries, value);
     401           1 : }
     402             : 
     403             : template <class T>
     404             : void
     405           1 : MultiIndex<T>::dataStore(std::ostream & stream, void * context)
     406             : {
     407           1 :   ::dataStore(stream, _shape, context);
     408           1 :   ::dataStore(stream, _data, context);
     409           1 : }
     410             : 
     411             : template <class T>
     412             : void
     413           1 : MultiIndex<T>::dataLoad(std::istream & stream, void * context)
     414             : {
     415           1 :   ::dataLoad(stream, _shape, context);
     416           1 :   ::dataLoad(stream, _data, context);
     417           1 :   _dim = _shape.size();
     418           1 :   _nentries = _data.size();
     419           1 :   buildAccumulatedShape();
     420           1 : }
     421             : 
     422             : template <class T>
     423             : void
     424        6760 : MultiIndex<T>::findIndexVector(unsigned int flat_index, MultiIndex<T>::size_type & indices) const
     425             : {
     426        6760 :   indices.resize(_dim);
     427       28241 :   for (unsigned int d = 0; d < _dim; ++d)
     428             :   {
     429       21481 :     unsigned int i = flat_index / _stride[d];
     430       21481 :     indices[d] = i;
     431       21481 :     flat_index -= i * _stride[d];
     432             :   }
     433        6760 : }
     434             : 
     435             : // compute the accumulated shapes as:
     436             : // as[0] = I_1 * I_2 ...* I_{M}, as[1] = I_2 * I_3 ...* I_{M} ...
     437             : template <class T>
     438             : void
     439        2684 : MultiIndex<T>::buildAccumulatedShape()
     440             : {
     441             :   // TODO: simplify - this is needlessly complicated - can be done in a single loop
     442        2684 :   _stride.resize(_dim);
     443       10741 :   for (unsigned int d = 0; d < _dim; ++d)
     444             :   {
     445        8057 :     unsigned int k = 1;
     446       16127 :     for (unsigned int j = d + 1; j < _dim; ++j)
     447        8070 :       k *= _shape[j];
     448        8057 :     _stride[d] = k;
     449             :   }
     450        2684 : }
     451             : 
     452             : template <class T>
     453             : void
     454        2688 : MultiIndex<T>::reshape(const size_type & shape)
     455             : {
     456        2688 :   if (shape.size() == 0)
     457             :   {
     458           5 :     _shape = {};
     459           5 :     _dim = 0;
     460           5 :     _stride = {};
     461           5 :     _nentries = 0;
     462           5 :     return;
     463             :   }
     464             : 
     465        2683 :   _shape = shape;
     466        2683 :   _dim = shape.size();
     467             : 
     468        2683 :   _nentries = 1;
     469       10737 :   for (unsigned int d = 0; d < _dim; ++d)
     470        8054 :     _nentries *= _shape[d];
     471             : 
     472        2683 :   buildAccumulatedShape();
     473             : }
     474             : 
     475             : template <class T>
     476             : unsigned int
     477        3354 : MultiIndex<T>::flatIndex(const size_type & indices) const
     478             : {
     479             :   mooseAssert(indices.size() == _dim,
     480             :               "Indices vector has wrong size. size=" << indices.size() << " vs. dim=" << _dim);
     481             : #if DEBUG
     482             :   for (unsigned int j = 0; j < indices.size(); ++j)
     483             :     if (indices[j] >= _shape[j])
     484             :       mooseError("Indices vector at entry ", j, " is ", indices[j], " vs. shape ", _shape[j]);
     485             : #endif // DEBUG
     486             : 
     487             :   // implement the index
     488             :   // index = i_M + i_{M-1} * I_M + i_{M-1} * I_M * I_{M-1} ...
     489        3354 :   unsigned int index = 0;
     490       13588 :   for (unsigned int d = 0; d < _dim; ++d)
     491       10234 :     index += indices[d] * _stride[d];
     492             : 
     493        3354 :   return index;
     494             : }
     495             : 
     496             : template <class T>
     497             : void
     498           1 : dataStore(std::ostream & stream, MultiIndex<T> & mi, void * context)
     499             : {
     500           1 :   mi.dataStore(stream, context);
     501           1 : }
     502             : 
     503             : template <class T>
     504             : void
     505           1 : dataLoad(std::istream & stream, MultiIndex<T> & mi, void * context)
     506             : {
     507           1 :   mi.dataLoad(stream, context);
     508           1 : }

Generated by: LCOV version 1.14