Skip to content

Commit e9d84e9

Browse files
committed
Apply Eigen approach to MLCA functions as well.
1 parent fc9d407 commit e9d84e9

File tree

1 file changed

+47
-49
lines changed

1 file changed

+47
-49
lines changed

Libs/Particles/ParticleShapeStatistics.cpp

Lines changed: 47 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
#include <Logging.h>
55
#include <Particles/ParticleFile.h>
66
#include <Project/Project.h>
7-
#include <vnl/algo/vnl_symmetric_eigensystem.h>
87

98
#include "ExternalLibs/tinyxml/tinyxml.h"
109
#include "Profiling.h"
@@ -292,62 +291,57 @@ void ParticleShapeStatistics::compute_multi_level_analysis_statistics(std::vecto
292291

293292
//---------------------------------------------------------------------------
294293
int ParticleShapeStatistics::compute_shape_dev_modes_for_mca() {
295-
unsigned int m = points_minus_mean_shape_dev_.rows();
296-
Eigen::MatrixXd A =
297-
points_minus_mean_shape_dev_.transpose() * points_minus_mean_shape_dev_ * (1.0 / ((double)(num_samples_ - 1)));
294+
// Use SVD to compute PCA. For X = U * S * V^T:
295+
// - The covariance matrix X^T * X / (n-1) has eigenvalues S^2 / (n-1) and eigenvectors V
296+
// - The old code computed eigenvectors_ = X * V = U * S
297+
Eigen::BDCSVD<Eigen::MatrixXd> svd(points_minus_mean_shape_dev_, Eigen::ComputeThinU | Eigen::ComputeThinV);
298298

299-
vnl_matrix<double> vnlA = vnl_matrix<double>(A.data(), A.rows(), A.cols());
300-
vnl_symmetric_eigensystem<double> symEigen(vnlA);
301-
Eigen::MatrixXd shape_dev_eigenSymEigenV =
302-
Eigen::Map<Eigen::MatrixXd>(symEigen.V.transpose().data_block(), symEigen.V.rows(), symEigen.V.cols());
303-
Eigen::VectorXd shape_dev_eigenSymEigenD = Eigen::Map<Eigen::VectorXd>(symEigen.D.data_block(), symEigen.D.rows(), 1);
299+
Eigen::VectorXd eigenvalues_eigen = svd.singularValues().array().square() / (num_samples_ - 1);
300+
int num_modes = eigenvalues_eigen.size();
304301

305-
eigenvectors_shape_dev_ = points_minus_mean_shape_dev_ * shape_dev_eigenSymEigenV;
306-
eigenvalues_shape_dev_.resize(num_samples_);
302+
// Compute eigenvectors_ = X * V = U * S (matches old behavior before normalization)
303+
eigenvectors_shape_dev_ = svd.matrixU() * svd.singularValues().asDiagonal();
307304

308-
for (unsigned int i = 0; i < num_samples_; i++) {
309-
double total = 0.0f;
310-
for (unsigned int j = 0; j < m; j++) {
311-
total += eigenvectors_shape_dev_(j, i) * eigenvectors_shape_dev_(j, i);
312-
}
313-
total = sqrt(total);
305+
// normalize the eigenvectors
306+
for (int i = 0; i < eigenvectors_shape_dev_.cols(); i++) {
307+
eigenvectors_shape_dev_.col(i).normalize();
308+
}
314309

315-
for (unsigned int j = 0; j < m; j++) {
316-
eigenvectors_shape_dev_(j, i) = eigenvectors_shape_dev_(j, i) / (total + 1.0e-15);
317-
}
318-
eigenvalues_shape_dev_[i] = shape_dev_eigenSymEigenD(i);
310+
// SVD returns values in descending order, but we need ascending order for backward compatibility
311+
eigenvalues_shape_dev_.resize(num_modes);
312+
for (int i = 0; i < num_modes; i++) {
313+
eigenvalues_shape_dev_[i] = eigenvalues_eigen[num_modes - 1 - i];
319314
}
315+
eigenvectors_shape_dev_ = eigenvectors_shape_dev_.rowwise().reverse().eval();
316+
320317
return 0;
321318
}
322319

323320
//---------------------------------------------------------------------------
324321
int ParticleShapeStatistics::compute_relative_pose_modes_for_mca() {
325-
unsigned int m = points_minus_mean_rel_pose_.rows();
326-
Eigen::MatrixXd A =
327-
points_minus_mean_rel_pose_.transpose() * points_minus_mean_rel_pose_ * (1.0 / ((double)(num_samples_ - 1)));
328-
auto vnlA = vnl_matrix<double>(A.data(), A.rows(), A.cols());
329-
vnl_symmetric_eigensystem<double> symEigen(vnlA);
330-
Eigen::MatrixXd rel_pose_eigenSymEigenV =
331-
Eigen::Map<Eigen::MatrixXd>(symEigen.V.transpose().data_block(), symEigen.V.rows(), symEigen.V.cols());
322+
// Use SVD to compute PCA. For X = U * S * V^T:
323+
// - The covariance matrix X^T * X / (n-1) has eigenvalues S^2 / (n-1) and eigenvectors V
324+
// - The old code computed eigenvectors_ = X * V = U * S
325+
Eigen::BDCSVD<Eigen::MatrixXd> svd(points_minus_mean_rel_pose_, Eigen::ComputeThinU | Eigen::ComputeThinV);
332326

333-
Eigen::VectorXd rel_pose_eigenSymEigenD = Eigen::Map<Eigen::VectorXd>(symEigen.D.data_block(), symEigen.D.rows(), 1);
327+
Eigen::VectorXd eigenvalues_eigen = svd.singularValues().array().square() / (num_samples_ - 1);
328+
int num_modes = eigenvalues_eigen.size();
334329

335-
eigenvectors_rel_pose_ = points_minus_mean_rel_pose_ * rel_pose_eigenSymEigenV;
336-
eigenvalues_rel_pose_.resize(num_samples_);
330+
// Compute eigenvectors_ = X * V = U * S (matches old behavior before normalization)
331+
eigenvectors_rel_pose_ = svd.matrixU() * svd.singularValues().asDiagonal();
337332

338-
for (unsigned int i = 0; i < num_samples_; i++) {
339-
double total = 0.0f;
340-
for (unsigned int j = 0; j < m; j++) {
341-
total += eigenvectors_rel_pose_(j, i) * eigenvectors_rel_pose_(j, i);
342-
}
343-
total = sqrt(total);
344-
345-
for (unsigned int j = 0; j < m; j++) {
346-
eigenvectors_rel_pose_(j, i) = eigenvectors_rel_pose_(j, i) / (total + 1.0e-15);
347-
}
333+
// normalize the eigenvectors
334+
for (int i = 0; i < eigenvectors_rel_pose_.cols(); i++) {
335+
eigenvectors_rel_pose_.col(i).normalize();
336+
}
348337

349-
eigenvalues_rel_pose_[i] = rel_pose_eigenSymEigenD(i);
338+
// SVD returns values in descending order, but we need ascending order for backward compatibility
339+
eigenvalues_rel_pose_.resize(num_modes);
340+
for (int i = 0; i < num_modes; i++) {
341+
eigenvalues_rel_pose_[i] = eigenvalues_eigen[num_modes - 1 - i];
350342
}
343+
eigenvectors_rel_pose_ = eigenvectors_rel_pose_.rowwise().reverse().eval();
344+
351345
return 0;
352346
}
353347

@@ -583,17 +577,21 @@ int ParticleShapeStatistics::compute_modes() {
583577
TIME_SCOPE("ParticleShapeStatistics::compute_modes");
584578
SW_DEBUG("computing PCA modes");
585579

586-
double scale = std::sqrt(num_samples_ - 1);
587-
Eigen::BDCSVD<Eigen::MatrixXd> svd(points_minus_mean_ / scale, Eigen::ComputeThinU);
580+
// Use SVD to compute PCA. For X = U * S * V^T:
581+
// - The covariance matrix X^T * X / (n-1) has eigenvalues S^2 / (n-1) and eigenvectors V
582+
// - The old code computed eigenvectors_ = X * V = U * S
583+
Eigen::BDCSVD<Eigen::MatrixXd> svd(points_minus_mean_, Eigen::ComputeThinU | Eigen::ComputeThinV);
588584

589-
Eigen::VectorXd eigenvalues_eigen = svd.singularValues().array().square();
590-
591-
// U * S gives us the eigenvectors in particle space (like points_minus_mean_ * V did before)
592-
// But we want them normalized, so just use U (which is already orthonormal)
593-
eigenvectors_ = svd.matrixU() * scale; // Scale back to match original magnitudes
585+
// Eigenvalues of X^T * X / (n-1) = singular_values^2 / (n-1)
586+
Eigen::VectorXd eigenvalues_eigen = svd.singularValues().array().square() / (num_samples_ - 1);
594587

588+
// Eigenvectors in particle space: X * V = U * S
589+
// SVD returns singular values in descending order, so we need to reverse for ascending order
595590
int num_modes = eigenvalues_eigen.size();
596591

592+
// Compute eigenvectors_ = X * V = U * S (matches old behavior before normalization)
593+
eigenvectors_ = svd.matrixU() * svd.singularValues().asDiagonal();
594+
597595
// normalize the eigenvectors
598596
for (int i = 0; i < eigenvectors_.cols(); i++) {
599597
eigenvectors_.col(i).normalize();

0 commit comments

Comments
 (0)