Difference between revisions of "Eigen3 Developer Documentation"

From Eigen
Jump to: navigation, search
(Explanation of dense expression class hierarchy)
 
(Dense classes)
 
(10 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 
== Dense classes ==
 
== Dense classes ==
  
* '''Matrix''' means plain dense matrix. If m is a Matrix, then, for instance, m+m is no longer a matrix, it is a "matrix expression".
+
This section can now be found in the doxygen-generated documentation; see
* '''MatrixBase''' means dense matrix expression. This means that a MatrixBase is something that can be added, matrix-multiplied, LU-decomposed, QR-decomposed... All matrix expression classes, including Matrix itself, inherit MatrixBase.
+
* [http://eigen.tuxfamily.org/dox/TopicClassHierarchy.html docs for Eigen3]
* '''Array''' means plain dense array. If x is an Array, then, for instance, x+x is no longer an array, it is an "array expression".
+
* [http://eigen.tuxfamily.org/dox-devel/TopicClassHierarchy.html docs for development branch]
* '''ArrayBase''' means dense array expression. This means that an ArrayBase is something that can be added, array-multiplied, and on which you can perform all sorts of array operations... All array expression classes, including Array itself, inherit ArrayBase.
+
 
* '''DenseBase''' means dense (matrix or array) expression. Both ArrayBase and MatrixBase inherit DenseBase. DenseBase is where go all the methods that apply to dense expressions regardless of whether they are matrix or array expressions. For example, the block(...) methods are in DenseBase.
+
== Nested Expression Templates ==
* '''DenseStorageBase''' means dense (matrix or array) plain object, i.e. something that stores its own dense array of coefficients. This is where, for instance, the resize() methods go. DenseStorageBase is inherited by Matrix and by Array. But above, we said that Matrix inherited MatrixBase and Array inherited ArrayBase. So does that mean multiple inheritance? No, because DenseStorageBase _itself_ inherits MatrixBase or ArrayBase depending on whether we are in the matrix or array case. When we said above that Matrix inherited MatrixBase, we omitted to say it does so indirectly via DenseStorageBase. Same for Array.
+
 
* '''DenseCoeffsBase''' means something that has dense coefficient accessors. It is a base class for DenseBase. The reason for DenseCoeffsBase to exist is that the set of available coefficient accessors is very different depending on whether a dense expression has direct memory access or not (the DirectAccessBit flag). For example, if x is a plain matrix, then x has direct access, and x.transpose() and x.block(...) also have direct access, because their coeffs can be read right off memory, but for example, x+x does not have direct memory access, because obtaining any of its coefficients requires a computation (an addition), it can't be just read off memory.
+
<p style="border:solid;font-style:italic;font-size:2em;text-align:center;color: red;font-weight : bold;">This section is completely outdated. It needs to be rewritten to match with Eigen 3.3 internals.</p>
* '''EigenBase''' means anything that can be evaluated into a plain dense matrix or array (even if that would be a bad idea). EigenBase is really the absolute base class for anything that remotely looks like a matrix or array. It is a base class for DenseCoeffsBase, so it sits below all our dense class hierarchy, but it is not limited to dense expressions. For example, EigenBase is also inherited by diagonal matrices, sparse matrices, etc...
+
Example:
 +
<code> <br>
 +
    MatrixXf A, B, C; <br>
 +
    C = A.transpose() + B; <br>
 +
</code>
 +
 
 +
<code>A.transpose()</code> returns an object of type <code>Transpose<MatrixXf></code> where the <code>MatrixXf A</code> is nested into the Transpose expression. The nested type tells how to store the nested object. Here <code>MatrixXf::Nested</code> boils down to a <code>MatrixXf&</code>, and thus "A.transpose()" stores a reference to A.
 +
 
 +
There are two main reasons we introduced such a nesting type mechanism and not always use a reference:
 +
 
 +
'''[I]''' Expressions other than Matrix or Array are lightweight and better nested by value. In the previous example, <code>A.transpose() + B</code> returns an object of type <code> CwiseBinaryOp<ei_scalar_sum_op<float>, Transpose<MatrixXf>, MatrixXf> </code>  storing both sides of the addition as follows:<br>
 +
<code>
 +
    Transpose<MatrixXf>::Nested lhs; // left hand side <br>
 +
    MatrixXf::Nested rhs; // right hand side
 +
</code><br> which boils down to:<br>
 +
<code>
 +
    const Transpose<MatrixXf> lhs; // nesting by value <br>
 +
    const MatrixXf& rhs; // nesting by reference <br>
 +
</code><br>
 +
Nesting by value small object avoids temporary headache when a function has to return complex expressions, e.g.:<br>
 +
<code>
 +
    template<typename A, typename B> <br>
 +
    CwiseBinaryOp<ei_scalar_sum_op<float>, Transpose<A>, B> <br>
 +
    adjoint(const A& a, const B& b) <br>
 +
    { <br>
 +
      return a.transpose() + b; <br>
 +
    } <br>
 +
</code> <br>
 +
If the temporary "a.transpose()" was stored by reference by the <code>CwiseBinaryOp expression</code>, then you would end up with a segfault because the "a.transpose()" temporary is destroyed just before the function returns, and so the <code> CwiseBinaryOp </code> expression would store a reference to dead object.
 +
 
 +
'''[II]''' Some expressions must be evaluated into temporaries before being used. For instance, in the following example:<code>d = a * b + c;</code> for performance reason, the matrix product <code>a * b</code> has to be evaluated into a temporary before evaluating the addition. This is achieved as follows. Here we build the expression of type:<br>
 +
<code>
 +
    CwiseBinaryOp<ei_scalar_sum_op<float>, Product<MatrixXf,MatrixXf>, MatrixXf>
 +
</code><br>
 +
which stores a <code>Product<MatrixXf,MatrixXf>::Nested</code> for the lhs, and <code>Product<MatrixXf,MatrixXf>::Nested</code> is ... <code>MatrixXf</code> !!
 +
 
 +
 
 +
Something more complicated:<br>
 +
<code>
 +
    (a + b) * c <br>
 +
</code><br>
 +
Here, if c is not too small, it is better to evaluate (a+b) into a temporary before doing the matrix product, otherwise, a+b would be computed c.cols() times. To this end we have a <code>ei_nested<></code> helper class to determine the ideal nesting type. In Product, we have something like:<br>
 +
<code>
 +
    ei_nested<CwiseBinaryOp<ei_scalar_sum_op<float>, type_of_a, type_of_b>, type_of_c::ColsAtCompileTime>::type
 +
</code> <br>
 +
giving us the nesting type of the left hand side of the product (here a <code>MatrixXf</code> if a, b, and c are <code>MatrixXf</code>). For the right hand side here we have: <br>
 +
<code>
 +
    ei_nested<CwiseBinaryOp<ei_scalar_sum_op<float>, type_of_c, type_of_a_plus_b::RowsAtCompileTime>::type <br>
 +
</code> <br>
 +
which gives us a <code>MatrixXf&</code>.
 +
 
 +
 
 +
<code> ei_nested<> </code> determines whether the nested expression has to be evaluated or not in function of an estimation of the evaluation cost of one coefficient. This cost is automatically computed by the expressions in the <code> ei_traits<> </code> specializations.
 +
 
 +
'''Very important:''' When you write a generic function taking, e.g., a <code> MatrixBase<Derived> </code> object you should really honor the nesting type of the Derived class:<br>
 +
<code>
 +
    template<typename Derived> <br>
 +
    void foo(const MatrixBase<Derived>& _x) <br>
 +
    { <br>
 +
      typename Derived::Nested x(_x.derived()); <br>
 +
    <br>
 +
      // use x safely <br>
 +
    } <br>
 +
</code><br>
 +
 
 +
Actually, if you use the argument more than once, you should even use the <code> ei_nested<> </code> helper:<br>
 +
<code>
 +
    template<typename Derived> </br>
 +
    typename Derived::Scalar foo(const MatrixBase<Derived>& _x) <br>
 +
    { <br>
 +
      typename ei_nested<Derived,2>::type x(_x.derived()); <br>
 +
    <br>
 +
      return (x + x.adjoint()).maxCoeff(); <br>
 +
    }<br>
 +
</code> <br>
 +
If you don't do so, and call <code> foo(a*b); </code> then the expensive product a*b will be computed twice !!

Latest revision as of 09:01, 30 September 2015

Dense classes

This section can now be found in the doxygen-generated documentation; see

Nested Expression Templates

This section is completely outdated. It needs to be rewritten to match with Eigen 3.3 internals.

Example:

   MatrixXf A, B, C; 
C = A.transpose() + B;

A.transpose() returns an object of type Transpose<MatrixXf> where the MatrixXf A is nested into the Transpose expression. The nested type tells how to store the nested object. Here MatrixXf::Nested boils down to a MatrixXf&, and thus "A.transpose()" stores a reference to A.

There are two main reasons we introduced such a nesting type mechanism and not always use a reference:

[I] Expressions other than Matrix or Array are lightweight and better nested by value. In the previous example, A.transpose() + B returns an object of type CwiseBinaryOp<ei_scalar_sum_op<float>, Transpose<MatrixXf>, MatrixXf> storing both sides of the addition as follows:

   Transpose<MatrixXf>::Nested lhs; // left hand side 
MatrixXf::Nested rhs; // right hand side


which boils down to:

   const Transpose<MatrixXf> lhs; // nesting by value 
const MatrixXf& rhs; // nesting by reference


Nesting by value small object avoids temporary headache when a function has to return complex expressions, e.g.:

   template<typename A, typename B> 
CwiseBinaryOp<ei_scalar_sum_op<float>, Transpose<A>, B>
adjoint(const A& a, const B& b)
{
return a.transpose() + b;
}


If the temporary "a.transpose()" was stored by reference by the CwiseBinaryOp expression, then you would end up with a segfault because the "a.transpose()" temporary is destroyed just before the function returns, and so the CwiseBinaryOp expression would store a reference to dead object.

[II] Some expressions must be evaluated into temporaries before being used. For instance, in the following example:d = a * b + c; for performance reason, the matrix product a * b has to be evaluated into a temporary before evaluating the addition. This is achieved as follows. Here we build the expression of type:

   CwiseBinaryOp<ei_scalar_sum_op<float>, Product<MatrixXf,MatrixXf>, MatrixXf>


which stores a Product<MatrixXf,MatrixXf>::Nested for the lhs, and Product<MatrixXf,MatrixXf>::Nested is ... MatrixXf !!


Something more complicated:

   (a + b) * c 


Here, if c is not too small, it is better to evaluate (a+b) into a temporary before doing the matrix product, otherwise, a+b would be computed c.cols() times. To this end we have a ei_nested<> helper class to determine the ideal nesting type. In Product, we have something like:

   ei_nested<CwiseBinaryOp<ei_scalar_sum_op<float>, type_of_a, type_of_b>, type_of_c::ColsAtCompileTime>::type


giving us the nesting type of the left hand side of the product (here a MatrixXf if a, b, and c are MatrixXf). For the right hand side here we have:

   ei_nested<CwiseBinaryOp<ei_scalar_sum_op<float>, type_of_c, type_of_a_plus_b::RowsAtCompileTime>::type 


which gives us a MatrixXf&.


ei_nested<> determines whether the nested expression has to be evaluated or not in function of an estimation of the evaluation cost of one coefficient. This cost is automatically computed by the expressions in the ei_traits<> specializations.

Very important: When you write a generic function taking, e.g., a MatrixBase<Derived> object you should really honor the nesting type of the Derived class:

   template<typename Derived> 
void foo(const MatrixBase<Derived>& _x)
{
typename Derived::Nested x(_x.derived());

// use x safely
}


Actually, if you use the argument more than once, you should even use the ei_nested<> helper:

   template<typename Derived> </br>
   typename Derived::Scalar foo(const MatrixBase<Derived>& _x) 
{
typename ei_nested<Derived,2>::type x(_x.derived());

return (x + x.adjoint()).maxCoeff();
}


If you don't do so, and call foo(a*b); then the expensive product a*b will be computed twice !!