-1

In a restricted sense, a matrix is a table of numbers. It has rows and columns that are generally numbered sequentially. So, we have the element (i,j). Of course, this is all in our heads!!! There are no real "columns" or "rows".

In practice, the column and the row are related to data that is more naturally referred to by means of things that are not integers. I would rather index matrices using pointers, for example.

Imagine that each "column" refers to some input parameters. Then, I need to keep a mapping that translates the parameter* to the column index, back and forth. But then, if I remove one of the parameters, I have to update the mapping.

Is there a way to use, for example, double* instead of int as the keys to Eigen matrix entries?

If there isn't... are there other free software (as in freedom) alternatives for sparse matrices?

André Caldas
  • 128
  • 1
  • 7
  • 1
    You mean … [`std::map`](https://en.cppreference.com/w/cpp/container/map) or [`std::unordered_map`](https://en.cppreference.com/w/cpp/container/unordered_map)? – Homer512 May 10 '23 at 06:40
  • @Homer512: I don't mean any. A "map" is an abstraction. Which implementation to choose is a different problem. You question is irrelevant to my post. I don't understand why you ask... – André Caldas May 10 '23 at 10:08
  • I don't understand what you intend to do. Are you sure you want to have matrices? How would you multiply/add matrices which have non-integer indexes? – chtz May 10 '23 at 12:16
  • @chtz: matrices are a table of data. You could call the frist row the 0-row. The second, the 1-row, etc. But you could also call it the A-row, the B-row. Or, you could call it the CAR-row, BICICLE-row. The columns you might want to call the WEIGHT-column, the PRICE-column, etc. When you eliminate rows from a matrix, the remaining rows get renumbered. But if you give it a name, you don't need to worry. Using integers is just like, in a program, calling all your variables var1, var2, var3, etc. And if you eliminate var2, var3 gets renamed to var2. A nightmare... – André Caldas May 10 '23 at 13:33
  • @chtz: You can multiply and add just the same. Addition is done for each entry that has common index. Multiplication can be done if the rows on the right are compatible with columns on the left. Matrices are just a way to represent linear transforms... there are no "rows" or "columns" in linear transforms, and not all vector spaces are R^n. I am talking about R^A, where A is a finite set: CAR, BICYCLE, etc. – André Caldas May 10 '23 at 13:38
  • @AndréCaldas Ok, you can work on the vector space `R^{set of doubles}` and define a "sparse matrix" as `map >` and a sparse vector as `map` (where `map` can but does not have to be `std::map`). Implementing Matrix-Matrix or Matrix-Vector-products should be relatively straight-forward. Eigen does not support this, and I doubt that you can convince anyone to integrate that into Eigen. I also don't know of any library which implements that (asking for existing libraries would be off-topic, anyways) – chtz May 10 '23 at 15:51
  • @chtz: I think the idea is very similar to Eigen3's slicing. Maybe one could create a matrix using pointers as index (double*) as integers (long long long integer)... and then use "Matrix(vector_of_pointers, another_vector_of_pointers)" to get a slice. But then, you still need to map the indexes back, because the new "sliced matrix" does not know the original pointers. – André Caldas May 10 '23 at 16:36
  • @chtz: I have just realized that Eigen3 can use std::ptrdiff_t as index... maybe, this could be it... – André Caldas May 10 '23 at 16:55

1 Answers1

2

The use of integers isn't arbitrary, they are inherent to an efficient implementation through their fast and direct mapping to and from memory addresses. Any implementation that doesn't use integer indices will naturally degrade performance. The simplest such implementation would be based on an associative array such as std::map<RowKey, std::map<ColumnKey, double>>.

It seems like you want to provide some sort of meaning to individual rows / columns. If you want to preserve reasonable performance, you should stick with the integer-based sparse matrix format but of course you can map integers from and to arbitrary keys.

template<class T>
struct IndexFunction
{
    std::vector<T> index_to_key;
    std::unordered_map<T, int> key_to_index;
    
    IndexFunction() = default;

    template<class Iterator>
    IndexFunction(Iterator first, Iterator last)
    : index_to_key(first, last)
    {
        int index = 0;
        for(const T& key: index_to_key)
            key_to_index[key] = index++;
    }
    bool operator==(const IndexFunction& o) const noexcept
    { return this == &o || index_to_key == o.index_to_key; }
    
    bool operator!=(const IndexFunction& o) const noexcept
    { return this != &o && index_to_key != o.index_to_key; }
};

We can then attach this to our matrix type.

template<class Scalar, class RowKey, class ColKey, int Options=0>
struct Matrix
{
    using ScalarType = Scalar;
    using MatrixType = Eigen::SparseMatrix<Scalar, Options>;
    using RowFunction = IndexFunction<RowKey>;
    using ColFunction = IndexFunction<ColKey>;

    MatrixType matrix;
    std::shared_ptr<const RowFunction> row_function;
    std::shared_ptr<const ColFunction> col_function;

    template<class OtherColKey, int OtherOptions>
    friend Matrix<Scalar, RowKey, OtherColKey, Options> operator*(
            const Matrix& left,
            const Matrix<Scalar, ColKey, OtherColKey, OtherOptions>& right)
    {
        if(*left.col_function != *right.row_function)
            throw std::runtime_error("Incompatible dimensions");
        Matrix<Scalar, RowKey, OtherColKey, Options> rtrn;
        rtrn.matrix = left.matrix * right.matrix;
        rtrn.row_function = left.row_function;
        rtrn.col_function = right.col_function;
        return rtrn;
    }
};

Use it like this:

int main()
{
    enum class IndexA { a, b, c, d };
    static const IndexA akeys[] = { IndexA::a, IndexA::b, IndexA::c, IndexA::d };
    static const double IndexB[] = { 0., 1., 2., 3. };
    const int IndexC[] = { 4, 5, 6, 7 };
    using MatrixAB = Matrix<double, IndexA, double>;
    using MatrixBC = Matrix<double, double, int, Eigen::RowMajor>;
    using MatrixAC = Matrix<double, IndexA, int>;
    
    MatrixAB ab;
    ab.matrix = Eigen::Matrix4d::Random().sparseView();
    ab.row_function = std::make_shared<IndexFunction<IndexA>>(
        std::begin(akeys), std::end(akeys));
    ab.col_function = std::make_shared<IndexFunction<double>>(
        std::begin(IndexB), std::end(IndexB));
    MatrixBC bc;
    bc.matrix = Eigen::Matrix4d::Random().sparseView();
    bc.row_function = ab.col_function;
    bc.col_function = std::make_shared<IndexFunction<int>>(
        std::begin(IndexC), std::end(IndexC));
    MatrixAC ac = ab * bc;
}

Of course a comprehensive implementation would be more involved but since you didn't provide any specific needs, this outline must suffice. I'm not aware of any existing library which provides this kind of function. Most users are content with keeping the index mapping separate from the computation.

Homer512
  • 9,144
  • 2
  • 8
  • 25
  • Thank you for your time! The concept is called matrix-free: https://www.eigen.tuxfamily.org/dox/group__MatrixfreeSolverExample.html . A matrix-free option would allow one to use different representations for different things. It can also, as your implementation does, restrict compositions to when the types of left col / right row match. After all, what people want is not a matrix... it is a linear transform. A matrix is an XY-problem. – André Caldas Jun 06 '23 at 12:00
  • Eigen's internal representation is good for some stuff and bad for other stuff. It could be nice to have internal representations that were more friendly to permutations, for example. Up to some days ago, people were using matrix products to eliminate one matrix column. Works... but it is very ineficient. This is much better if implemented by directly manipulating the matrix internal representation. Now, after my "feature request" it has been implemented: https://gitlab.com/libeigen/eigen/-/issues/2659 . Good thing: use solvers that are matrix-free... no internal eigen matrix needed. – André Caldas Jun 06 '23 at 12:19