Eigen 版本 3.2.0 和 3.3.4 之间的数值差异

Numerical differences between Eigen version 3.2.0 and 3.3.4

我最近推出了一个使用一些基本矢量数学和 SelfAdjointEigenSolverEigen 3.2.0 到 3.3.4 的代码。该代码是用 Matlab 制作的原型,它与 3.2.0 一致。滚动到 3.3.4 的动机是为了避免 C++11(及更新版本)和 3.2.0 出现的一些警告和错误。

代码通过将数千个点读入 Eigen::Matrix<double, Eigen::Dynamic, 3> 来工作。当我使用 gdb 检查值时,输入值是相同的。下面显示了一段显示数值差异的代码。 pilType 定义为 double.

// this is the point cloud, center it about the origin
Eigen::Matrix<pilType, 1, 3> center = gridPnts.colwise().mean();
gridPnts.rowwise() -= center;
// Get the transformation matrix to align the point cloud with it's normal
// Build the co variance matrix
Eigen::Matrix<double, 3, 3> S = gridPnts.transpose() * gridPnts;
S /= static_cast<pilType>(gridPnts.rows() - 1);
Eigen::SelfAdjointEigenSolver<pilMat3> es(S);
Eigen::Matrix<pilType, 3, 2> trans;
trans = es.eigenvectors().block<3, 2>(0, 1);
// convert the point cloud to 2D for Convex Hull calculation
Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> output(gridPnts.rows(), 2);
output = gridPnts * trans;

我正在使用 GCC 5.3.0 编译新版本,标记 -std=c++11 -O3 -DEIGEN_NO_DEBUG -Wall -Wextra -Werror -march=native -mtune=native。我尝试过使用和不使用 march 和 mtune 选项。旧版本是使用 GCC 4.3.4 和标志 -O3 -DEIGEN_NO_DEBUG -Wall -Wextra -Werror 编译的。 CPU 是 SandyBridge E5-2670 运行ning SuSE Enterprise SLES 11。差异在 S 除以点数后开始 - 1。它们非常小,在1e-12 的顺序,但是当涉及到特征解时,非常接近 0 的值会导致特征向量中的符号发生变化。这会导致变换矩阵针对一个轴进行切换。生成的输出沿该轴翻转。将生成的 2D 补丁发送到例程以查找凸包。由于轴翻转,返回相同的点但顺序相反。这不是错误,但出乎意料。

事实上,在整个应用程序中,各种数字在 1e-12 到 1e-15 范围内显示出差异。

以下是 运行 使用 GDB 时的一些值(最高值来自版本 3.3.4,较低的值来自 3.2.0)

S 除法前:

    Eigen::Matrix<double,3,3,ColMajor> (data ptr: 0x7fffffffb0b0) = {[0,0] = 3532221.7020642869,
  [1,0] = 1.0913936421275139e-11, [2,0] = 3332.4628071428679, [0,1] = 1.0913936421275139e-11,
  [1,1] = 335265.83999999962, [2,1] = -1.3073986337985843e-12, [0,2] = 3332.4628071428679,
  [1,2] = -1.3073986337985843e-12, [2,2] = 4697.4509785714226}

Eigen::Matrix<double,3,3,ColMajor> (data ptr: 0x7fffffffb8f0) = {[0,0] = 3532221.7020642869,
  [1,0] = 1.0913936421275139e-11, [2,0] = 3332.4628071428679, [0,1] = 1.0913936421275139e-11,
  [1,1] = 335265.83999999962, [2,1] = -1.3073986337985843e-12, [0,2] = 3332.4628071428679,
  [1,2] = -1.3073986337985843e-12, [2,2] = 4697.4509785714226}

S 除法后

Eigen::Matrix<double,3,3,ColMajor> (data ptr: 0x7fffffffb0b0) = {[0,0] = 21151.028156073575,
  [1,0] = 6.5352912702246338e-14, [2,0] = 19.954867108639927, [0,1] = 6.5352912702246338e-14,
  [1,1] = 2007.5798802395186, [2,1] = -7.8287343341232597e-15, [0,2] = 19.954867108639927,
  [1,2] = -7.8287343341232597e-15, [2,2] = 28.128448973481571}

Eigen::Matrix<double,3,3,ColMajor> (data ptr: 0x7fffffffb8f0) = {[0,0] = 21151.028156073575,
  [1,0] = 6.5352912702246351e-14, [2,0] = 19.954867108639927, [0,1] = 6.5352912702246351e-14,
  [1,1] = 2007.5798802395188, [2,1] = -7.8287343341232597e-15, [0,2] = 19.954867108639927,
  [1,2] = -7.8287343341232597e-15, [2,2] = 28.128448973481575}

执行特征解后 trans 的值为:

{[0,0] = 3.4096942364292876e-18, [1,0] = -1,
  [2,0] = 3.9893751393767394e-18, [0,1] = 0.99999955376919869, [1,1] = 3.4134614846087836e-18,
  [2,1] = 0.00094470175363752104}

{[0,0] = 0, [1,0] = 1,
  [2,0] = -3.2750362278258563e-15, [0,1] = 0.9999995537691988, [1,1] = 3.093932467653499e-18,
  [2,1] = 0.00094470175363752125}

不可否认,数值差异非常小,并且在准确度范围内。在显示的数字中,变换矩阵的符号翻转导致 2D 云翻转。有没有办法清理所做的事情以消除这些差异?我见过一些 Eigen 具有小值阈值的函数,但我没有看到一个用于除法的函数。

您根本不应依赖特征向量上的符号(无论求解器和库如何),而是根据您的需要调整它们。例如,如果你想确保有一个右手基础,那么用前两个的叉积更新最后一个。

Eigen 使用 version 3.2.7 计算除以标量的方式发生了变化。

operator/=(Scalar) now performs a true division (instead of mat*(1/s))

使用 S *= 1.0 / (static_cast<pilType>(gridPnts.rows() - 1)); 测试代码显示数值差异已解决。