我试图用线性代数c ++库Eigen3得到一个随机对称矩阵。我是这样做的:
Eigen::MatrixXd m(3, 3);
m.setRandom();
m = 0.5 * (m + m.transpose());
但是这种结果是完全错误的。但是,如果我不会重写m变量,只需将其输出到控制台,如下所示:
Eigen::MatrixXd m(3, 3);
m.setRandom();
cout << 0.5 * (m + m.transpose()) << endl;
一切似乎都正常。我无法理解问题出在哪里。是因为转换和像*和+这样的操作之类的方法不会立即创建一个新矩阵,而是以一种懒惰的方式进行并保持对矩阵m的引用?但是,我应该如何从官方文档中了解它呢?并非像这样的行为极其容易出错吗?
更新:
是的,我认为我对懒惰计算的猜测是正确的。在transpose
方法的文档化中提到了它:
/** \returns an expression of the transpose of *this.
*
* Example: \include MatrixBase_transpose.cpp
* Output: \verbinclude MatrixBase_transpose.out
*
* \warning If you want to replace a matrix by its own transpose, do \b NOT do this:
* \code
* m = m.transpose(); // bug!!! caused by aliasing effect
* \endcode
* Instead, use the transposeInPlace() method:
* \code
* m.transposeInPlace();
* \endcode
* which gives Eigen good opportunities for optimization, or alternatively you can also do:
* \code
* m = m.transpose().eval();
* \endcode
*
* \sa transposeInPlace(), adjoint() */
所以现在我想知道在执行长链计算时我应该使用哪些模式?在哪里写.eval()?说实话,它很丑陋而且容易出错。
答案 0 :(得分:3)
“我认为我对懒惰计算的猜测是正确的。”
是的,你是对的。描述了惰性评估的规则here。我已经提取了以下几点:
对于每个子表达式,Eigen自动确定是否将其评估为临时变量。 [...]
基于表达式模板的库可以避免将子表达式评估为临时表,这在很多情况下会导致大的速度提升。这称为延迟评估,因为表达式会尽可能晚地进行评估,而不是立即进行评估。但是,大多数其他基于表达模板的库总是选择延迟评估。这有两个问题:首先,懒惰评估并不总是性能的好选择;第二,懒惰的评估可能非常危险,例如使用矩阵产品:如果矩阵乘积是惰性评估的话,做
matrix = matrix*matrix
会产生错误的结果,因为矩阵产品的工作方式。
“所以现在我想知道在执行长链计算时我应该使用哪些模式?”
通常,表达式模板应该解决是否立即/懒惰地评估的问题,但是正如您所指出的,有时他们找不到所有边缘情况,matrix.transpose()
似乎是一个。您可以添加.eval()
和.noalias()
来强制进行延迟或即时评估,否则将选择其他评估:
强制延迟评估:matrix1.noalias() = matrix2 * matrix2;
延迟评估没问题 - 在matrix1和matrix2之间没有别名
强制立即评估:matrix1 = matrix1.transpose().eval()
懒惰评估不正常
对于转置案例,只需添加.eval()
:
m = 0.5 * (m + m.transpose()).eval();
答案 1 :(得分:1)
pingul的答案描述了为什么你的解决方案失败了,我只想添加你可以做的,如果你想避免临时的。基本上,诀窍是将表达式分配给矩阵的一半三角形:
#include <iostream>
#include <Eigen/Core>
int main() {
Eigen::Matrix4f M;
M.setRandom();
std::cout << "M:\n" << M << '\n';
// for comparison, evaluate into other matrix (similar to using .eval()):
Eigen::Matrix4f M2 = 0.5f*(M+M.transpose());
// in place operation, only assing to lower half, then to upper half:
M.triangularView<Eigen::StrictlyLower>() = 0.5f*(M + M.transpose());
M.triangularView<Eigen::StrictlyUpper>() = M.transpose();
std::cout << "M2:\n" << M2 << "\nM:\n" << M << '\n';
}
事实上,如果你只想要一个“随机对称矩阵”而不一定是给定矩阵M
的对称部分,你可以简单地将上部复制到下部(或者反之) :
M.triangularView<Eigen::StrictlyLower>() = M.transpose();