Re: [eigen] combine matrix blocks

[ Thread Index | Date Index | More lists.tuxfamily.org/eigen Archives ]


Hello,


I think this is a good start. Some comments:

- I'm not fan of the name "Proxy". It is too generic. Perhaps: "IndirectView", not sure though.

RowColProxy would be less generic. However I like IndirectView or IndirectAdresser or only Indirect. 


- in ei_traits, it'd be better to write ei_traits<RowType>::RowsAtCompileTime instead of RowType::RowsAtCompileTime. This will allow to write a ei_traits for some std containers (std::vector, std::, whatever), or other classes comming from different libs.

okay.

I started on an operator that returns the 'proxy'. I'm not so sure but I think constructor copies the whole matrix. For the operator (or in general) I guess this is not necessary, is it? 

 template <typename RowType, typename ColType>
  RowColProxy<Derived, RowType, ColType>
  operator()(RowType rowProxy, ColType colProxy) {
          Eigen::RowColProxy<Derived,RowType,ColType> proxy(*this, rowProxy, colProxy);
          return proxy;
  }



- for the same reason I'd use operator[] instead of operator().

- would be cool to implement Range on top of CwiseNullaryOp (like Ones, Zeros and Identity). This will allow us to use it to select a range of slices, and also to do some fancy things:

VectorXf v1to10 = VectorXf::Range(1,10); // this one is boring
VectorXf allpowerOfTwo = VectorXf::Constant(10,2).cwise().pow(VectorXf::Range(1,10)); // this one is cooler but not optimal

- regarding the API in MatrixBase, I guess we could afford something similar than in mathlab:

VectorXi colIndices = ...;
VectorXi rowIndices = ....;
mat(Range(...),colIndices);
mat(5,colIndices); // the integer 5 will be automatically converted to Range(5,5)
mat(rowIndices,Identity);
mat(Range(...),Range(...)); // no index vector => return a Block

For the Range I thought about three versions
1. the normal range

       template<int Size>
                  inline CwiseNullaryOp<ei_scalar_range_op<int>, Matrix<int,Size,1> >
                  Range(int from, int to) {
                          int size = (to-from);
                          return CwiseNullaryOp<ei_scalar_range_op<int>, Matrix<int,Size,1> >(size, 1, ei_scalar_range_op<int>(from));
                  };
2. slices  
          // typedef for return type (template parameter Size)
          template<int Size>
                  inline CwiseNullaryOp<ei_scalar_slice_op<int>, Matrix<int,Size,1> >
                  Range(int from, int to, int stride) {
                          int size = (int) ((to-from)/stride);
                          return CwiseNullaryOp<ei_scalar_slice_op<int>, Matrix<int,Size,1> >(size, 1, ei_scalar_slice_op<int>(from,stride));
                  };

 3. the identity interval
          template<int Size>
                  inline CwiseNullaryOp<ei_scalar_id_range_op<int>, Matrix<int,Size,1> >
                  Range() {
                          return CwiseNullaryOp<ei_scalar_id_range_op<int>, Matrix<int,Size,1> >(Size, 1, ei_scalar_id_range_op<int>());
                  };

The question is if it's better to define it dynamically sized (there are no default template parameters for functions) or like I did?

The according functors are:

#define EIGEN_FUNCTORS_PLUGIN "./Range.hpp"

#ifndef __RANGE_HPP__
#define __RANGE_HPP__

template<typename Scalar>
class ei_scalar_range_op EIGEN_EMPTY_STRUCT {
private:
Scalar _start;
public:
inline ei_scalar_range_op(Scalar start) : _start(start) {}; 
inline const Scalar operator() (int i) const { return _start+((Scalar) i); }
inline const Scalar operator() (int i,int j) const { 
assert(j==0);
return this->operator()(i); 
}
};

template<typename Scalar>
struct ei_functor_traits<ei_scalar_range_op<Scalar> >
{ enum { Cost = 1/*NumTraits<Scalar>::AddCost*/, PacketAccess = false, IsRepeatable = true }; };


template<typename Scalar>
class ei_scalar_slice_op EIGEN_EMPTY_STRUCT {
private:
Scalar _start,_stride;
public:
inline ei_scalar_slice_op(Scalar start, Scalar stride) : _start(start), _stride(stride) {}; 
inline const Scalar operator() (int i) const { return _start+((Scalar) i)*_stride; }
inline const Scalar operator() (int i,int j) const { 
assert(j==0);
return this->operator()(i); 
}
};

template<typename Scalar>
struct ei_functor_traits<ei_scalar_slice_op<Scalar> >
{ enum { Cost = 1/*NumTraits<Scalar>::AddCost*/, PacketAccess = false, IsRepeatable = true }; };


template<typename Scalar>
struct ei_scalar_id_range_op EIGEN_EMPTY_STRUCT {
inline ei_scalar_id_range_op() {}; 
inline const Scalar operator() (int i) const { return i; }
inline const Scalar operator() (int i,int j) const { 
assert(j==0);
return i;
}
};

template<typename Scalar>
struct ei_functor_traits<ei_scalar_id_range_op<Scalar> >
{ enum { Cost = 1/*NumTraits<Scalar>::AddCost*/, PacketAccess = false, IsRepeatable = true }; };

/*
 
template<typename Scalar> struct ei_scalar_identity_op EIGEN_EMPTY_STRUCT {
  EIGEN_STRONG_INLINE ei_scalar_identity_op(void) {}
  EIGEN_STRONG_INLINE const Scalar operator() (int row, int col) const { return row==col ? Scalar(1) : Scalar(0); }
};
template<typename Scalar>
struct ei_functor_traits<ei_scalar_identity_op<Scalar> >
{ enum { Cost = NumTraits<Scalar>::AddCost, PacketAccess = false, IsRepeatable = true }; };

*/

#endif




that's it for now,

gael.

On Mon, Jul 6, 2009 at 8:04 PM, Stefan Ulbrich <s.ulbrich@xxxxxxxxxxxxxx> wrote:
Hi,

this is how I implemented the "Proxy" class. In MatrixBase one could add a ::getColumnProxy (and Row,RowColumn).
In case only a partial proxy is chosen, matrix base could pass some kind of Range Vector class which efficiently returns the index as coeff().
Alternatively, one could do a type check at compile time if the template parameter was a constant 'identity'.
What do you think?

cheers,
stefan

template<typename MatrixType, typename RowType, typename ColType> class Proxy
 : public MatrixBase<Proxy<MatrixType, RowType, ColType> >
{
 public:

   EIGEN_GENERIC_PUBLIC_INTERFACE(Proxy)

   inline Proxy(const MatrixType& matrix, RowType rows, ColType cols)
     : m_matrix(matrix), m_rows(rows), m_cols(cols)
   {
           std::cout << "Debug: " << m_rows.minCoeff() << "," << m_rows.maxCoeff() << "," << matrix.rows() << std::endl;
           std::cout << "Debug: " << m_cols.minCoeff() << "," << m_cols.maxCoeff() << "," << matrix.cols() << std::endl;
     ei_assert(m_rows.minCoeff() >= 0 && m_rows.maxCoeff() < matrix.rows()
         && m_cols.minCoeff() >= 0 && m_cols.maxCoeff() < matrix.cols());
   }

   EIGEN_INHERIT_ASSIGNMENT_OPERATORS(Proxy)

   inline int rows() const { return m_rows.rows(); }
   inline int cols() const { return m_cols.rows(); }

   inline Scalar& coeffRef(int row, int col)
   {
     return m_matrix.const_cast_derived().coeffRef(m_rows(row), m_cols(col));
   }

   inline const Scalar coeff(int row, int col) const
   {
     return m_matrix.coeff(m_rows(row), m_cols(col));
   }

 protected:
   const typename MatrixType::Nested m_matrix;
   const typename RowType::Nested m_rows;
   const typename ColType::Nested m_cols;
};

template<typename MatrixType, typename RowType, typename ColType>
struct ei_traits<Proxy<MatrixType, RowType, ColType> >
{
 typedef typename MatrixType::Scalar Scalar;
 typedef typename ei_nested<MatrixType>::type MatrixTypeNested;
 typedef typename ei_unref<MatrixTypeNested>::type _MatrixTypeNested;
 enum {
   RowsAtCompileTime = (MatrixType::RowsAtCompileTime != Dynamic && RowType::RowsAtCompileTime != Dynamic) ?
                         int(RowType::RowsAtCompileTime)  : Dynamic,
   ColsAtCompileTime = (MatrixType::ColsAtCompileTime != Dynamic && ColType::RowsAtCompileTime != Dynamic) ?
                         int(ColType::RowsAtCompileTime)  : Dynamic,
   MaxRowsAtCompileTime = (MatrixType::MaxRowsAtCompileTime != Dynamic ) ?
                            int(RowType::MaxRowsAtCompileTime)  : Dynamic,
   MaxColsAtCompileTime = (MatrixType::MaxColsAtCompileTime != Dynamic ) ?
                            int(ColType::MaxRowsAtCompileTime)  : Dynamic,
   Flags = _MatrixTypeNested::Flags & HereditaryBits,
   CoeffReadCost = _MatrixTypeNested::CoeffReadCost
 };
};



Am 03.07.2009 um 09:55vorm. schrieb Gael Guennebaud:


On Fri, Jul 3, 2009 at 9:12 AM, Stefan Ulbrich<s.ulbrich@xxxxxxxxxxxxxx> wrote:
thanks for the answer. Actually, block / corner and minor don't allow you to
select the for example the columns 1,3,4,6. The closest thing I found in
Eigen is the Minor Class which allows to omit one row/col. I now tried to
clone and to modificate this class but haven't finished it yet (still
problems with io.h). This class will take two row-vectors that serve as a
proxy for the addressing of the coefficients (allows for permutations,
repetition of elements...). What do you think: do I have to worry about
speed using this method? Does the use of Minor result in lower speed?

Indeed this requires a novel _expression_ with a coeff(i,j) method which
looks like:

Scalar coeff(i,j) {
return m_matrix.coeff(m_rowIndices[i], m_colIndices[j]);
}

Assuming the compiler is able to move one of the m_*Indices[] out of
the inner evaluation loop, I guess this should be quite efficient.
Regarding API I think it'd be nice to be able to only provide a list
of rows or columns. To this end, the type of m_rowIndices could be a
template parameter for which the possibilities would be any kind of
integer vectors and a special class having n operator[i] always
returning "i". Regarding the names of these additional classes and
methods, I have no clue.


In the same way I want to create a class that connects matrix with the same
amount of rows (cols) to a single one.

yes it is quite easy to write such a matrix _expression_. Here is how
the coeff(i,j) method would look like:

Scalar coeff(i,j) {
if (j<m_matrixA.cols())
 return m_matrixA.coeff(i,j);
else
 return m_matrixB.coeff(i,j-m_matrixA.cols());
}

The problem is the "if" which will kill the performance (+ no
vectorization, etc.). So, if the memory consumption is not critical
for you I suggest you to copy the matrices to a single bigger one:

MatrixXf A, B, C;
/* ... */
A.resize(B.rows(), B.cols()+C.cols());
A << B, C;

This assignment is fast (vectorized), and then the use of A will be
fast too. Also note that this syntax is very powerful: it allows you
to assemble as many as scalars and/or matrix expressions as you want
in any direction. This is also why you have to set the size of the
matrix A manually. For instance you can  also assemble B and C
vertically:

A.resize(B.rows()+C.rows(), B.cols());
A << B, C;

See the tutorial for other more fancy examples.

cheers,
Gael



cheers,
stefan

Am 02.07.2009 um 19:46nachm. schrieb Thomas Capricelli:



Erm, all of this seems already available,have you read the tutorial
http://eigen.tuxfamily.org/dox/TutorialCore.html

especially the part
http://eigen.tuxfamily.org/dox/TutorialCore.html#TutorialCoreMatrixBlocks

Anything specific you think about that can not currently be done ?

regards,
Thomas

In data giovedì 02 luglio 2009 18:47:16, Stefan Ulbrich ha scritto:
: > Hello,

in matlab you can select a partial matrix by explicitly specifying
which cols and rows to take from the original matrix, e.g. with
A([1,3],[2 4])
further, it would be interesting to combine blocks into a matrix that
serves as a proxy to the elements: A = [B C]
I'd really would really like to see that functionality in eigen2. Do
you think this is possible? Do you have any suggestions on how to
start implementing it?

best regards,
Stefan

--
Thomas Capricelli <orzel@xxxxxxxxxxxxxxx>
http://www.freehackers.org/thomas















Mail converted by MHonArc 2.6.19+ http://listengine.tuxfamily.org/