Eigen入門之密集矩陣 7 - Map class:連接Eigen與C++的數據

簡介

本文介紹一下Dense Matrix如何與c/C++的數組進行交互操作,這在引入其他的庫中的vector向量和矩陣到Eigen中時要使用到的技術。

有時,你有一些定義好的數據,可能是數組,你需要在Eigen內使用它。一個可選的方法是你拷貝一份數據,在添加到Eigen中,這樣會有些工程的問題,數據的一致性等問題。幸運的是,Eigen內爲此提供了Map類,提供了便利的使用方法。

Map在Eigen的四元數quarternion、排列permutation等都有應用。

Map類型及變量

在Eigen內,Map對象的模板類型定義如下:

Map<Matrix<typename Scalar, int RowsAtCompileTime, int ColsAtCompileTime> >

構造器

首先,需要了解的一點是: Map沒有缺省的構造器。這裏使用示例來介紹Map的變量定義/構造方法。

要構造一個Map變量,需要提供另外的2個其他的信息給Eigen: 想要得到的具體的矩陣或者向量、一個指向存儲了係數的數組的地址指針。比如,要定義一個編譯時即知道尺寸大小的浮點矩陣,其中pf是一個指向浮點類型數組的指針。可以這樣做:

Map<MatrixXf> mf(pf,rows,columns);

一個固定尺寸大小的只讀整形向量的Map類型,可以通過一個int * p指針,通過構造器,這樣定義:
Map<const Vector4i> mi(pi);
在這裏,看起來沒有傳遞給構造器尺寸大小,這是因爲已經在模板參數Vector4i中隱含指定了。

類Map提供了足夠的靈活性,以滿足多種數據形式。這裏提供了有2個模板參數的定義:

Map<typename MatrixType,
    int MapOptions,
    typename StrideType>

這裏簡單介紹一下:

  • MapOptions: 指定指針是否對齊(Aligned),或者不對齊(Unaligned);缺省值爲不對齊(Unaligned)。
  • StrideType: 讓使用者指定內存中數組的佈局,類型爲class Stride。下面的例子指定其數據數組使用行優先(row-major)格式。

示例:

//matrix_map1.cpp
#include <iostream>
#include <Eigen/Dense>

using namespace std;
using namespace Eigen;

int main()
{
    int array[9];
    for(int i = 0; i < 9; ++i) array[i] = i;

    Map<VectorXi>  vi(array,9);
    Map<const Vector4i> fixed_v(array);    // unknown type name 'Vector5i' -- 這種模式size不能大於4
    cout<< "vector vi: "<< endl << vi << endl<< endl;
    cout<< "fixed-vector : "<< endl << fixed_v << endl<< endl;
    
    cout << "Column-major:\n" << Map<Matrix<int,2,4> >(array) << endl;
    
    cout << "Row-major:\n" << Map<Matrix<int,2,4,RowMajor> >(array) << endl;
    
    cout << "Row-major using stride:\n" <<
        Map<Matrix<int,2,4>, Unaligned, Stride<1,4> >(array) << endl;
}

執行結果:

$ g++   -I /usr/local/include/eigen3 matrix_map1.cpp -o matrix_map1
$ ./matrix_map1
vector vi: 
0
1
2
3
4
5
6
7
8

fixed-vector : 
0
1
2
3

Column-major:
0 2 4 6
1 3 5 7
Row-major:
0 1 2 3
4 5 6 7
Row-major using stride:
0 1 2 3
4 5 6 7

從結果可以看到,Map()進行映射時,缺省時列有先column-major,或者安裝Eigen中的設定執行。

其實, Stride提供了更大的靈活性,具體信息可以查詢Map和Stride的說明文檔。

使用Map變量

對Map變量的使用,就和其映射的類型一致。比如Map到一個矩陣,則和使用矩陣對象一樣。如果是向量,則按照向量的類型進行訪問。

直接看Eigen中的示例。

//matrix_map2.cpp
#include <iostream>
#include <Eigen/Dense>

using namespace std;
using namespace Eigen;

typedef Matrix<float,1,Dynamic> MatrixType;
typedef Map<MatrixType> MapType;
typedef Map<const MatrixType> MapTypeConst;   // a read-only map
const int n_dims = 5;

int main()
{
    MatrixType m1(n_dims), m2(n_dims);
    m1.setRandom();
    m2.setRandom();

    float *p = &m2(0);  // get the address storing the data for m2
    MapType m2map(p,m2.size());   // m2map shares data with m2

    MapTypeConst m2mapconst(p,m2.size());  // a read-only accessor for m2
    cout << "m1: " << m1 << endl;
    cout << "m2: " << m2 << endl;
    cout << "Squared euclidean distance: " << (m1-m2).squaredNorm() << endl;
    cout << "Squared euclidean distance, using map: " << (m1-m2map).squaredNorm() << endl;
    
    m2map(3) = 7;   // this will change m2, since they share the same array
    cout << "Updated m2: " << m2 << endl;
    cout << "m2 coefficient 2, constant accessor: " << m2mapconst(2) << endl;
    
    //m2mapconst(2) = 5;    // compile-time error: expression is not assignable
    cout << endl;

}

執行結果:

$ g++   -I /usr/local/include/eigen3 matrix_map2.cpp -o matrix_map2
$ 
$ ./matrix_map2
m1:  -0.999984  -0.736924   0.511211 -0.0826997  0.0655345
m2: -0.562082 -0.905911  0.357729  0.358593  0.869386
Squared euclidean distance: 1.08479
Squared euclidean distance, using map: 1.08479
Updated m2: -0.562082 -0.905911  0.357729         7  0.869386
m2 coefficient 2, constant accessor: 0.357729

可以看到,對係數的訪問,使用括號運算符(int index),矩陣的一些計算保持不變。
但這裏有一個限制:你自己定義的函數,使用Eigen的類型時,如果使用了Map,這和其對應的密集矩陣類Matrix可不一致了,因爲它們是不同的類型Class。

使用placement new修改映射的數組

Map映射後得到的Matrix或者vector對象,也可以被修改。但這需要使用C++的placement new語法。

這裏先對C++的Placemet new做一下說明,這涉及到3種不同的new: new、operator new、placement new

placement new是在用戶指定的內存位置上構建新的對象,那麼這樣構建時,就不需要再額外分配內存空間,只需要調用對象的構造函數。就是這樣!因爲內存和數據都已經在那裏了。其語法如此這樣:new(&p) constructor(......)

下面看Eigen內的一下Map相關的示例:

//matrix_map3.cpp
#include <iostream>
#include <Eigen/Dense>

using namespace std;
using namespace Eigen;

int main()
{
    int data[] = {1,2,3,4,5,6,7,8,9};
    Map<RowVectorXi> ov(data,9);
    cout << "Origin data: " << ov << endl; 

    Map<RowVectorXi> v(data,4);
    cout << "The mapped vector v is: " << v << "\n";
    
    // placement new 
    new (&v) Map<RowVectorXi>(data+4,5);
    
    cout << "Changed, Now v is: " << v << "\n";

    cout << "Again origin data: " << ov << endl; 
}

執行結果:

$ g++   -I /usr/local/include/eigen3 matrix_map3.cpp -o matrix_map3
promote:eigen david$ ./matrix_map3
Origin data: 1 2 3 4 5 6 7 8 9
The mapped vector v is: 1 2 3 4
Changed, Now v is: 5 6 7 8 9
Again origin data: 1 2 3 4 5 6 7 8 9

使用這種placement new語法,可以聲明一個Map對象,而不用先分配內存,也無需知道具體真實的數據。
比如:

//matrix_map4.cpp
#include <iostream>
#include <Eigen/Dense>

using namespace std;
using namespace Eigen;

int n_matrices = 3;
int data[] = {1,2,3,4,5,6,7,8,9};

int* get_matrix_pointer(int i) 
{
    return data+3*i;
}

int main()
{
    Map<Matrix3i> A(NULL);  // // 此時對數據一無所知

    VectorXi b(n_matrices);

    for (int i = 0; i < n_matrices; i++)
    {
        int * pp = get_matrix_pointer(i);
        cout << "point : " << pp << " : data " << *pp << endl;
        //
        new (&A) Map<Matrix3i>( pp );
        cout<< "Matrix A: "<< endl<<A<<endl;
        b(i) = A.trace();
    }

    cout<< "vector b : "<< endl<<b<<endl;
}

執行結果:

$ g++   -I /usr/local/include/eigen3 matrix_map4.cpp -o matrix_map4
$ ./matrix_map4
point : 0x106cb7880 : data 1
Matrix A: 
1 4 7
2 5 8
3 6 9
point : 0x106cb788c : data 4
Matrix A: 
4 7 0
5 8 0
6 9 0
point : 0x106cb7898 : data 7
Matrix A: 
7 0 0
8 0 0
9 0 0
vector b : 
15
12
 7
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章