Eigen學習筆記(10)-混淆

原文:Eigen官網-Aliasing

在Eigen中,當變量同時出現在左值和右值,賦值操作可能會帶來混淆問題。比如:mat = 2 * matmat = mat.transpose() ,對於第一個表達式,是無害的,但是第二個表達式則會造成意想不到的後果。這一篇將解釋什麼是混淆,什麼時候是有害的,怎麼使用做。

1. 舉例

如下是一個存在混淆的簡單例子:

MatrixXi mat(3,3); 
mat << 1, 2, 3,   4, 5, 6,   7, 8, 9;
cout << "Here is the matrix mat:\n" << mat << endl;
// This assignment shows the aliasing problem
mat.bottomRightCorner(2,2) = mat.topLeftCorner(2,2);
cout << "After the assignment, mat = \n" << mat << endl;

結果如下:

Here is the matrix mat:
1 2 3
4 5 6
7 8 9
After the assignment, mat = 
1 2 3
4 1 2
7 4 1

出現上述混淆的原因是:Eigen使用了lazy evaluation

一般來說,混淆在編譯階段很難被檢測到。比如第一個例子,如果mat再大一些可能就不會出現混淆了。但是Eigen可以在運行時檢測某些混淆,如前面講的例子。

2. 解決混淆問題

Eigen需要把右值賦值爲一個臨時matrix/array,然後再將臨時值賦值給左值,便可以解決混淆。eval()函數實現了這個功能。

如下是對第一個例子的修正:

MatrixXi mat(3,3); 
mat << 1, 2, 3,   4, 5, 6,   7, 8, 9;
cout << "Here is the matrix mat:\n" << mat << endl;
// The eval() solves the aliasing problem
mat.bottomRightCorner(2,2) = mat.topLeftCorner(2,2).eval();
cout << "After the assignment, mat = \n" << mat << endl;

結果如下:

Here is the matrix mat:
1 2 3
4 5 6
7 8 9
After the assignment, mat = 
1 2 3
4 1 2
7 4 5

對於a = a.transpose(), 可以使用a = a.transpose().eval()進行替換,另外也可以使用transposeInPlace()來替換transpose()函數,以解決混淆問題。

如果存在xxxInPlace()函數,推薦使用這類函數,它們更加清晰地標明瞭你在做什麼。如下是提供的這類函數:

Original function In-place function
MatrixBase::adjoint() MatrixBase::adjointInPlace()
DenseBase::reverse() DenseBase::reverseInPlace()
LDLT::solve() LDLT::solveInPlace()
LLT::solve() LLT::solveInPlace()
TriangularView::solve() TriangularView::solveInPlace()
DenseBase::transpose() DenseBase::transposeInPlace()

而針對vec = vec.head(n)這種情況,推薦使用conservativeResize()

3. 混淆和component級的操作

對於元素級的操作(比如matrix加法、scalar乘、array乘等)是安全的,不會出現混淆的問題。
示例如下:

MatrixXf mat(2,2); 
mat << 1, 2,  4, 7;
cout << "Here is the matrix mat:\n" << mat << endl << endl;
mat = 2 * mat;
cout << "After 'mat = 2 * mat', mat = \n" << mat << endl << endl;
mat = mat - MatrixXf::Identity(2,2);
cout << "After the subtraction, it becomes\n" << mat << endl << endl;
ArrayXXf arr = mat;
arr = arr.square();
cout << "After squaring, it becomes\n" << arr << endl << endl;
// Combining all operations in one statement:
mat << 1, 2,  4, 7;
mat = (2 * mat - MatrixXf::Identity(2,2)).array().square();
cout << "Doing everything at once yields\n" << mat << endl << endl;

結果如下:

Here is the matrix mat:
1 2
4 7

After 'mat = 2 * mat', mat = 
 2  4
 8 14

After the subtraction, it becomes
 1  4
 8 13

After squaring, it becomes
  1  16
 64 169

Doing everything at once yields
  1  16
 64 169

4. 混淆和矩陣的乘法

在假設目標矩陣的尺寸不變的情況下,矩陣乘法是Eigen中唯一默認會出現混淆的操作。因此,對於方陣,matA = matA * matA是安全的。因爲Eigen默認進行了混淆問題的解決。

對於Eigen中的其他所有操作,Eigen默認是不存在混淆問題的,或者是因爲目標矩陣變成了具有不同尺寸大小的矩陣,或者是元素級的操作。因此Eigen沒有設置進行默認混淆問題解決的操作。

示例如下:

// 單位陣
MatrixXf matA(2,2); 
matA << 2, 0,  0, 2;
matA = matA * matA;
cout << matA;

結果如下:

4 0
0 4

但是Eigen中默認對矩乘法中混淆問題的解決也是有一些問題的。Eigen對矩陣乘法自動引入了臨時變量,對的matA=matA*matA這是必須的,但是對matB=matA*matA這樣便是不必要的了。我們可以使用noalias()函數來聲明這裏沒有混淆,matA*matA的結果可以直接賦值爲matB

從Eigen3.3開始,如果目標矩陣resize且結果不直接賦值給目標矩陣,默認不存在混淆。

當然,對於任何混淆問題,都可以通過matA=(matB*matA).eval()在賦值之前顯示求表達式的值來解決。

5. 總結

當同一個matrix或array在等號左右都出現時,很容易出現混淆。

  • compnent級別的操作不存在混淆問題,比如標量乘法、matrix加法、array加法。
  • 矩陣相乘,Eigen默認會解決混淆問題,如果你確定不會出現混淆,可以使用noalias()來提高效率。
  • 在其他情況中,Eigen假設不存在混淆問題,因此當混淆出現時,Eigen會給出錯誤的結果,但是可以用eval()或者xxxInPlace()函數解決。

參考:

“Eigen教程(10)”

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章