lorniper
lorniper

Reputation: 638

multidimensional complex valued array in C++

I have to generate a multidimensional array N (j, k, m, l) which is complex valued, approximately 10*100*100*1000.

I want to do the following to compute this N and return.

for j<10 ...
{
  for k<100 ...
  {
     ......
     some matrix multiplication to generate a 2D complex valued matrix n(100*1000)
     ......
     N(j,k,:,:)= n
   }
}

My questions:

  1. how to implement N(j,k,:,:)= n efficiently.

  2. for the present problem size, should I code from scratch or use some existing library?

Upvotes: 1

Views: 515

Answers (1)

Tony Delroy
Tony Delroy

Reputation: 106096

You're talking about 10*100*100*1000 = 100,000,000 complex numbers, likely 8 bytes each if 2 floats, or 16 bytes for 2 doubles, so roughly 800 megabytes or 1.6 gigabytes. Well within the capacity of an average desktop PC, which is a good start.

The main thing for efficient assignment is to ensure the memory layout is such that the assignment is dealing with contiguous memory. You can write a couple classes to provide a nice interface - say Matrix_2D then a Matrix_4D like:

template <typename T>
class Matrix_4D
{
  public:
    Matrix_4D(size_t j, size_t k, size_t l, size_t m)
      : j_(j), k_(k), l_(l), m_(m), data_(new T[j * k * l * m]),
        klm_(k * l * m), lm_(l * m),
    { /* optionally, initialise elements */ }

    ~Matrix_4D() { delete data_; }

    T& operator()(size_t j, size_t k, size_t l, size_t m)
    {
        return data_[j * klm_ + k * lm_ + l * m_ + m];
    }

    const T& operator()(size_t j, size_t k, size_t l, size_t m) const
    {
        return data_[j * klm_ + k * lm_ + l * m_ + m];
    }

    void set(size_t l, size_t m, const Matrix_2D& m2)
    {
        if (m2.j_ != l_ || m2.k_ != m_)
            throw std::runtime_error("mismatched dimensions");
        std::copy(m2.data_[0], m2.data_[lm_], (*this)(l, m, 0, 0));
    }

  private:
    size_t j_, k_, l_, m_;
    size_t klm_, lm_; // needed so often -> save
    T* data_;
};

The matrix classes should be friends so they can rip data out of each other. If you want to get fancier, you can actually provide a proxy object - add the following to Matrix_4D

    struct Proxy_2D
    {
        Proxy_2D(Matrix_4D& m4, size_t l, size_t m) : m4_(m4), l_(l), m_(m) { }
        Proxy_2D& operator=(const Matrix2D& m2)
        {
            m4_.set(l_, m_, m2);
            return *this;
        }
        Matrix_4D& m4_;
        size_t l_, m_;
    };

    Proxy_2D operator()(size_t l, size_t m) { return Proxy_2D(*this, l, m); }

Then you can do this:

Matrix_4D m4(10, 20, 30, 40);
Matrix_2D m2(30, 40);
... set stuff in m2 ...
m4(2, 4) = m2;

EDIT: for the code in your comment - m2= m2 * transpose(m2) - if you want to pursue this kind of do-it-yourself implementation to learn C++ rather than grab an existing efficient library using high-performance techniques like template expressions (which are way too complicated to get into here), then in Matrix_2D:

    Matrix_2D transpose() const
    {
        Matrix_2D result(m_, l_);
        for (size_t l = 0; l < l_; ++l)
            for (size_t m = 0; m < m_; ++m)
                result(m, l)= (*this)(l, m);
        return result;
    }

    Matrix_2D& operator+=(const Matrix_2D& rhs)
    {
        for (size_t l = 0; l < l_; ++l)
            for (size_t m = 0; m < m_; ++m)
                (*this)(l, m) += rhs(l, m);
        return *this;
    }

    Matrix_2D operator+(const Matrix_2D& rhs) const
    {
        Matrix_2D result(*this); // copy *this
        return result += rhs;
    }

Interestingly, you can also have a transpose as a kind of dynamic perspective on a matrix without copying the data, but then you need to make sure the underlying matrix object's lifetime spans the use of the transposition object:

template <typename T>
class Transpose_2D
{
  public:
    Transpose_2D(Matrix_2D<T>& m) : m_(m) { }

    T& operator()(size_t l, size_t m) { return m_(m, l); }
    const T& operator()(size_t l, size_t m) const { return m_(m, l); }

  private:
    Matrix_2D<T>& m_;
};

Changing the Matrix_2D addition function signatures correspondingly allows this to be used, e.g.:

template <typename U>
Matrix_2D& operator+=(const U& rhs)
...

Then you can do:

m2 += Transpose_2D(m2);

And it will be reasonably efficient.

Upvotes: 2

Related Questions