Skip to content

Commit 75eda45

Browse files
committed
Fix eigenvector sign ambiguity in Eigen SVD PCA implementation
The switch from VNL's vnl_symmetric_eigensystem to Eigen's BDCSVD introduced test failures due to eigenvector sign ambiguity. In PCA, eigenvectors are only determined up to a sign (both v and -v are valid), and different libraries may return either arbitrarily. Add a consistent sign convention: make the first element of each eigenvector positive. This ensures deterministic results regardless of the underlying linear algebra implementation. Update test baselines to match the new consistent sign convention. This way if we switch to another implementation later, we can keep the results consistent.
1 parent e9d84e9 commit 75eda45

File tree

5 files changed

+405
-393
lines changed

5 files changed

+405
-393
lines changed

Libs/Particles/ParticleShapeStatistics.cpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -302,9 +302,13 @@ int ParticleShapeStatistics::compute_shape_dev_modes_for_mca() {
302302
// Compute eigenvectors_ = X * V = U * S (matches old behavior before normalization)
303303
eigenvectors_shape_dev_ = svd.matrixU() * svd.singularValues().asDiagonal();
304304

305-
// normalize the eigenvectors
305+
// normalize the eigenvectors and enforce sign convention
306+
// (make first element positive to match VNL eigensystem convention)
306307
for (int i = 0; i < eigenvectors_shape_dev_.cols(); i++) {
307308
eigenvectors_shape_dev_.col(i).normalize();
309+
if (eigenvectors_shape_dev_(0, i) < 0) {
310+
eigenvectors_shape_dev_.col(i) *= -1;
311+
}
308312
}
309313

310314
// SVD returns values in descending order, but we need ascending order for backward compatibility
@@ -330,9 +334,13 @@ int ParticleShapeStatistics::compute_relative_pose_modes_for_mca() {
330334
// Compute eigenvectors_ = X * V = U * S (matches old behavior before normalization)
331335
eigenvectors_rel_pose_ = svd.matrixU() * svd.singularValues().asDiagonal();
332336

333-
// normalize the eigenvectors
337+
// normalize the eigenvectors and enforce sign convention
338+
// (make first element positive to match VNL eigensystem convention)
334339
for (int i = 0; i < eigenvectors_rel_pose_.cols(); i++) {
335340
eigenvectors_rel_pose_.col(i).normalize();
341+
if (eigenvectors_rel_pose_(0, i) < 0) {
342+
eigenvectors_rel_pose_.col(i) *= -1;
343+
}
336344
}
337345

338346
// SVD returns values in descending order, but we need ascending order for backward compatibility
@@ -592,9 +600,13 @@ int ParticleShapeStatistics::compute_modes() {
592600
// Compute eigenvectors_ = X * V = U * S (matches old behavior before normalization)
593601
eigenvectors_ = svd.matrixU() * svd.singularValues().asDiagonal();
594602

595-
// normalize the eigenvectors
603+
// normalize the eigenvectors and enforce sign convention
604+
// (make first element positive to match VNL eigensystem convention)
596605
for (int i = 0; i < eigenvectors_.cols(); i++) {
597606
eigenvectors_.col(i).normalize();
607+
if (eigenvectors_(0, i) < 0) {
608+
eigenvectors_.col(i) *= -1;
609+
}
598610
}
599611

600612
// SVD returns values in descending order, but we need ascending order for backward compatibility
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
version https://git-lfs.github.com/spec/v1
2-
oid sha256:a40f757b38409c3bff3ff3f15acda4597a695889e83f4c29e4b76183f7688768
3-
size 56666
2+
oid sha256:8289ef4063092ae299ae7fbcaaf673884661c6493afe5f3506f56c132525f565
3+
size 73323
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
version https://git-lfs.github.com/spec/v1
2-
oid sha256:547770a438f1b239103719dc98d54aeb289c665017a37e3098266054b4050b5a
3-
size 56666
2+
oid sha256:5af6e305291ee6b7790399d499a4594af087fe7b9f6584a55bfba8f820a9b992
3+
size 72872
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
version https://git-lfs.github.com/spec/v1
2-
oid sha256:f1ffb527bd7116b6e9378d40dcbe55ac356eedf3e604cc63ed4a9464696ce8f3
3-
size 56666
2+
oid sha256:85d9be22f479b528dd602d555582d6948cfefb03aacdfb79177406eeacdb316b
3+
size 72770

0 commit comments

Comments
 (0)